From d274cc20e627ca27fc3014d3b67b8a9ecf615533 Mon Sep 17 00:00:00 2001 From: bakrantz Date: Sat, 12 Apr 2014 21:14:38 -0700 Subject: [PATCH 1/4] New Renderer for Vertical and Horizontal-Step line charts. --- lib/Chart/Clicker/Renderer/Step.pm | 349 +++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 lib/Chart/Clicker/Renderer/Step.pm diff --git a/lib/Chart/Clicker/Renderer/Step.pm b/lib/Chart/Clicker/Renderer/Step.pm new file mode 100644 index 0000000..ef7558d --- /dev/null +++ b/lib/Chart/Clicker/Renderer/Step.pm @@ -0,0 +1,349 @@ +package Chart::Clicker::Renderer::Step; +$Chart::Clicker::Renderer::Step::VERSION = '2.88'; +use Moose; + +# ABSTRACT: Line renderer + +extends 'Chart::Clicker::Renderer'; + +use Geometry::Primitive::Point; +use Graphics::Primitive::Brush; +use Graphics::Primitive::Operation::Stroke; +use Geometry::Primitive::Circle; + +#number of defined points we must have around another point +#to render a line instead of a scatter + +use constant MIN_DEFINED_SURROUNDING_POINTS => 5; #B. Krantz: in this plot mode this constant may not be needed + + +has 'brush' => ( + is => 'rw', + isa => 'Graphics::Primitive::Brush', + default => sub { Graphics::Primitive::Brush->new(width => 2) } +); + + +has 'shape' => ( + is => 'rw', + isa => 'Geometry::Primitive::Shape', +); + + +has 'shape_brush' => ( + is => 'rw', + isa => 'Graphics::Primitive::Brush', +); + +#B. Krantz Hack start +#New attribute called type which designates whether this is a vertical- or horizontal-step graph +has 'type' => ( + is => 'rw', + isa => 'Str', + default => 'vertical', +); +#B. Krantz Hack end + + +sub finalize { + my ($self) = @_; + + my $width = $self->width; + my $height = $self->height; + + my $clicker = $self->clicker; + + my $dses = $clicker->get_datasets_for_context($self->context); + my %accum; + foreach my $ds (@{ $dses }) { + foreach my $series (@{ $ds->series }) { + my $ctx = $clicker->get_context($ds->context); + my $domain = $ctx->domain_axis; + my $range = $ctx->range_axis; + my $color = $clicker->color_allocator->next; + my @vals = @{ $series->values }; + my @keys = @{ $series->keys }; + +#B. Krantz Hack starts: going to change those input x and y values to stepify them + #memorize keys and vals in case needed later + my ($step_vals, $step_keys) = ([], []); + my $step_graph_type = lc($self->type); + $step_graph_type = 'vertical' unless (($step_graph_type eq 'vertical') || ($step_graph_type eq 'horizontal')); + if ($step_graph_type eq 'vertical') { + ($step_keys, $step_vals) = _vertical_stepify(\@keys, \@vals); #vertical_stepify + } + elsif ($step_graph_type eq 'horizontal') { + ($step_keys, $step_vals) = _horizontal_stepify(\@keys, \@vals); #horizontal_stepify + }; + @vals = @$step_vals; + @keys = @$step_keys; + #Note had to change the key_count index here to reflect the + #increase in the number of points due to stepification. + my $kcount = scalar(@vals) - 1; + # OLD CODE: my $kcount = $series->key_count - 1; +#B. Krantz Hack end + + my $skip = 0; + my $previous_x = -1; + my $previous_y = -1; + my $min_y_delta_on_same_x = $height / 100; + + for(0..$kcount) { + + my $key = $keys[$_]; + + my $x = $domain->mark($width, $key); + next unless defined($x); + $skip = 1 unless defined $vals[$_]; + my $ymark = $range->mark($height, $vals[$_]); + next unless defined($ymark); + + if($self->additive) { + if(exists($accum{$key})) { + $accum{$key} += $ymark; + $ymark = $accum{$key}; + } else { + $accum{$key} = $ymark; + } + } + + my $y = $height - $ymark; + if( $_ == 0 || $skip ) { + my $lineop = Graphics::Primitive::Operation::Stroke->new( + brush => $self->brush->clone + ); + $lineop->brush->color($color); + $self->do($lineop); + $self->move_to($x, $y); + + my $start_new_line = 1; + foreach my $i ($_..($_ + MIN_DEFINED_SURROUNDING_POINTS)) { + if ($i > 0 && $i < @vals && !defined($vals[$i])) { + $start_new_line = 0; + } + } + if ($start_new_line) { + $skip = 0; + } + else { + my $shape = Geometry::Primitive::Circle->new(radius => 3); + $shape->origin(Geometry::Primitive::Point->new(x => $x, y => $y)); + $self->path->add_primitive($shape); + my $fill = Graphics::Primitive::Operation::Fill->new( + paint => Graphics::Primitive::Paint::Solid->new( + color => $color + ) + ); + $self->do($fill); + } + + } + else { + # when in fast mode, we plot only if we moved by more than + # 1 of a pixel on the X axis or we moved by more than 1% + # of the size of the Y axis. + if( $clicker->plot_mode ne 'fast' || + $x - $previous_x > 1 || + abs($y - $previous_y) > $min_y_delta_on_same_x + ) + { + $self->line_to($x, $y); + $previous_x = $x; + $previous_y = $y; + } + } + + } + my $op = Graphics::Primitive::Operation::Stroke->new; + $op->brush($self->brush->clone); + $op->brush->color($color); + $self->do($op); + +#START shape hack B. Krantz +#Need to change keys and vals back to original vals and keys and need to redo $kcount + my @vals = @{ $series->values }; #B. Krantz Hack to remember original x and y's + my @keys = @{ $series->keys }; #B. Krantz Hack to remember original x and y's + $kcount = $series->key_count - 1; +#END shape hack B. Krantz + + if(defined($self->shape)) { + for(0..$kcount) { + my $key = $keys[$_]; + my $x = $domain->mark($width, $key); + next unless defined($x); + my $ymark = $range->mark($height, $vals[$_]); + next unless defined($ymark); + + if($self->additive) { + if(exists($accum{$key})) { + $ymark = $accum{$key}; + } else { + $accum{$key} = $ymark; + } + } + + my $y = $height - $ymark; + + $self->move_to($x, $y); + $self->draw_point($x, $y, $series, $vals[$_]); + } + + # Fill the shape + my $op2 = Graphics::Primitive::Operation::Fill->new( + paint => Graphics::Primitive::Paint::Solid->new( + color => $color + ) + ); + if(defined($self->shape_brush)) { + $op2->preserve(1); + } + $self->do($op2); + + # Optionally stroke the shape + if(defined($self->shape_brush)) { + my $op3 = Graphics::Primitive::Operation::Stroke->new; + $op3->brush($self->shape_brush->clone); + $self->do($op3); + } + } + + } + } + return 1; +} + +sub draw_point { + my ($self, $x, $y, $series, $count) = @_; + my $shape = $self->shape->clone; + $shape->origin(Geometry::Primitive::Point->new(x => $x, y => $y)); + $self->path->add_primitive($shape); +} + +#B. Krantz internal stepify routines +#Horizontal Step graph takes regular line graph data +#and converts to a horizontal-step style graph +sub _horizontal_stepify { + my ($x, $y, $d) = (shift(), shift(), []); + push @$d, [shift(@$x), shift(@$y)]; + while (@$x and @$y) { + push @$d, [shift(@$x), $d -> [-1] -> [1]]; + push @$d, [$d -> [-1] -> [0], shift(@$y)]; + }; + return [map $_ -> [0], @$d], [map $_ -> [1], @$d] +}; + +#Vertical Step graph takes regular line graph data +#and converts to a vertical-step style graph +sub _vertical_stepify { + my ($x, $y, $d) = (shift(), shift(), []); + push @$d, [shift(@$x), shift(@$y)]; + while (@$x and @$y) { + push @$d, [$d -> [-1] -> [0], shift(@$y)]; + push @$d, [shift(@$x), $d -> [-1] -> [1]]; + }; + return [map $_ -> [0], @$d], [map $_ -> [1], @$d] +}; +#B. Krantz Hack end + +__PACKAGE__->meta->make_immutable; + +no Moose; + +1; + +__END__ + +=pod + +=head1 NAME + +Chart::Clicker::Renderer::Step - Vertical and Horizontal-step renderer + +=head1 VERSION + +version 2.88 + +=head1 SYNOPSIS + + my $step_renderer = Chart::Clicker::Renderer::Step->new( + #Required 'type': choose 'vertical' or 'horizontal' but defaults to 'vertical' + type => 'horizontal', + #optional 'shape' if you want to display datapoints distinctively + shape => Geometry::Primitive::Circle->( radius => 3 ), + brush => Graphics::Primitive::Brush->new({ + #...some brush attributes + }) + ); + +=head1 DESCRIPTION + +Chart::Clicker::Renderer::Step renders a dataset as a special vertical- or horizontal-step line graph +often used in statistical plots. This module's guts were borrowed from L and +will use its attributes, of course, since they are preserved here. One additional attribute, 'type', +is used to specify whether the step graph is 'vertical' or 'horizontal'. The default is 'vertical'. +As the step-style graph appears to create new points in the rendering, the 'shape' attribute can be set +to show the actual data points on the line graph. + +=for HTML

Vertical Step Chart

+ +=head1 ATTRIBUTES + +=head2 additive + +If true, the lines are drawn "stacked", each key accumulates based on those drawn below it. + +=head2 brush + +Set/Get a L to be used for the lines. + +=head2 shape + +Set a L object to draw at each of the data points. +There are many shapes available in L: + +=over 5 + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + + +=head2 shape_brush + +Set/Get the L to be used on the shapes at each +point. If no shape_brush is provided, then the shapes will be filled. +The brush allows you to draw a "halo" around each shape. This sometimes help +to separate the points from the lines and make them more distinct. + +=head2 type + +Set/Get the type of Step graph. There are two options: 'vertical' or 'horizontal'. Default is 'vertical'. + +=head1 METHODS + +=head2 draw_point + +Called for each point encountered on the line. + +=head1 AUTHOR + +Cory G Watson wrote original L + +B. Krantz modified original to make L + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2014 by Cold Hard Code, LLC. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut From 409aadac1aaeb5cd74601668aac3ebbc6294956e Mon Sep 17 00:00:00 2001 From: bakrantz Date: Tue, 15 Apr 2014 17:04:44 -0700 Subject: [PATCH 2/4] Adding a new Chart::Clicker:Data:Series class, called ErrorBar --- lib/Chart/Clicker/Data/Series/ErrorBar.pm | 165 ++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 lib/Chart/Clicker/Data/Series/ErrorBar.pm diff --git a/lib/Chart/Clicker/Data/Series/ErrorBar.pm b/lib/Chart/Clicker/Data/Series/ErrorBar.pm new file mode 100644 index 0000000..e7e8e02 --- /dev/null +++ b/lib/Chart/Clicker/Data/Series/ErrorBar.pm @@ -0,0 +1,165 @@ +package Chart::Clicker::Data::Series::ErrorBar; +use Moose; + +extends 'Chart::Clicker::Data::Series'; + +# ABSTRACT: Chart data with additional attributes for Size charts + +use List::Util qw(min max); + +=head1 DESCRIPTION + +Chart::Clicker::Data::Series::ErrorBar is an extension of the Series class +that provides storage for x and y error bar values. These values are plus and minus +the given value. This is useful for the ErrorBar renderer. + +=head1 SYNOPSIS + + use Chart::Clicker::Data::Series::ErrorBar; + + my @keys = (); + my @values = (); + my @yerrors = (); + my @xerrors = (); + + my $series = Chart::Clicker::Data::Series::Size->new({ + keys => \@keys, + values => \@values, + x_errors => \@xerrors, + y_errors => \@yerrors, + }); + +=head1 ATTRIBUTES + +=head2 x_errors + +Set/Get the x error bar values for this series. + +=head2 y_errors + +Set/Get the y error bar values for this series. + +=head1 METHODS + +=head2 add_to_x_errors + +Adds a x_error value to this series as push would. + +=head2 add_to_y_errors + +Adds a y_error value to this series as push would. + +=head2 get_x_error + +Get an x_error value by it's index. + +=head2 get_y_error + +Get an y_error value by it's index. + +=head2 x_error_count + +Gets the count of x_error values in this series. + +=head2 y_error_count + +Gets the count of y_error values in this series. + +=cut + +has 'x_errors' => ( + traits => [ 'Array' ], + is => 'rw', + isa => 'ArrayRef', + default => sub { [] }, + handles => { + 'add_to_x_errors' => 'push', + 'x_error_count' => 'count', + 'get_x_error' => 'get' + } +); + +has 'y_errors' => ( + traits => [ 'Array' ], + is => 'rw', + isa => 'ArrayRef', + default => sub { [] }, + handles => { + 'add_to_y_errors' => 'push', + 'y_error_count' => 'count', + 'get_y_error' => 'get' + } +); + +=head2 max_x_error + +Gets the largest x-error value from this Series' C. + +=cut + +has max_x_error => ( + is => 'ro', + isa => 'Num', + lazy => 1, + default => sub { + my ($self) = @_; + return max(@{ $self->x_errors }); + } +); + +=head2 min_x_error + +Gets the smallest value from this Series' C. + +=cut + +has min_x_error => ( + is => 'ro', + isa => 'Num', + lazy => 1, + default => sub { + my ($self) = @_; + return min(@{ $self->x_errors }); + } +); + + +=head2 max_y_error + +Gets the largest y-error value from this Series' C. + +=cut + +has max_y_error => ( + is => 'ro', + isa => 'Num', + lazy => 1, + default => sub { + my ($self) = @_; + return max(@{ $self->y_errors }); + } +); + + +=head2 min_y_error + +Gets the smallest value from this Series' C. + +=cut + +has min_y_error => ( + is => 'ro', + isa => 'Num', + lazy => 1, + default => sub { + my ($self) = @_; + return min(@{ $self->y_errors }); + } +); + + +__PACKAGE__->meta->make_immutable; + +no Moose; + +1; From 67a45bf27601758c2a36e583c02a33e7cc4037d9 Mon Sep 17 00:00:00 2001 From: bakrantz Date: Wed, 16 Apr 2014 10:10:29 -0700 Subject: [PATCH 3/4] Simplify code --- lib/Chart/Clicker/Renderer/Bubble.pm | 49 ---------------------------- lib/Chart/Clicker/Renderer/Step.pm | 25 +++++++------- 2 files changed, 11 insertions(+), 63 deletions(-) delete mode 100644 lib/Chart/Clicker/Renderer/Bubble.pm diff --git a/lib/Chart/Clicker/Renderer/Bubble.pm b/lib/Chart/Clicker/Renderer/Bubble.pm deleted file mode 100644 index 926c892..0000000 --- a/lib/Chart/Clicker/Renderer/Bubble.pm +++ /dev/null @@ -1,49 +0,0 @@ -package Chart::Clicker::Renderer::Bubble; -use Moose; - -# ABSTRACT: Bubble render - -extends 'Chart::Clicker::Renderer::Point'; - -=head1 DESCRIPTION - -Chart::Clicker::Renderer::Bubble is a subclass of the Point renderer where -the points' radiuses are determined by the size value of a Series::Size. - -Note: B. - -=begin HTML - -

Bubble Chart

- -=end HTML - -=head1 SYNOPSIS - - my $pr = Chart::Clicker::Renderer::Bubble->new({ - shape => Geometry::Primitive::Circle->new({ - radius => 3 - }) - }); - -=method draw_point - -Called for each point. Implemented as a separate method so that subclasses -such as Bubble may override the drawing. - -=cut - -override('draw_point', sub { - my ($self, $x, $y, $series, $count) = @_; - - my $shape = $self->shape->scale($series->get_size($count)); - $shape->origin(Geometry::Primitive::Point->new(x => $x, y => $y)); - $self->path->add_primitive($shape); -}); - -__PACKAGE__->meta->make_immutable; - -no Moose; - -1; \ No newline at end of file diff --git a/lib/Chart/Clicker/Renderer/Step.pm b/lib/Chart/Clicker/Renderer/Step.pm index ef7558d..a7fbb34 100644 --- a/lib/Chart/Clicker/Renderer/Step.pm +++ b/lib/Chart/Clicker/Renderer/Step.pm @@ -63,25 +63,22 @@ sub finalize { my $color = $clicker->color_allocator->next; my @vals = @{ $series->values }; my @keys = @{ $series->keys }; + my $kcount = $series->key_count - 1; #B. Krantz Hack starts: going to change those input x and y values to stepify them - #memorize keys and vals in case needed later my ($step_vals, $step_keys) = ([], []); - my $step_graph_type = lc($self->type); - $step_graph_type = 'vertical' unless (($step_graph_type eq 'vertical') || ($step_graph_type eq 'horizontal')); - if ($step_graph_type eq 'vertical') { - ($step_keys, $step_vals) = _vertical_stepify(\@keys, \@vals); #vertical_stepify + $self->type = lc($self->type); + $self->type = 'vertical' unless (($self->type eq 'vertical') || ($self->type eq 'horizontal')); + if ($self->type eq 'vertical') { + ($step_keys, $step_vals) = _vertical_stepify(\@keys, \@vals); } - elsif ($step_graph_type eq 'horizontal') { - ($step_keys, $step_vals) = _horizontal_stepify(\@keys, \@vals); #horizontal_stepify + elsif ($self->type eq 'horizontal') { + ($step_keys, $step_vals) = _horizontal_stepify(\@keys, \@vals); }; @vals = @$step_vals; @keys = @$step_keys; - #Note had to change the key_count index here to reflect the - #increase in the number of points due to stepification. - my $kcount = scalar(@vals) - 1; - # OLD CODE: my $kcount = $series->key_count - 1; -#B. Krantz Hack end + $kcount = scalar(@vals) - 1; #Change the key_count index here to reflect the +#B. Krantz Hack end #increase in the number of points due to stepification. my $skip = 0; my $previous_x = -1; @@ -161,8 +158,8 @@ sub finalize { #START shape hack B. Krantz #Need to change keys and vals back to original vals and keys and need to redo $kcount - my @vals = @{ $series->values }; #B. Krantz Hack to remember original x and y's - my @keys = @{ $series->keys }; #B. Krantz Hack to remember original x and y's + @vals = @{ $series->values }; + @keys = @{ $series->keys }; $kcount = $series->key_count - 1; #END shape hack B. Krantz From 43a6d5ce6dcfeb4bf868491e096284773c819d75 Mon Sep 17 00:00:00 2001 From: bakrantz Date: Wed, 16 Apr 2014 10:16:37 -0700 Subject: [PATCH 4/4] Bubble lost somehow --- lib/Chart/Clicker/Renderer/Bubble.pm | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lib/Chart/Clicker/Renderer/Bubble.pm diff --git a/lib/Chart/Clicker/Renderer/Bubble.pm b/lib/Chart/Clicker/Renderer/Bubble.pm new file mode 100644 index 0000000..926c892 --- /dev/null +++ b/lib/Chart/Clicker/Renderer/Bubble.pm @@ -0,0 +1,49 @@ +package Chart::Clicker::Renderer::Bubble; +use Moose; + +# ABSTRACT: Bubble render + +extends 'Chart::Clicker::Renderer::Point'; + +=head1 DESCRIPTION + +Chart::Clicker::Renderer::Bubble is a subclass of the Point renderer where +the points' radiuses are determined by the size value of a Series::Size. + +Note: B. + +=begin HTML + +

Bubble Chart

+ +=end HTML + +=head1 SYNOPSIS + + my $pr = Chart::Clicker::Renderer::Bubble->new({ + shape => Geometry::Primitive::Circle->new({ + radius => 3 + }) + }); + +=method draw_point + +Called for each point. Implemented as a separate method so that subclasses +such as Bubble may override the drawing. + +=cut + +override('draw_point', sub { + my ($self, $x, $y, $series, $count) = @_; + + my $shape = $self->shape->scale($series->get_size($count)); + $shape->origin(Geometry::Primitive::Point->new(x => $x, y => $y)); + $self->path->add_primitive($shape); +}); + +__PACKAGE__->meta->make_immutable; + +no Moose; + +1; \ No newline at end of file