Chart-GGPlot

 view release on metacpan or  search on metacpan

lib/Chart/GGPlot/Position/Util.pm  view on Meta::CPAN

package Chart::GGPlot::Position::Util;

# ABSTRACT: Utilities internally used by Chart::GGPlot::Position

use Chart::GGPlot::Setup qw(:base :pdl);

our $VERSION = '0.002003'; # VERSION

use Data::Frame;
use List::AllUtils qw(reduce);
use PDL::Core;
use PDL::Primitive qw(which);

use Chart::GGPlot::Util qw(ifelse match pmax pmin);

use parent qw(Exporter::Tiny);

our @EXPORT_OK   = qw(collide collide2 pos_dodge pos_dodge2 pos_stack);
our %EXPORT_TAGS = ( all => \@EXPORT_OK );

fun collide_setup ($data, $width, $name, $strategy,
                   $check_width=true, $reverse=false) {
    # determine width
    if ( defined $width ) {
        unless ( List::AllUtils::all { $data->exists($_) } qw(xmin xmax) ) {
            $data->set( 'xmin', $data->at('x') - $width / 2 );
            $data->set( 'xmax', $data->at('x') + $width / 2 );
        }
    }
    else {
        unless ( List::AllUtils::all { $data->exists($_) } qw(xmin xmax) ) {
            $data->set( 'xmin', $data->at('x') );
            $data->set( 'xmax', $data->at('x') );
        }

        my $widths = ( $data->at('xmax') - $data->at('xmin') )->uniq;
        $widths = $widths->where( $widths->isgood ) if $widths->badflag;

        $width = $widths->at(0);
    }

    return { data => $data, width => $width };
}

fun collide ($data, $width, $name, $strategy,
             :$check_width=true, :$reverse=false, %rest) {
    my $dlist =
      collide_setup( $data, $width, $name, $strategy, $check_width, $reverse );
    $data  = $dlist->{data};
    $width = $dlist->{width};

    # Reorder by x position, then on group. The default stacking order
    # reverses the group in order to match the legend order.
    $data = $data->sort( [qw(xmin group)], $reverse ? true : [ true, false ] );

    # TODO: ddply to preserve the order.
    #  So firstly DF::split() shall preserve the order.
    state $ddply = sub {
        my ( $df, $vars, $func ) = @_;

        my $ids         = $df->select_columns($vars)->id;
        my $splitted    = $df->split($ids);
        my @transformed = map { $func->($_) } values %$splitted;
        return ( reduce { $a->append($b) } @transformed );
    };

    my $strategy_wrapped = sub { $strategy->( $_[0], $width, %rest ) };
    if ( $data->exists('ymax') ) {
        $data = $ddply->( $data, ['xmin'], $strategy_wrapped );
    }
    elsif ( $data->exists('y') ) {
        $data->set( 'ymax', $data->at('y') );
        $data = $ddply->( $data, ['xmin'], $strategy_wrapped );
        $data->set( 'y', $data->at('ymax') );
    }
    else {
        die "Neither y nor ymax defined";
    }

    # TODO: This is only to maintain some order to get some tests pass.
    # This should be not needed once we fix ddply.
    return $data->sort( [qw(xmin group)], $reverse ? true : [ true, false ] );
}

# Alternate version of collide() used by position_dodge2()
fun collide2 ($data, $width, $name, $strategy,
              :$check_width=true, :$reverse=false, %rest) {
    my $dlist =
      collide_setup( $data, $width, $name, $strategy, $check_width, $reverse );
    $data  = $dlist->{data};
    $width = $dlist->{width};

    # Reorder by x position, then on group. The default stacking order is
    # different than for collide() because of the order in which pos_dodge2
    # places elements
    $data = $data->sort( [qw(xmin group)], $reverse ? [ true, false ] : true );

   return $strategy->($data, $width, %rest); 
}

fun pos_dodge ($df, $width, :$n=undef) {
    $n //= $df->at('group')->uniq->length;
    if ( $n == 1 ) {
        return $df;
    }

    my $d_width = ( $df->at('xmax') - $df->at('xmin') )->max;

    my $group    = $df->at('group');
    my $groupidx = match( $group, $group->uniq->qsort );
    $df->set( 'x', $df->at('x') + $width * ( ( $groupidx + 0.5 ) / $n - 0.5 ) );

    my $half_width = $d_width / $n / 2;



( run in 1.235 second using v1.01-cache-2.11-cpan-39bf76dae61 )