File Coverage

blib/lib/GD/Graph/lines3d.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             #==========================================================================
2             # Module: GD::Graph::lines3d
3             #
4             # Copyright (C) 1999,2001 Wadsack-Allen. All Rights Reserved.
5             #
6             # Based on GD::Graph::lines.pm,v 1.10 2000/04/15 mgjv
7             # Copyright (c) 1995-1998 Martien Verbruggen
8             #
9             #--------------------------------------------------------------------------
10             # Date Modification Author
11             # -------------------------------------------------------------------------
12             # 1999SEP18 Created 3D line chart class (this module) JAW
13             # 1999SEP19 Finished overwrite 1 style JAW
14             # 1999SEP19 Polygon'd linewidth rendering JAW
15             # 2000SEP19 Converted to a GD::Graph class JAW
16             # 2000APR18 Modified for compatibility with GD::Graph 1.30 JAW
17             # 2000APR24 Fixed a lot of rendering bugs JAW
18             # 2000AUG19 Changed render code so lines have consitent width JAW
19             # 2000AUG21 Added 3d shading JAW
20             # 2000AUG24 Fixed shading top/botttom vs. postive/negative slope JAW
21             # 2000SEP04 For single point "lines" made a short segment JAW
22             # 2000OCT09 Fixed bug in rendering of legend JAW
23             #==========================================================================
24             # TODO
25             # ** The new mitred corners don't work well at data anomlies. Like
26             # the set (0,0,1,0,0,0,1,0,1) Looks really wrong!
27             # * Write a draw_data_set that draws the line so they appear to pass
28             # through one another. This means drawing a border edge at each
29             # intersection of the data lines so the points of pass-through show.
30             # Probably want to draw all filled polygons, then run through the data
31             # again finding intersections of line segments and drawing those edges.
32             #==========================================================================
33             package GD::Graph::lines3d;
34              
35 1     1   1143 use strict;
  1         3  
  1         39  
36            
37 1     1   979 use GD;
  0            
  0            
38             use GD::Graph::axestype3d;
39             use Data::Dumper;
40              
41             @GD::Graph::lines3d::ISA = qw( GD::Graph::axestype3d );
42             $GD::Graph::lines3d::VERSION = '0.63';
43              
44             my $PI = 4 * atan2(1, 1);
45              
46             my %Defaults = (
47             # The depth of the line in their extrusion
48              
49             line_depth => 10,
50             );
51              
52             sub initialise()
53             {
54             my $self = shift;
55              
56             my $rc = $self->SUPER::initialise();
57              
58             while( my($key, $val) = each %Defaults ) {
59             $self->{$key} = $val
60              
61             # *** [JAW]
62             # Should we reset the depth_3d param based on the
63             # line_depth, numsets and overwrite parameters, here?
64             #
65             } # end while
66              
67             return $rc;
68            
69             } # end initialize
70              
71             sub set
72             {
73             my $s = shift;
74             my %args = @_;
75              
76             $s->{_set_error} = 0;
77              
78             for (keys %args)
79             {
80             /^line_depth$/ and do
81             {
82             $s->{line_depth} = $args{$_};
83             delete $args{$_};
84             next;
85             };
86             }
87              
88             return $s->SUPER::set(%args);
89             } # end set
90              
91             # PRIVATE
92              
93             # [JAW] Changed to draw_data intead of
94             # draw_data_set to allow better control
95             # of multiple set rendering
96             sub draw_data
97             {
98             my $self = shift;
99             my $d = $self->{_data};
100             my $g = $self->{graph};
101              
102             $self->draw_data_overwrite( $g, $d );
103              
104             # redraw the 'zero' axis, front and right
105             if( $self->{zero_axis} ) {
106             $g->line(
107             $self->{left}, $self->{zeropoint},
108             $self->{right}, $self->{zeropoint},
109             $self->{fgci} );
110             $g->line(
111             $self->{right}, $self->{zeropoint},
112             $self->{right} + $self->{depth_3d}, $self->{zeropoint} - $self->{depth_3d},
113             $self->{fgci} );
114             } # end if
115            
116             # redraw the box face
117             if ( $self->{box_axis} ) {
118             # Axes box
119             $g->rectangle($self->{left}, $self->{top}, $self->{right}, $self->{bottom}, $self->{fgci});
120             $g->line($self->{right}, $self->{top}, $self->{right} + $self->{depth_3d}, $self->{top} - $self->{depth_3d}, $self->{fgci});
121             $g->line($self->{right}, $self->{bottom}, $self->{right} + $self->{depth_3d}, $self->{bottom} - $self->{depth_3d}, $self->{fgci});
122             } # end if
123              
124             return $self;
125            
126             } # end draw_data
127              
128             # Copied from MVERB source
129             sub pick_line_type
130             {
131             my $self = shift;
132             my $num = shift;
133              
134             ref $self->{line_types} ?
135             $self->{line_types}[ $num % (1 + $#{$self->{line_types}}) - 1 ] :
136             $num % 4 ? $num % 4 : 4
137             }
138              
139             # ----------------------------------------------------------
140             # Sub: draw_data_overwrite
141             #
142             # Args: $gd
143             # $gd The GD object to draw on
144             #
145             # Description: Draws each line segment for each set. Runs
146             # over sets, then points so that the appearance is better.
147             # ----------------------------------------------------------
148             # Date Modification Author
149             # ----------------------------------------------------------
150             # 19SEP1999 Added this for overwrite support. JW
151             # 20AUG2000 Changed structure to use points 'objects' JW
152             # ----------------------------------------------------------
153             sub draw_data_overwrite {
154             my $self = shift;
155             my $g = shift;
156             my @points_cache;
157              
158             my $i;
159             for $i (0 .. $self->{_data}->num_points())
160             {
161             my $j;
162             for $j (1 .. $self->{_data}->num_sets())
163             {
164             my @values = $self->{_data}->y_values($j) or
165             return $self->_set_error( "Impossible illegal data set: $j", $self->{_data}->error );
166              
167             if( $self->{_data}->num_points() == 1 && $i == 1 ) {
168             # Copy the first point to the "second"
169             $values[$i] = $values[0];
170             } # end if
171              
172             next unless defined $values[$i];
173              
174             # calculate offset of this line
175             # *** Should offset be the max of line_depth
176             # and depth_3d/numsets? [JAW]
177             #
178             my $offset = $self->{line_depth} * ($self->{_data}->num_sets() - $j);
179              
180             # Get the coordinates of the previous point, if this is the first
181             # point make a point object and start over (i.e. next;)
182             unless( $i ) {
183             my( $xb, $yb );
184             if (defined($self->{x_min_value}) && defined($self->{x_max_value})) {
185             ($xb, $yb) = $self->val_to_pixel( $self->{_data}->get_x($i), $values[$i], $j );
186             } else {
187             ($xb, $yb) = $self->val_to_pixel( $i + 1, $values[$i], $j );
188             } # end if
189             $xb += $offset;
190             $yb -= $offset;
191             $points_cache[$i][$j] = { coords => [$xb, $yb] };
192             next;
193             } # end unless
194              
195             # Pick a data colour, calc shading colors too, if requested
196             my( @rgb ) = $self->pick_data_clr( $j );
197             my $dsci = $self->set_clr( @rgb );
198             if( $self->{'3d_shading'} ) {
199             $self->{'3d_highlights'}[$dsci] = $self->set_clr( $self->_brighten( @rgb ) );
200             $self->{'3d_shadows'}[$dsci] = $self->set_clr( $self->_darken( @rgb ) );
201             } # end if
202              
203             # Get the type
204             my $type = $self->pick_line_type($j);
205            
206             # Get the coordinates of the this point
207             unless( ref $points_cache[$i][$j] ) {
208             my( $xe, $ye );
209             if( defined($self->{x_min_value}) && defined($self->{x_max_value}) ) {
210             ( $xe, $ye ) = $self->val_to_pixel( $self->{_data}->get_x($i), $values[$i], $j );
211             } else {
212             ( $xe, $ye ) = $self->val_to_pixel($i + 1, $values[$i], $j);
213             } # end if
214             $xe += $offset;
215             $ye -= $offset;
216             $points_cache[$i][$j] = { coords => [$xe, $ye] };
217             } # end if
218            
219             # Find the coordinates of the next point
220             if( defined $values[$i + 1] ) {
221             my( $xe, $ye );
222             if( defined($self->{x_min_value}) && defined($self->{x_max_value}) ) {
223             ( $xe, $ye ) = $self->val_to_pixel( $self->{_data}->get_x($i + 1), $values[$i + 1], $j );
224             } else {
225             ( $xe, $ye ) = $self->val_to_pixel($i + 2, $values[$i + 1], $j);
226             } # end if
227             $xe += $offset;
228             $ye -= $offset;
229             $points_cache[$i + 1][$j] = { coords => [$xe, $ye] };
230             } # end if
231              
232             if( $self->{_data}->num_points() == 1 && $i == 1 ) {
233             # Nudge the x coords back- and forwards
234             my $n = int(($self->{right} - $self->{left}) / 30);
235             $n = 2 if $n < 2;
236             $points_cache[$i][$j]{coords}[0] = $points_cache[$i - 1][$j]{coords}[0] + $n;
237             $points_cache[$i - 1][$j]{coords}[0] -= $n;
238             } # end if
239            
240             # Draw the line segment
241             $self->draw_line( $points_cache[$i - 1][$j],
242             $points_cache[$i][$j],
243             $points_cache[$i + 1][$j],
244             $type,
245             $dsci );
246            
247             # Draw the end cap if last segment
248             if( $i >= $self->{_data}->num_points() - 1 ) {
249             my $poly = new GD::Polygon;
250             $poly->addPt( $points_cache[$i][$j]{face}[0], $points_cache[$i][$j]{face}[1] );
251             $poly->addPt( $points_cache[$i][$j]{face}[2], $points_cache[$i][$j]{face}[3] );
252             $poly->addPt( $points_cache[$i][$j]{face}[2] + $self->{line_depth}, $points_cache[$i][$j]{face}[3] - $self->{line_depth} );
253             $poly->addPt( $points_cache[$i][$j]{face}[0] + $self->{line_depth}, $points_cache[$i][$j]{face}[1] - $self->{line_depth} );
254             if( $self->{'3d_shading'} ) {
255             $g->filledPolygon( $poly, $self->{'3d_shadows'}[$dsci] );
256             } else {
257             $g->filledPolygon( $poly, $dsci );
258             } # end if
259             $g->polygon( $poly, $self->{fgci} );
260             } # end if
261              
262             } # end for -- $self->{_data}->num_sets()
263             } # end for -- $self->{_data}->num_points()
264              
265             } # end sub draw_data_overwrite
266              
267             # ----------------------------------------------------------
268             # Sub: draw_line
269             #
270             # Args: $prev, $this, $next, $type, $clr
271             # $prev A hash ref for the prev point's object
272             # $this A hash ref for this point's object
273             # $next A hash ref for the next point's object
274             # $type A predefined line type (2..4) = (dashed, dotted, dashed & dotted)
275             # $clr The color (colour) index to use for the fill
276             #
277             # Point "Object" has these properties:
278             # coords A 2 element array of the coordinates for the line
279             # (this should be filled in before calling)
280             # face An 4 element array of end points for the face
281             # polygon. This will be populated by this method.
282             #
283             # Description: Draws a line segment in 3d extrusion that
284             # connects the prev point the the this point. The next point
285             # is used to calculate the mitre at the joint.
286             # ----------------------------------------------------------
287             # Date Modification Author
288             # ----------------------------------------------------------
289             # 18SEP1999 Modified MVERB source to work on data
290             # point, not data set for better rendering JAW
291             # 19SEP1999 Ploygon'd line rendering for better effect JAW
292             # 19AUG2000 Made line width perpendicular JAW
293             # 19AUG2000 Changed parameters to use %line_seg hash/obj JAW
294             # 20AUG2000 Mitred joints of line segments JAW
295             # ----------------------------------------------------------
296             sub draw_line
297             {
298             my $self = shift;
299             my( $prev, $this, $next, $type, $clr ) = @_;
300             my $xs = $prev->{coords}[0];
301             my $ys = $prev->{coords}[1];
302             my $xe = $this->{coords}[0];
303             my $ye = $this->{coords}[1];
304              
305             my $lw = $self->{line_width};
306             my $lts = $self->{line_type_scale};
307              
308             my $style = gdStyled;
309             my @pattern = ();
310              
311             LINE: {
312              
313             ($type == 2) && do {
314             # dashed
315              
316             for (1 .. $lts) { push @pattern, $clr }
317             for (1 .. $lts) { push @pattern, gdTransparent }
318              
319             $self->{graph}->setStyle(@pattern);
320              
321             last LINE;
322             };
323              
324             ($type == 3) && do {
325             # dotted,
326              
327             for (1 .. 2) { push @pattern, $clr }
328             for (1 .. 2) { push @pattern, gdTransparent }
329              
330             $self->{graph}->setStyle(@pattern);
331              
332             last LINE;
333             };
334              
335             ($type == 4) && do {
336             # dashed and dotted
337              
338             for (1 .. $lts) { push @pattern, $clr }
339             for (1 .. 2) { push @pattern, gdTransparent }
340             for (1 .. 2) { push @pattern, $clr }
341             for (1 .. 2) { push @pattern, gdTransparent }
342              
343             $self->{graph}->setStyle(@pattern);
344              
345             last LINE;
346             };
347              
348             # default: solid
349             $style = $clr;
350             }
351              
352             # [JAW] Removed the dataset loop for better results.
353              
354             # Need the setstyle to reset
355             $self->{graph}->setStyle(@pattern) if (@pattern);
356              
357             #
358             # Find the x and y offsets for the edge of the front face
359             # Do this by adjusting them perpendicularly from the line
360             # half the line width in front and in back.
361             #
362             my( $lwyoff, $lwxoff );
363             if( $xe == $xs ) {
364             $lwxoff = $lw / 2;
365             $lwyoff = 0;
366             } elsif( $ye == $ys ) {
367             $lwxoff = 0;
368             $lwyoff = $lw / 2;
369             } else {
370             my $ln = sqrt( ($ys-$ye)**2 + ($xe-$xs)**2 );
371             $lwyoff = ($xe-$xs) / $ln * $lw / 2;
372             $lwxoff = ($ys-$ye) / $ln * $lw / 2;
373             } # end if
374              
375             # For first line, figure beginning point
376             unless( defined $prev->{face}[0] ) {
377             $prev->{face} = [];
378             $prev->{face}[0] = $xs - $lwxoff;
379             $prev->{face}[1] = $ys - $lwyoff;
380             $prev->{face}[2] = $xs + $lwxoff;
381             $prev->{face}[3] = $ys + $lwyoff;
382             } # end unless
383            
384             # Calc and store this point's face coords
385             unless( defined $this->{face}[0] ) {
386             $this->{face} = [];
387             $this->{face}[0] = $xe - $lwxoff;
388             $this->{face}[1] = $ye - $lwyoff;
389             $this->{face}[2] = $xe + $lwxoff;
390             $this->{face}[3] = $ye + $lwyoff;
391             } # end if
392            
393             # Now find next point and nudge these coords to mitre
394             if( ref $next->{coords} eq 'ARRAY' ) {
395             my( $lwyo2, $lwxo2 );
396             my( $x2, $y2 ) = @{$next->{coords}};
397             if( $x2 == $xe ) {
398             $lwxo2 = $lw / 2;
399             $lwyo2 = 0;
400             } elsif( $y2 == $ye ) {
401             $lwxo2 = 0;
402             $lwyo2 = $lw / 2;
403             } else {
404             my $ln2 = sqrt( ($ye-$y2)**2 + ($x2-$xe)**2 );
405             $lwyo2 = ($x2-$xe) / $ln2 * $lw / 2;
406             $lwxo2 = ($ye-$y2) / $ln2 * $lw / 2;
407             } # end if
408             $next->{face} = [];
409             $next->{face}[0] = $x2 - $lwxo2;
410             $next->{face}[1] = $y2 - $lwyo2;
411             $next->{face}[2] = $x2 + $lwxo2;
412             $next->{face}[3] = $y2 + $lwyo2;
413            
414             # Now get the intersecting coordinates
415             my $mt = ($ye - $ys)/($xe - $xs);
416             my $mn = ($y2 - $ye)/($x2 - $xe);
417             my $bt = $this->{face}[1] - $this->{face}[0] * $mt;
418             my $bn = $next->{face}[1] - $next->{face}[0] * $mn;
419             if( $mt != $mn ) {
420             $this->{face}[0] = ($bn - $bt) / ($mt - $mn);
421             } # end if
422             $this->{face}[1] = $mt * $this->{face}[0] + $bt;
423             $bt = $this->{face}[3] - $this->{face}[2] * $mt;
424             $bn = $next->{face}[3] - $next->{face}[2] * $mn;
425             if( $mt != $mn ) {
426             $this->{face}[2] = ($bn - $bt) / ($mt - $mn);
427             } # end if
428             $this->{face}[3] = $mt * $this->{face}[2] + $bt;
429             } # end if
430              
431              
432             # Make the top/bottom polygon
433             my $poly = new GD::Polygon;
434             if( ($ys-$ye)/($xe-$xs) > 1 ) {
435             $poly->addPt( $prev->{face}[2], $prev->{face}[3] );
436             $poly->addPt( $this->{face}[2], $this->{face}[3] );
437             $poly->addPt( $this->{face}[2] + $self->{line_depth}, $this->{face}[3] - $self->{line_depth} );
438             $poly->addPt( $prev->{face}[2] + $self->{line_depth}, $prev->{face}[3] - $self->{line_depth} );
439             if( $self->{'3d_shading'} && $style == $clr ) {
440             if( ($ys-$ye)/($xe-$xs) > 0 ) {
441             $self->{graph}->filledPolygon( $poly, $self->{'3d_shadows'}[$clr] );
442             } else {
443             $self->{graph}->filledPolygon( $poly, $self->{'3d_highlights'}[$clr] );
444             } # end if
445             } else {
446             $self->{graph}->filledPolygon( $poly, $style );
447             } # end if
448             } else {
449             $poly->addPt( $prev->{face}[0], $prev->{face}[1] );
450             $poly->addPt( $this->{face}[0], $this->{face}[1] );
451             $poly->addPt( $this->{face}[0] + $self->{line_depth}, $this->{face}[1] - $self->{line_depth} );
452             $poly->addPt( $prev->{face}[0] + $self->{line_depth}, $prev->{face}[1] - $self->{line_depth} );
453             if( $self->{'3d_shading'} && $style == $clr ) {
454             if( ($ys-$ye)/($xe-$xs) < 0 ) {
455             $self->{graph}->filledPolygon( $poly, $self->{'3d_shadows'}[$clr] );
456             } else {
457             $self->{graph}->filledPolygon( $poly, $self->{'3d_highlights'}[$clr] );
458             } # end if
459             } else {
460             $self->{graph}->filledPolygon( $poly, $style );
461             } # end if
462             } # end if
463             $self->{graph}->polygon( $poly, $self->{fgci} );
464              
465             # *** This paints dashed and dotted patterns on the faces of
466             # the polygons. They don't look very good though. Would it
467             # be better to extrude the style as well as the lines?
468             # Otherwise could also be improved by using gdTiled instead of
469             # gdStyled and making the tile a transform of the line style
470             # for each face. [JAW]
471              
472             # Make the face polygon
473             $poly = new GD::Polygon;
474             $poly->addPt( $prev->{face}[0], $prev->{face}[1] );
475             $poly->addPt( $this->{face}[0], $this->{face}[1] );
476             $poly->addPt( $this->{face}[2], $this->{face}[3] );
477             $poly->addPt( $prev->{face}[2], $prev->{face}[3] );
478              
479             $self->{graph}->filledPolygon( $poly, $style );
480             $self->{graph}->polygon( $poly, $self->{fgci} );
481              
482             } # end draw line
483              
484             # ----------------------------------------------------------
485             # Sub: draw_legend_marker
486             #
487             # Args: $dsn, $x, $y
488             # $dsn The dataset number to draw the marker for
489             # $x The x position of the marker
490             # $y The y position of the marker
491             #
492             # Description: Draws the legend marker for the specified
493             # dataset number at the given coordinates
494             # ----------------------------------------------------------
495             # Date Modification Author
496             # ----------------------------------------------------------
497             # 2000OCT06 Fixed rendering bugs JW
498             # ----------------------------------------------------------
499             sub draw_legend_marker
500             {
501             my $self = shift;
502             my ($n, $x, $y) = @_;
503              
504             my $ci = $self->set_clr($self->pick_data_clr($n));
505             my $type = $self->pick_line_type($n);
506              
507             $y += int($self->{lg_el_height}/2);
508              
509             # Joe Smith
510             local($self->{line_width}) = 2; # Make these show up better
511              
512             $self->draw_line(
513             { coords => [$x, $y] },
514             { coords => [$x + $self->{legend_marker_width}, $y] },
515             undef,
516             $type,
517             $ci
518             );
519              
520             } # end draw_legend_marker
521              
522             1;