Chart-GGPlot

 view release on metacpan or  search on metacpan

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

package Chart::GGPlot::Util::Scales;

# ABSTRACT: R 'scales' package functions used by Chart::GGPlot

# TODO: make a separate library, e.g. Color::Scales for the color palettes.
#  Some of the color palette function's signature may change.

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

our $VERSION = '0.002003'; # VERSION

use Color::Brewer;
use Color::Library;
use Convert::Color::LCh;
use Data::Munge qw(elem);
use Machine::Epsilon qw(machine_epsilon);
use Math::Round qw(round);
use Memoize;
use PDL::Primitive qw(which interpol);
use PDL::Graphics::ColorSpace 0.203 qw(rgb_to_lab lab_to_rgb);
use Number::Format 1.75;
use Scalar::Util qw(looks_like_number);
use Time::Moment;
use Types::Standard qw(Int);

use POSIX qw(ceil floor log10);

use Role::Tiny ();

use Type::Params;
use Types::PDL qw(Piddle Piddle1D PiddleFromAny);
use Types::Standard qw(ArrayRef ConsumerOf Num Optional Str Maybe);

use Chart::GGPlot::Types qw(:all);
use Chart::GGPlot::Util::_Base qw(:all);
use Chart::GGPlot::Util::_Labeling qw(:all);
use Chart::GGPlot::Util::Scales::_Viridis;

use parent qw(Exporter::Tiny);

our @EXPORT_OK = qw(
  censor discard expand_range zero_range
  rescale squish
  hue_pal brewer_pal gradient_n_pal rescale_pal viridis_pal
  seq_gradient_pal div_gradient_pal
  area_pal
  identity_pal
  extended_breaks regular_minor_breaks log_breaks
  pretty pretty_breaks
  number comma percent dollar
  rgb255_to_csshex rgb_to_csshex
  csshex_to_rgb255
  colorname_to_csshex
);
our %EXPORT_TAGS = ( all => \@EXPORT_OK );


fun censor ( $p, $range = pdl([ 0, 1 ]), $only_finite = true ) {
    my ( $min, $max ) = $range->minmax;
    my $finite = $only_finite ? $p->isfinite : PDL->ones( $p->length );
    return $p->setbadif( $finite & ( ( $p < $min ) | ( $p > $max ) ) );
}


fun discard ( $p, $range = pdl([ 0, 1 ]) ) {
    my ( $min, $max ) = $range->minmax;
    return $p->where( ( ( $p >= $min ) & ( $p <= $max ) ) );
}

# Expand a range with a multiplicative or additive constant
fun expand_range ( $range, $mul = 0, $add = 0, $zero_width = 1 ) {
    state $check =
      Type::Params::compile( Piddle->plus_coercions(PiddleFromAny) );
    ($range) = $check->($range);

    return if ( $range->isempty );  # TODO: return undef or return empty $range?

    my ( $min, $max ) = $range->minmax;
    if ( zero_range($range) ) {
        return pdl( [ $max - $zero_width / 2, $min + $zero_width / 2 ] );
    }
    else {
        my $delta = ( $max - $min ) * $mul + $add;
        return pdl( [ $min - $delta, $max + $delta ] );
    }
}


fun zero_range ( $range, $tol = 1000 * machine_epsilon() ) {
    state $check =
      Type::Params::compile( Piddle1D->where( sub { $_->length == 2 } ) );
    ($range) = $check->($range);
    return ( abs( $range->at(1) - $range->at(0) ) < $tol );
}


fun squish ( $p, $range = pdl([ 0, 1 ]), $only_finite = true ) {
    my ( $min, $max ) = $range->minmax;
    my $finite = $only_finite ? $p->isfinite : PDL->ones( $p->length );
    my $r = $p->copy;

    $r->where( ( $finite & ( $r < $min ) ) ) .= $min;
    $r->where( ( $finite & ( $r > $max ) ) ) .= $max;
    return $r;
}

## scale

# Rescale range to have specified minimum and maximum
fun rescale ( $p, $to = pdl([0, 1]), $from = range_($p) ) {

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

}


fun pretty_breaks($n=5, %rest) {
    return sub {
        my ($x) = @_;

        my $f = $x->$_DOES('PDL::DateTime') ? 'pretty_dt' : 'pretty';
        no strict 'refs';
        return $f->($x, n=>$n, %rest);
    };
}

fun dollar ($p, :$accuracy=undef, :$scale=1, 
            :$prefix='$', :$suffix='',
            :$big_mark=',', :$decimal_mark='.',
            :$largest_with_cents=1e5, :$negative_parens=false) {
    return PDL::SV->new( [] ) if ( $p->length == 0 );

    $accuracy //= _need_cents( $p * $scale, $largest_with_cents ) ? 0.01 : 1;
    my $precision = List::AllUtils::max( -floor( log10($accuracy) ), 0 );
    my $negative  = ( $p->isgood & ( $p < 0 ) );

    my $fmt = Number::Format->new(
        -thousands_sep     => $big_mark,
        -mon_thousands_sep => $big_mark,
        -decimal_point     => $decimal_mark,
        -mon_decimal_point => $decimal_mark,
        -int_curr_symbol   => $prefix,
        ( $negative_parens ? ( -n_sign_posn => 0 ) : () ),

        # Number::Format would use locale's settings like P/N_SEP_BY_SPACE.
        # Here we force these values to align with R's scale::dollar to make
        # the behavior simple.
        -p_sep_by_space => 0,
        -n_sep_by_space => 0,
        -p_cs_precedes  => 1,
        -n_cs_precedes  => 1,
    );

    no warnings 'numeric';
    my @amount = map { $fmt->format_price( $_, $precision ); } @{ $p->unpdl };

    my $rslt = PDL::SV->new( \@amount );
    $rslt = $rslt->setbadif( $p->isbad ) if $p->badflag;
    return $rslt;
}

fun _need_cents ($p, $threshold) {
    return false if ($p->badflag and $p->isbad->all);
    return false if ($p->abs->max > $threshold);
    return !(
        (
            $p->badflag
            ? ( ( $p->floor == $p ) | $p->isbad )
            : ( $p->floor == $p )
        )->all
    );
}

fun percent ($p, :$accuracy=undef, :$scale=100,
             :$prefix='', :$suffix="%",
             :$big_mark=',', :$decimal_mark='.'
        ) {
    return number(
        $p,
        accuracy => $accuracy,
        scale    => $scale,
        prefix   => $prefix,
        suffix   => $suffix
    );
}

fun comma ($p, :$big_mark=',', %rest) {
    return number($p, big_mark => $big_mark, %rest);
}

fun number ($p, :$accuracy=1, :$scale=1,
            :$big_mark=' ', :$decimal_mark='.',
            :$prefix='', :$suffix=''
        ) {
    return PDL::SV->new( [] ) if $p->length == 0;

    $accuracy //= _accuracy($p);
    my $precision =
      List::AllUtils::max( -floor( log10( $accuracy / $scale ) ), 0 );
    my $fmt = Number::Format->new(
        -thousands_sep => $big_mark,
        -decimal_point => $decimal_mark
    );

    my @s = ( $p * $scale )->list;
    no warnings 'numeric';
    @s = map { "${prefix}${_}${suffix}" }
      map { $_ eq 'BAD' ? $_ : $fmt->format_number( $_, $precision ); } @s;

    my $rslt = PDL::SV->new( \@s );
    $rslt->setbadif( $p->isbad ) if $p->badflag;
    return $rslt;
}

fun _accuracy ($p) {
    return 1 if ((!$p->isfinite)->all);

    my $rng = range_($p, true, true);
    my $span = zero_range($rng) ? $rng->at(0)->abs : $rng->at(1) - $rng->at(0);
    return 1 if ($span == 0);
    return 10 ** (pdl($span)->log10->floor);  
}


sub rgb255_to_csshex { sprintf("#%02x%02x%02x", @_); }

sub rgb_to_csshex {
    rgb255_to_csshex(
        map { List::Util::max( 0, List::Util::min( 255, int( $_ * 256.0 ) ) ) }
          @_ );
}

sub csshex_to_rgb255 {
    my ($csshex) = @_;



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