1package Chart::Clicker::Renderer::Line; 2$Chart::Clicker::Renderer::Line::VERSION = '2.90'; 3use Moose; 4 5# ABSTRACT: Line renderer 6 7extends 'Chart::Clicker::Renderer'; 8 9use Geometry::Primitive::Point; 10use Graphics::Primitive::Brush; 11use Graphics::Primitive::Operation::Stroke; 12use Geometry::Primitive::Circle; 13 14#number of defined points we must have around another point 15#to render a line instead of a scatter 16# 17use constant MIN_DEFINED_SURROUNDING_POINTS => 5; 18 19 20has 'brush' => ( 21 is => 'rw', 22 isa => 'Graphics::Primitive::Brush', 23 default => sub { Graphics::Primitive::Brush->new(width => 2) } 24); 25 26 27has 'shape' => ( 28 is => 'rw', 29 isa => 'Geometry::Primitive::Shape', 30); 31 32 33has 'shape_brush' => ( 34 is => 'rw', 35 isa => 'Graphics::Primitive::Brush', 36); 37# TODO Readd shapes 38 39sub finalize { 40 my ($self) = @_; 41 42 my $width = $self->width; 43 my $height = $self->height; 44 45 my $clicker = $self->clicker; 46 47 my $dses = $clicker->get_datasets_for_context($self->context); 48 my %accum; 49 foreach my $ds (@{ $dses }) { 50 foreach my $series (@{ $ds->series }) { 51 52 # TODO if undef... 53 my $ctx = $clicker->get_context($ds->context); 54 my $domain = $ctx->domain_axis; 55 my $range = $ctx->range_axis; 56 57 my $color = $clicker->color_allocator->next; 58 59 my @vals = @{ $series->values }; 60 my @keys = @{ $series->keys }; 61 62 my $kcount = $series->key_count - 1; 63 64 my $skip = 0; 65 my $previous_x = -1; 66 my $previous_y = -1; 67 my $min_y_delta_on_same_x = $height / 100; 68 69 for(0..$kcount) { 70 71 my $key = $keys[$_]; 72 73 my $x = $domain->mark($width, $key); 74 next unless defined($x); 75 $skip = 1 unless defined $vals[$_]; 76 my $ymark = $range->mark($height, $vals[$_]); 77 next unless defined($ymark); 78 79 if($self->additive) { 80 if(exists($accum{$key})) { 81 $accum{$key} += $ymark; 82 $ymark = $accum{$key}; 83 } else { 84 $accum{$key} = $ymark; 85 } 86 } 87 88 my $y = $height - $ymark; 89 if( $_ == 0 || $skip ) { 90 my $lineop = Graphics::Primitive::Operation::Stroke->new( 91 brush => $self->brush->clone 92 ); 93 $lineop->brush->color($color); 94 $self->do($lineop); 95 $self->move_to($x, $y); 96 my $start_new_line = 1; 97 foreach my $i ($_..($_ + MIN_DEFINED_SURROUNDING_POINTS)) { 98 if ($i > 0 && $i < @vals && !defined($vals[$i])) { 99 $start_new_line = 0; 100 } 101 } 102 if ($start_new_line){ 103 $skip = 0; 104 } 105 else { 106 my $shape = Geometry::Primitive::Circle->new(radius => 3); 107 $shape->origin(Geometry::Primitive::Point->new(x => $x, y => $y)); 108 $self->path->add_primitive($shape); 109 my $fill = Graphics::Primitive::Operation::Fill->new( 110 paint => Graphics::Primitive::Paint::Solid->new( 111 color => $color 112 ) 113 ); 114 $self->do($fill); 115 } 116 } 117 else { 118 # when in fast mode, we plot only if we moved by more than 119 # 1 of a pixel on the X axis or we moved by more than 1% 120 # of the size of the Y axis. 121 if( $clicker->plot_mode ne 'fast' || 122 $x - $previous_x > 1 || 123 abs($y - $previous_y) > $min_y_delta_on_same_x 124 ) 125 { 126 $self->line_to($x, $y); 127 $previous_x = $x; 128 $previous_y = $y; 129 } 130 } 131 132 } 133 my $op = Graphics::Primitive::Operation::Stroke->new; 134 $op->brush($self->brush->clone); 135 $op->brush->color($color); 136 $self->do($op); 137 138 if(defined($self->shape)) { 139 for(0..$kcount) { 140 my $key = $keys[$_]; 141 my $x = $domain->mark($width, $key); 142 next unless defined($x); 143 my $ymark = $range->mark($height, $vals[$_]); 144 next unless defined($ymark); 145 146 if($self->additive) { 147 if(exists($accum{$key})) { 148 $ymark = $accum{$key}; 149 } else { 150 $accum{$key} = $ymark; 151 } 152 } 153 154 my $y = $height - $ymark; 155 156 $self->move_to($x, $y); 157 $self->draw_point($x, $y, $series, $vals[$_]); 158 } 159 160 # Fill the shape 161 my $op2 = Graphics::Primitive::Operation::Fill->new( 162 paint => Graphics::Primitive::Paint::Solid->new( 163 color => $color 164 ) 165 ); 166 if(defined($self->shape_brush)) { 167 $op2->preserve(1); 168 } 169 $self->do($op2); 170 171 # Optionally stroke the shape 172 if(defined($self->shape_brush)) { 173 my $op3 = Graphics::Primitive::Operation::Stroke->new; 174 $op3->brush($self->shape_brush->clone); 175 $self->do($op3); 176 } 177 } 178 } 179 } 180 181 return 1; 182} 183 184 185sub draw_point { 186 my ($self, $x, $y, $series, $count) = @_; 187 188 my $shape = $self->shape->clone; 189 $shape->origin(Geometry::Primitive::Point->new(x => $x, y => $y)); 190 $self->path->add_primitive($shape); 191} 192 193__PACKAGE__->meta->make_immutable; 194 195no Moose; 196 1971; 198 199__END__ 200 201=pod 202 203=head1 NAME 204 205Chart::Clicker::Renderer::Line - Line renderer 206 207=head1 VERSION 208 209version 2.90 210 211=head1 SYNOPSIS 212 213 my $lr = Chart::Clicker::Renderer::Line->new( 214 brush => Graphics::Primitive::Brush->new({ 215 #... 216 }) 217 ); 218 219=head1 DESCRIPTION 220 221Chart::Clicker::Renderer::Line renders a dataset as lines. 222 223=for HTML <p><img src="http://gphat.github.com/chart-clicker/static/images/examples/line.png" width="500" height="250" alt="Line Chart" /></p> 224 225=for HTML <p><img src="http://gphat.github.com/chart-clicker/static/images/examples/line-shapes.png" width="500" height="250" alt="Line + Shape Chart" /></p> 226 227=for HTML <p><img src="http://gphat.github.com/chart-clicker/static/images/examples/line-shapes-brushed.png" width="500" height="250" alt="Line + Shape (Brushed) Chart" /></p> 228 229=head1 ATTRIBUTES 230 231=head2 additive 232 233If true, the lines are drawn "stacked", each key accumulates based on those 234drawn below it. 235 236=head2 brush 237 238Set/Get a L<brush|Graphics::Primitive::Brush> to be used for the lines. 239 240=head2 shape 241 242Set a L<shape|Geometry::Primitive::Shape> object to draw at each of the data points. Adding a shape results 243in: 244 245=head2 shape_brush 246 247Set/Get the L<brush|Graphics::Primitive::Brush> to be used on the shapes at 248each point. If no shape_brush is provided, then the shapes will be filled. 249The brush allows you to draw a "halo" around each shape. This sometimes help 250to separate the points from the lines and make them more distinct. 251 252=head1 METHODS 253 254=head2 draw_point 255 256Called for each point encountered on the line. 257 258=head1 AUTHOR 259 260Cory G Watson <gphat@cpan.org> 261 262=head1 COPYRIGHT AND LICENSE 263 264This software is copyright (c) 2016 by Cory G Watson. 265 266This is free software; you can redistribute it and/or modify it under 267the same terms as the Perl 5 programming language system itself. 268 269=cut 270