File Coverage

blib/lib/GD/Graph/axestype.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             # Copyright (c) 1995-1998 Martien Verbruggen
3             #--------------------------------------------------------------------------
4             #
5             # Name:
6             # GD::Graph::axestype.pm
7             #
8             # $Id: axestype.pm,v 1.45 2007/04/26 03:16:09 ben Exp $
9             #
10             #==========================================================================
11              
12             package GD::Graph::axestype;
13              
14             ($GD::Graph::axestype::VERSION) = '$Revision: 1.45 $' =~ /\s([\d.]+)/;
15              
16 2     2   11 use strict;
  2         3  
  2         64  
17            
18 2     2   1188 use GD::Graph;
  0            
  0            
19             use GD::Graph::utils qw(:all);
20             use Carp;
21              
22             @GD::Graph::axestype::ISA = qw(GD::Graph);
23              
24             use constant PI => 4 * atan2(1,1);
25              
26             my %Defaults = (
27            
28             # Set the length for the 'short' ticks on the axes.
29             x_tick_length => 4,
30             y_tick_length => 4,
31            
32             # Do you want ticks to span the entire width of the graph?
33             x_long_ticks => 0,
34             y_long_ticks => 0,
35            
36             # Number of ticks for the y axis
37             y_tick_number => 5,
38             x_tick_number => undef, # CONTRIB Scott Prahl
39             x_tick_offset => 0, # CONTRIB Damon Brodi
40            
41             # Skip every nth label. if 1 will print every label on the axes,
42             # if 2 will print every second, etc..
43             x_label_skip => 1,
44             y_label_skip => 1,
45              
46             # When skipping labels, also skip the last one.
47             x_last_label_skip => 0,
48              
49             # Do we want ticks on the x axis?
50             x_ticks => 1,
51             x_all_ticks => 0,
52              
53             # Where to place the x and y labels
54             x_label_position => 3/4,
55             y_label_position => 1/2,
56              
57             # vertical printing of x labels
58             x_labels_vertical => 0,
59            
60             # Draw axes as a box? (otherwise just left and bottom)
61             box_axis => 1,
62              
63             # Disable axes?
64             # undef -> all axes, 0 -> Only line for bars, other -> no axes at all.
65             no_axes => undef,
66            
67             # Use two different axes for the first and second dataset. The first
68             # will be displayed using the left axis, the second using the right
69             # axis. You cannot use more than two datasets when this option is on.
70             two_axes => 0,
71              
72             # Which axis to use for each dataset. This only is in effect when
73             # two_axes is true. The axis number will wrap around, just like
74             # the dclrs array.
75             use_axis => [1, 2],
76            
77             # Print values on the axes?
78             x_plot_values => 1,
79             y_plot_values => 1,
80            
81             # Space between axis and text
82             axis_space => 4,
83            
84             # Do you want bars to be drawn on top of each other, or side by side?
85             overwrite => 0,
86              
87             # This will replace 'overwrite = 2'. For now, it is hardcoded to set
88             # overwrite to 2
89             cumulate => 0,
90              
91             # Do you want me to correct the width of the graph, so that bars are
92             # always drawn with a nice integer number of pixels?
93             #
94             # The GD::Graph::bars::initialise sub will switch this on.
95             # Do not set this to anything else than undef!
96             correct_width => undef,
97              
98             # XXX The following two need to get better defaults. Maybe computed.
99             # Draw the zero axis in the graph in case there are negative values
100             zero_axis => 0,
101              
102             # Draw the zero axis, but do not draw the bottom axis, in case
103             # box-axis == 0
104             # This also moves the x axis labels to the zero axis
105             zero_axis_only => 0,
106              
107             # Size of the legend markers
108             legend_marker_height => 8,
109             legend_marker_width => 12,
110             legend_spacing => 4,
111             legend_placement => 'BC', # '[BR][LCR]'
112             lg_cols => undef,
113              
114             # Display the y values above the bar or point in the graph.
115             show_values => undef,
116             hide_overlapping_values => 0,
117             values_vertical => undef, # vertical?
118             values_space => 4, # extra spacing
119             values_format => undef, # how to format the value
120            
121             # Draw the X axis left and the y1 axis at the bottom (y2 at top)
122             rotate_chart => undef,
123              
124             # CONTRIB Edwin Hildebrand
125             # How narrow is a dataset allowed to become before we drop the
126             # accents?
127             accent_treshold => 4,
128              
129             # Format of the numbers on the x and y axis
130             y_number_format => undef,
131             y1_number_format => undef, # CONTRIB Andrew OBrien
132             y2_number_format => undef, # CONTRIB Andrew OBrien
133             x_number_format => undef, # CONTRIB Scott Prahl
134              
135             # and some attributes without default values
136             x_label => undef,
137             y_label => undef,
138             y1_label => undef,
139             y2_label => undef,
140             x_min_value => undef,
141             x_max_value => undef,
142             y_min_value => undef,
143             y1_min_value => undef,
144             y2_min_value => undef,
145             y_max_value => undef,
146             y1_max_value => undef,
147             y2_max_value => undef,
148             y_min_range => undef, # CONTRIB Ben Tilly
149             y1_min_range => undef,
150             y2_min_range => undef,
151              
152             borderclrs => undef,
153              
154             # XXX
155             # Multiple inheritance (linespoints and mixed) finally bit me. The
156             # _has_defaults and set methods can only work correctly when the
157             # spot where the defaults are kept are in a mutual parent, which
158             # would be this. The odd implementation of SUPER doesn't help
159              
160             # XXX points
161             # The size of the marker to use in the points and linespoints graphs
162             # in pixels
163             marker_size => 4,
164              
165             # attributes with no default
166             markers => undef,
167              
168             # XXX lines
169             # The width of the line to use in the lines and linespoints graphs
170             # in pixels
171             line_width => 1,
172              
173             # Set the scale of the line types
174             line_type_scale => 8,
175              
176             # Which line types to use
177             line_types => [1],
178              
179             # Skip undefined values, and don't draw them at all
180             skip_undef => 0,
181              
182             # XXX bars
183             # Spacing between the bars and groups of bars
184             bar_width => undef,
185             bar_spacing => 0,
186             bargroup_spacing=> 0, # CONTRIB Grant McLean
187              
188             # cycle through colours per data point, not set
189             cycle_clrs => 0,
190              
191             # colour of the shadow
192             shadowclr => 'dgray',
193             shadow_depth => 0,
194              
195             # XXX mixed
196             default_type => 'lines',
197             types => undef,
198             );
199              
200             sub _has_default {
201             my $self = shift;
202             my $attr = shift || return;
203             exists $Defaults{$attr} || $self->SUPER::_has_default($attr);
204             }
205              
206             sub initialise
207             {
208             my $self = shift;
209              
210             $self->SUPER::initialise();
211              
212             while (my($key, $val) = each %Defaults)
213             { $self->{$key} = $val }
214              
215             $self->set_x_label_font(GD::gdSmallFont);
216             $self->set_y_label_font(GD::gdSmallFont);
217             $self->set_x_axis_font(GD::gdTinyFont);
218             $self->set_y_axis_font(GD::gdTinyFont);
219             $self->set_legend_font(GD::gdTinyFont);
220             $self->set_values_font(GD::gdTinyFont);
221             }
222              
223             # PUBLIC
224             sub plot
225             {
226             my $self = shift;
227             my $data = shift;
228              
229             $self->check_data($data) or return;
230             $self->init_graph() or return;
231             $self->setup_text() or return;
232             $self->setup_legend();
233             $self->setup_coords() or return;
234             $self->draw_text();
235             unless (defined $self->{no_axes})
236             {
237             $self->draw_axes();
238             $self->draw_ticks() or return;
239             }
240             $self->draw_data() or return;
241             $self->draw_values() or return;
242             $self->draw_legend();
243              
244             return $self->{graph}
245             }
246              
247             sub set
248             {
249             my $self = shift;
250             my %args = @_;
251              
252             for (keys %args)
253             {
254             /^tick_length$/ and do
255             {
256             $self->{x_tick_length} =
257             $self->{y_tick_length} = $args{$_};
258             delete $args{$_};
259             next;
260             };
261             /^long_ticks$/ and do
262             {
263             $self->{x_long_ticks} =
264             $self->{y_long_ticks} = $args{$_};
265             delete $args{$_};
266             next;
267             };
268             /^overwrite$/ and do
269             {
270             $self->{cumulate} = 1 if $args{$_} == 2;
271             $self->{overwrite} = $args{$_};
272             delete $args{$_};
273             next;
274             };
275             /^cumulate$/ and do
276             {
277             $self->{cumulate} = $args{$_};
278             # XXX And for now
279             $self->{overwrite} = 2 if $args{$_};
280             delete $args{$_};
281             next;
282             };
283             }
284              
285             return $self->SUPER::set(%args);
286             }
287              
288             sub setup_text
289             {
290             my $self = shift;
291              
292             $self->{gdta_x_label}->set(colour => $self->{lci});
293             $self->{gdta_y_label}->set(colour => $self->{lci});
294             $self->{xlfh} = $self->{gdta_x_label}->get('height');
295             $self->{ylfh} = $self->{gdta_y_label}->get('height');
296              
297             $self->{gdta_x_axis}->set(colour => $self->{alci});
298             $self->{gdta_y_axis}->set(colour => $self->{alci});
299             $self->{xafh} = $self->{gdta_x_axis}->get('height');
300             $self->{yafh} = $self->{gdta_x_axis}->get('height');
301              
302             $self->{gdta_title}->set(colour => $self->{tci});
303             $self->{gdta_title}->set_align('top', 'center');
304             $self->{tfh} = $self->{gdta_title}->get('height');
305              
306             $self->{gdta_legend}->set(colour => $self->{legendci});
307             $self->{gdta_legend}->set_align('top', 'left');
308             $self->{lgfh} = $self->{gdta_legend}->get('height');
309              
310             $self->{gdta_values}->set(colour => $self->{valuesci});
311             unless ($self->{rotate_chart})
312             {
313             if ($self->{values_vertical})
314             {
315             $self->{gdta_values}->set_align('center', 'left');
316             }
317             else
318             {
319             $self->{gdta_values}->set_align('bottom', 'center');
320             }
321             }
322             else
323             {
324             if ($self->{values_vertical})
325             {
326             $self->{gdta_values}->set_align('top', 'center');
327             }
328             else
329             {
330             $self->{gdta_values}->set_align('center', 'left');
331             }
332             }
333              
334             return $self;
335             }
336              
337             sub set_x_label_font # (fontname)
338             {
339             my $self = shift;
340             $self->_set_font('gdta_x_label', @_);
341             }
342             sub set_y_label_font # (fontname)
343             {
344             my $self = shift;
345             $self->_set_font('gdta_y_label', @_);
346             }
347             sub set_x_axis_font # (fontname)
348             {
349             my $self = shift;
350             $self->_set_font('gdta_x_axis', @_);
351             }
352              
353             sub set_y_axis_font # (fontname)
354             {
355             my $self = shift;
356             $self->_set_font('gdta_y_axis', @_);
357             }
358              
359             sub set_values_font
360             {
361             my $self = shift;
362             $self->_set_font('gdta_values', @_);
363             }
364              
365             sub set_legend # List of legend keys
366             {
367             my $self = shift;
368             $self->{legend} = [@_];
369             }
370              
371             sub set_legend_font # (font name)
372             {
373             my $self = shift;
374             $self->_set_font('gdta_legend', @_);
375             }
376              
377             sub get_hotspot
378             {
379             my $self = shift;
380             my $ds = shift; # Which data set
381             my $np = shift; # Which data point?
382              
383             if (defined $np && defined $ds)
384             {
385             return @{$self->{_hotspots}->[$ds]->[$np]};
386             }
387             elsif (defined $ds)
388             {
389             return @{$self->{_hotspots}->[$ds]};
390             }
391             else
392             {
393             return @{$self->{_hotspots}};
394             }
395             }
396              
397             sub _set_feature_coords
398             {
399             my $self = shift;
400             my $feature = shift;
401             my $type = shift;
402             $self->{_feat_coords}->{$feature} = [ $type, @_ ];
403             }
404              
405             sub _set_text_feature_coords
406             {
407             my $self = shift;
408             my $feature = shift;
409             $self->_set_feature_coords($feature, "rect", @_[0,1,4,5]);
410             }
411              
412             sub get_feature_coordinates
413             {
414             my $self = shift;
415             my $feature = shift;
416             if ($feature)
417             {
418             $self->{_feat_coords}->{$feature};
419             }
420             else
421             {
422             $self->{_feat_coords};
423             }
424             }
425              
426             # PRIVATE
427              
428             # inherit check_data from GD::Graph
429              
430             #
431             # calculate the bottom of the bounding box for the graph
432             #
433             sub setup_bottom_boundary
434             {
435             my $self = shift;
436             $self->{bottom} = $self->{height} - $self->{b_margin} - 1;
437             if (! $self->{rotate_chart})
438             {
439             # X label
440             $self->{bottom} -= $self->{xlfh} + $self->{text_space}
441             if $self->{xlfh};
442             # X axis tick labels
443             $self->{bottom} -= $self->{x_label_height} + $self->{axis_space}
444             if $self->{xafh};
445             }
446             else
447             {
448             # Y1 label
449             $self->{bottom} -= $self->{ylfh} + $self->{text_space}
450             if $self->{y1_label};
451             # Y1 axis labels
452             $self->{bottom} -= $self->{y_label_height}[1] + $self->{axis_space}
453             if $self->{y_label_height}[1];
454             }
455             }
456             #
457             # Calculate the top of the bounding box for the graph
458             #
459             sub setup_top_boundary
460             {
461             my $self = shift;
462              
463             $self->{top} = $self->{t_margin};
464             # Chart title
465             $self->{top} += $self->{tfh} + $self->{text_space} if $self->{tfh};
466             if (! $self->{rotate_chart})
467             {
468             # Make sure the text for the y axis tick markers fits on the canvas
469             $self->{top} = $self->{yafh}/2 if $self->{top} == 0;
470             }
471             else
472             {
473             if ($self->{two_axes})
474             {
475             # Y2 label
476             $self->{top} += $self->{ylfh} + $self->{text_space}
477             if $self->{y2_label};
478             # Y2 axis labels
479             $self->{top} += $self->{y_label_height}[2] + $self->{axis_space}
480             if $self->{y_label_height}[2];
481             }
482             }
483             }
484             #
485             # calculate the left of the bounding box for the graph
486             #
487             sub setup_left_boundary
488             {
489             my $self = shift;
490              
491             $self->{left} = $self->{l_margin};
492             if (! $self->{rotate_chart})
493             {
494             # Y1 label
495             $self->{left} += $self->{ylfh} + $self->{text_space}
496             if $self->{y1_label};
497             # Y1 axis labels
498             $self->{left} += $self->{y_label_len}[1] + $self->{axis_space}
499             if $self->{y_label_len}[1];
500             }
501             else
502             {
503             # X label
504             $self->{left} += $self->{xlfh} + $self->{text_space}
505             if $self->{x_label};
506             # X axis labels
507             $self->{left} += $self->{x_label_width} + $self->{axis_space}
508             if $self->{x_label_width};
509             }
510             }
511             #
512             # calculate the right of the bounding box for the graph
513             #
514             sub setup_right_boundary
515             {
516             my $self = shift;
517             $self->{right} = $self->{width} - $self->{r_margin} - 1;
518              
519             if (! $self->{rotate_chart})
520             {
521             if ($self->{two_axes})
522             {
523             # Y2 label
524             $self->{right} -= $self->{ylfh} + $self->{text_space}
525             if $self->{y2_label};
526             # Y2 axis label
527             $self->{right} -= $self->{y_label_len}[2] + $self->{axis_space}
528             if $self->{y_label_len}[2];
529             }
530             }
531             else
532             {
533             # Adjust right margin to allow last label of y axes. Only do
534             # this when the right margin doesn't have enough space
535             # already.
536             #
537             # TODO Don't assume rightmost label is the same as the
538             # longest label (stored in y_label_len) The worst that can
539             # happen now is that we reserve too much space.
540            
541             my $max_len = $self->{y_label_len}[1];
542             if ($self->{two_axes})
543             {
544             $max_len = $self->{y_label_len}[2] if
545             $self->{y_label_len}[2] > $max_len;
546             }
547             $max_len = int ($max_len/2);
548            
549             if ($self->{right} + $max_len >= $self->{width} - $self->{r_margin})
550             {
551             $self->{right} -= $max_len;
552             }
553             }
554             }
555              
556             sub _setup_boundaries
557             {
558             my $self = shift;
559              
560             $self->setup_bottom_boundary();
561             $self->setup_top_boundary();
562             $self->setup_left_boundary();
563             $self->setup_right_boundary();
564              
565             if ($self->correct_width && !$self->{x_tick_number})
566             {
567             if (! $self->{rotate_chart})
568             {
569             # Make sure we have a nice integer number of pixels
570             $self->{r_margin} += ($self->{right} - $self->{left}) %
571             ($self->{_data}->num_points + 1);
572            
573             $self->setup_right_boundary();
574             }
575             else
576             {
577             # Make sure we have a nice integer number of pixels
578             $self->{b_margin} += ($self->{bottom} - $self->{top}) %
579             ($self->{_data}->num_points + 1);
580            
581             $self->setup_bottom_boundary();
582             }
583             }
584              
585             return $self->_set_error('Vertical size too small')
586             if $self->{bottom} <= $self->{top};
587             return $self->_set_error('Horizontal size too small')
588             if $self->{right} <= $self->{left};
589              
590             return $self;
591             }
592              
593             # This method should return 1 if the width of the graph needs to be
594             # corrected to whole integers, and 0 if not. The default behaviour is to
595             # not correct the width. Individual classes should override this by
596             # setting the $self->{correct_width} attribute in their initialise
597             # method. Only in complex cases (see mixed.pm) should this method be
598             # overridden
599             sub correct_width { $_[0]->{correct_width} }
600              
601             sub setup_x_step_size_v
602             {
603             my $s = shift;
604              
605             # calculate the step size for x data
606             # CONTRIB Changes by Scott Prahl
607             if (defined $s->{x_tick_number})
608             {
609             my $delta = ($s->{right} - $s->{left})/($s->{x_max} - $s->{x_min});
610             # 'True' numerical X axis addition # From: Gary Deschaines
611             if (defined($s->{x_min_value}) && defined($s->{x_max_value}))
612             {
613             $s->{x_offset} = $s->{left};
614             $s->{x_step} = $delta;
615             }
616             else
617             {
618             $s->{x_offset} =
619             ($s->{true_x_min} - $s->{x_min}) * $delta + $s->{left};
620             $s->{x_step} =
621             ($s->{true_x_max} - $s->{true_x_min}) *
622             $delta/($s->{_data}->num_points - 1);
623             }
624             }
625             else
626             {
627             $s->{x_step} = ($s->{right} - $s->{left})/($s->{_data}->num_points + 1);
628             $s->{x_offset} = $s->{left};
629             }
630             }
631              
632             sub setup_x_step_size_h
633             {
634             my $s = shift;
635              
636             # calculate the step size for x data
637             # CONTRIB Changes by Scott Prahl
638             if (defined $s->{x_tick_number})
639             {
640             my $delta = ($s->{bottom} - $s->{top})/($s->{x_max} - $s->{x_min});
641             # 'True' numerical X axis addition # From: Gary Deschaines
642             if (defined($s->{x_min_value}) && defined($s->{x_max_value}))
643             {
644             $s->{x_offset} = $s->{top};
645             $s->{x_step} = $delta;
646             }
647             else
648             {
649             $s->{x_offset} =
650             ($s->{true_x_min} - $s->{x_min}) * $delta + $s->{top};
651             $s->{x_step} =
652             ($s->{true_x_max} - $s->{true_x_min}) *
653             $delta/($s->{_data}->num_points - 1);
654             }
655             }
656             else
657             {
658             $s->{x_step} = ($s->{bottom} - $s->{top})/($s->{_data}->num_points + 1);
659             $s->{x_offset} = $s->{top};
660             }
661             }
662              
663             sub setup_coords
664             {
665             my $s = shift;
666              
667             # Do some sanity checks
668             $s->{two_axes} = 0 if $s->{_data}->num_sets < 2 || $s->{two_axes} < 0;
669             $s->{two_axes} = 1 if $s->{two_axes} > 1;
670              
671             delete $s->{y_label2} unless $s->{two_axes};
672              
673             # Set some heights for text
674             $s->{tfh} = 0 unless $s->{title};
675             $s->{xlfh} = 0 unless $s->{x_label};
676              
677             # Make sure the y1 axis has a label if there is one set for y in
678             # general
679             $s->{y1_label} = $s->{y_label} if !$s->{y1_label} && $s->{y_label};
680              
681             # Set axis tick text heights and widths to 0 if they don't need to
682             # be plotted.
683             $s->{xafh} = 0, $s->{xafw} = 0 unless $s->{x_plot_values};
684             $s->{yafh} = 0, $s->{yafw} = 0 unless $s->{y_plot_values};
685              
686             # Calculate minima and maxima for the axes
687             $s->set_max_min() or return;
688              
689             # Create the labels for the axes, and calculate the max length
690             $s->create_y_labels();
691             $s->create_x_labels(); # CONTRIB Scott Prahl
692              
693             # Calculate the boundaries of the chart
694             $s->_setup_boundaries() or return;
695              
696             # CONTRIB Scott Prahl
697             # make sure that we can generate valid x tick marks
698             undef($s->{x_tick_number}) if $s->{_data}->num_points < 3;
699             undef($s->{x_tick_number}) if
700             !defined $s->{x_max} ||
701             !defined $s->{x_min} ||
702             $s->{x_max} == $s->{x_min};
703              
704             $s->{rotate_chart} ? $s->setup_x_step_size_h() :
705             $s->setup_x_step_size_v();
706              
707             # get the zero axis level
708             my ($zl, $zb) = $s->val_to_pixel(0, 0, 1);
709             my ($min,$val,$max) = $s->{rotate_chart}
710             ? ( $s->{left}, $zl, $s->{right} )
711             : ( $s->{top}, $zb, $s->{bottom} );
712            
713             $s->{zeropoint} = $min > $val ? $min : $max < $val ? $max : $val;
714              
715             # More sanity checks
716             $s->{x_label_skip} = 1 if $s->{x_label_skip} < 1;
717             $s->{y_label_skip} = 1 if $s->{y_label_skip} < 1;
718             $s->{y_tick_number} = 1 if $s->{y_tick_number} < 1;
719              
720             return $s;
721             }
722              
723             sub create_y_labels
724             {
725             my $self = shift;
726              
727             # XXX This should really be y_label_width
728             $self->{y_label_len}[$_] = 0 for 1, 2;
729             $self->{y_label_height}[$_] = 0 for 1, 2;
730              
731             for my $t (0 .. $self->{y_tick_number})
732             {
733             # XXX Ugh, why did I ever do it this way? How bloody obscure.
734             for my $axis (1 .. ($self->{two_axes} + 1))
735             {
736             my $label = $self->{y_min}[$axis] +
737             $t * ($self->{y_max}[$axis] - $self->{y_min}[$axis]) /
738             $self->{y_tick_number};
739            
740             $self->{y_values}[$axis][$t] = $label;
741              
742             if (my ($fmt) = grep defined, map($self->{"y${_}_number_format"},$axis,'') )
743             {
744             $label = ref $fmt eq 'CODE' ?
745             $fmt->($label) :
746             sprintf($fmt, $label);
747             }
748            
749             $self->{gdta_y_axis}->set_text($label);
750             my $len = $self->{gdta_y_axis}->get('width');
751              
752             $self->{y_labels}[$axis][$t] = $label;
753              
754             # TODO Allow vertical y labels
755             $self->{y_label_len}[$axis] = $len
756             if $len > $self->{y_label_len}[$axis];
757             $self->{y_label_height}[$axis] = $self->{yafh};
758             }
759             }
760             }
761              
762             sub get_x_axis_label_length
763             {
764             my $self = shift;
765              
766             my @values = $self->{x_tick_number} ?
767             @{$self->{x_values}} :
768             $self->{_data}->x_values;
769              
770             my $maxlen = 0;
771             foreach my $label (@values)
772             {
773             $self->{gdta_x_axis}->set_text($label);
774             my $len = $self->{gdta_x_axis}->get('width');
775             $maxlen = $len if $maxlen < $len;
776             }
777              
778             return $maxlen;
779             }
780              
781             # CONTRIB Scott Prahl
782             sub create_x_labels
783             {
784             my $self = shift;
785             my $maxlen = 0;
786              
787             $self->{x_label_height} = 0;
788             $self->{x_label_width} = 0;
789              
790             if (defined $self->{x_tick_number})
791             {
792             # We want to emulate numerical x axes
793             foreach my $t (0..$self->{x_tick_number})
794             {
795             my $label =
796             $self->{x_min} +
797             $t * ($self->{x_max} - $self->{x_min})/$self->{x_tick_number};
798              
799             $self->{x_values}[$t] = $label;
800              
801             if (defined $self->{x_number_format})
802             {
803             $label = ref $self->{x_number_format} eq 'CODE' ?
804             &{$self->{x_number_format}}($label) :
805             sprintf($self->{x_number_format}, $label);
806             }
807              
808             $self->{gdta_x_label}->set_text($label);
809             my $len = $self->{gdta_x_label}->get('width');
810              
811             $self->{x_labels}[$t] = $label;
812             $maxlen = $len
813             if $len > $self->{x_label_height};
814             }
815             }
816             else
817             {
818             $maxlen = $self->get_x_axis_label_length;
819             }
820              
821             $self->{x_label_height} = $self->{x_labels_vertical} ?
822             $maxlen : $self->{xafh};
823             $self->{x_label_width} = $self->{x_labels_vertical} ?
824             $self->{xafh} : $maxlen;
825             }
826              
827             #
828             # The drawing of labels for the axes. This is split up in the four
829             # positions a label can appear in, depending on a few settings. These
830             # settings are all dealt with in the draw_x_labels and draw_y_labels
831             # subroutines, which in turn call the appropriate directional label
832             # drawer
833             #
834             sub draw_left_label
835             {
836             my ($self, $label, $align) = @_;
837              
838             $label->set_align('top', 'left');
839             my $tx = $self->{l_margin};
840             my $ty = $self->{bottom} - $align * ($self->{bottom} - $self->{top}) +
841             $align * $label->get('width');
842             $label->draw($tx, $ty, PI/2);
843             }
844              
845             sub draw_bottom_label
846             {
847             my ($self, $label, $align) = @_;
848              
849             $label->set_align('bottom', 'left');
850             my $tx = $self->{left} + $align * ($self->{right} - $self->{left}) -
851             $align * $label->get('width');
852             my $ty = $self->{height} - $self->{b_margin};
853             $label->draw($tx, $ty, 0);
854             }
855              
856             sub draw_top_label
857             {
858             my ($self, $label, $align) = @_;
859              
860             $label->set_align('top', 'left');
861             my $tx = $self->{left} + $align * ($self->{right} - $self->{left}) -
862             $align * $label->get('width');
863             my $ty = $self->{t_margin};
864             $ty += $self->{tfh} + $self->{text_space} if $self->{tfh};
865             $label->draw($tx, $ty, 0);
866             }
867              
868             sub draw_right_label
869             {
870             my ($self, $label, $align) = @_;
871              
872             $label->set_align('bottom', 'left');
873             my $tx = $self->{width} - $self->{r_margin};
874             my $ty = $self->{bottom} - $align * ($self->{bottom} - $self->{top}) +
875             $align * $label->get('width');
876             $label->draw($tx, $ty, PI/2);
877             }
878              
879             sub draw_x_label
880             {
881             my $self = shift;
882             my ($tx, $ty, $a);
883              
884             my @coords; # coordinates of the label drawn
885              
886             return unless $self->{x_label};
887              
888             $self->{gdta_x_label}->set_text($self->{x_label});
889             if ($self->{rotate_chart})
890             {
891             @coords = $self->draw_left_label($self->{gdta_x_label},
892             $self->{x_label_position});
893             }
894             else
895             {
896             @coords = $self->draw_bottom_label($self->{gdta_x_label},
897             $self->{x_label_position});
898             }
899             $self->_set_text_feature_coords("x_label", @coords);
900             }
901              
902             sub draw_y_labels
903             {
904             my $self = shift;
905              
906             my @coords; # coordinates of the labels drawn
907              
908             if (defined $self->{y1_label})
909             {
910             $self->{gdta_y_label}->set_text($self->{y1_label});
911             if ($self->{rotate_chart})
912             {
913             @coords = $self->draw_bottom_label($self->{gdta_y_label},
914             $self->{y_label_position});
915             }
916             else
917             {
918             @coords = $self->draw_left_label($self->{gdta_y_label},
919             $self->{y_label_position});
920             }
921             $self->_set_text_feature_coords("y1_label", @coords);
922             $self->_set_text_feature_coords("y_label", @coords);
923             }
924             if ( $self->{two_axes} && defined $self->{y2_label} )
925             {
926             $self->{gdta_y_label}->set_text($self->{y2_label});
927             if ($self->{rotate_chart})
928             {
929             @coords = $self->draw_top_label($self->{gdta_y_label},
930             $self->{y_label_position});
931             }
932             else
933             {
934             @coords = $self->draw_right_label($self->{gdta_y_label},
935             $self->{y_label_position});
936             }
937             $self->_set_text_feature_coords("y2_label", @coords);
938             }
939             }
940              
941             sub draw_text
942             {
943             my $self = shift;
944              
945             if ($self->{title})
946             {
947             my $xc = $self->{left} + ($self->{right} - $self->{left})/2;
948             $self->{gdta_title}->set_align('top', 'center');
949             $self->{gdta_title}->set_text($self->{title});
950             my @coords = $self->{gdta_title}->draw($xc, $self->{t_margin});
951             $self->_set_text_feature_coords("title", @coords);
952             }
953              
954             $self->draw_x_label();
955             $self->draw_y_labels();
956             }
957              
958             sub draw_axes
959             {
960             my $self = shift;
961              
962             my ($l, $r, $b, $t) =
963             ( $self->{left}, $self->{right}, $self->{bottom}, $self->{top} );
964            
965             # Sanity check for zero_axis and zero_axis_only
966             unless ($self->{y_min}[1] < 0 && $self->{y_max}[1] > 0)
967             {
968             $self->{zero_axis} = 0;
969             $self->{zero_axis_only} = 0;
970             }
971              
972             if ( $self->{box_axis} )
973             {
974             $self->{graph}->filledRectangle($l+1, $t+1, $r-1, $b-1, $self->{boxci})
975             if $self->{boxci};
976              
977             $self->{graph}->rectangle($l, $t, $r, $b, $self->{fgci});
978             }
979             else
980             {
981             $self->{graph}->line($l, $t, $l, $b, $self->{fgci});
982             $self->{graph}->line($l, $b, $r, $b, $self->{fgci})
983             unless ($self->{zero_axis_only});
984             $self->{graph}->line($r, $b, $r, $t, $self->{fgci})
985             if ($self->{two_axes});
986             }
987              
988             if ($self->{zero_axis} or $self->{zero_axis_only})
989             {
990             my ($x, $y) = $self->val_to_pixel(0, 0, 1);
991             $self->{graph}->line($l, $y, $r, $y, $self->{fgci});
992             }
993              
994             $self->_set_feature_coords("axes", "rect", $l, $b, $r, $t);
995             }
996              
997             #
998             # Ticks and values for y axes
999             #
1000             sub draw_y_ticks_h
1001             {
1002             my $self = shift;
1003              
1004             for my $t (0 .. $self->{y_tick_number})
1005             {
1006             for my $axis (1 .. ($self->{two_axes} + 1))
1007             {
1008             my $value = $self->{y_values}[$axis][$t];
1009             my $label = $self->{y_labels}[$axis][$t];
1010            
1011             my ($x, $y) = $self->val_to_pixel(0, $value, -$axis);
1012             $y = ($axis == 1) ? $self->{bottom} : $self->{top};
1013            
1014             if ($self->{y_long_ticks})
1015             {
1016             $self->{graph}->line(
1017             $x, $self->{bottom},
1018             $x, $self->{top},
1019             $self->{fgci}
1020             ) unless ($axis-1);
1021             }
1022             else
1023             {
1024             $self->{graph}->line(
1025             $x, $y,
1026             $x, $y - $self->{y_tick_length},
1027             $self->{fgci}
1028             );
1029             }
1030              
1031             next
1032             if $t % ($self->{y_label_skip}) || ! $self->{y_plot_values};
1033              
1034             $self->{gdta_y_axis}->set_text($label);
1035             if ($axis == 1)
1036             {
1037             $self->{gdta_y_axis}->set_align('top', 'center');
1038             $y += $self->{axis_space};
1039             }
1040             else
1041             {
1042             $self->{gdta_y_axis}->set_align('bottom', 'center');
1043             $y -= $self->{axis_space};
1044             }
1045             $self->{gdta_y_axis}->draw($x, $y);
1046             }
1047             }
1048              
1049             return $self;
1050             }
1051              
1052             sub draw_y_ticks_v
1053             {
1054             my $self = shift;
1055              
1056             for my $t (0 .. $self->{y_tick_number})
1057             {
1058             # XXX Ugh, why did I ever do it this way? How bloody obscure.
1059             for my $axis (1 .. ($self->{two_axes} + 1))
1060             {
1061             my $value = $self->{y_values}[$axis][$t];
1062             my $label = $self->{y_labels}[$axis][$t];
1063            
1064             my ($x, $y) = $self->val_to_pixel(0, $value, -$axis);
1065             $x = ($axis == 1) ? $self->{left} : $self->{right};
1066              
1067             if ($self->{y_long_ticks})
1068             {
1069             $self->{graph}->line(
1070             $x, $y,
1071             $x + $self->{right} - $self->{left}, $y,
1072             $self->{fgci}
1073             ) unless ($axis-1);
1074             }
1075             else
1076             {
1077             $self->{graph}->line(
1078             $x, $y,
1079             $x + (3 - 2 * $axis) * $self->{y_tick_length}, $y,
1080             $self->{fgci}
1081             );
1082             }
1083              
1084             next
1085             if $t % ($self->{y_label_skip}) || ! $self->{y_plot_values};
1086              
1087             $self->{gdta_y_axis}->set_text($label);
1088             if ($axis == 1)
1089             {
1090             $self->{gdta_y_axis}->set_align('center', 'right');
1091             $x -= $self->{axis_space};
1092             }
1093             else
1094             {
1095             $self->{gdta_y_axis}->set_align('center', 'left');
1096             $x += $self->{axis_space};
1097             }
1098             $self->{gdta_y_axis}->draw($x, $y);
1099             }
1100             }
1101              
1102             return $self;
1103             }
1104              
1105             sub draw_y_ticks
1106             {
1107             #TODO Clean this up!
1108             $_[0]->{rotate_chart} ? goto &draw_y_ticks_h : goto &draw_y_ticks_v;
1109             }
1110              
1111              
1112             #
1113             # Ticks and values for x axes
1114             #
1115             sub draw_x_ticks_h
1116             {
1117             my $self = shift;
1118              
1119             for (my $i = 0; $i < $self->{_data}->num_points; $i++)
1120             {
1121             my ($x, $y) = $self->val_to_pixel($i + 1, 0, 1);
1122              
1123             $x = $self->{left} unless $self->{zero_axis_only};
1124              
1125             # Skip unwanted axis ticks
1126             next unless
1127             $self->{x_all_ticks} or
1128             ($i - $self->{x_tick_offset}) % $self->{x_label_skip} == 0 or
1129             $i == $self->{_data}->num_points - 1 && !$self->{x_last_label_skip};
1130              
1131             # Draw the tick on the X axis
1132             if ($self->{x_ticks})
1133             {
1134             if ($self->{x_long_ticks})
1135             {
1136             $self->{graph}->line($self->{left}, $y, $self->{right}, $y,
1137             $self->{fgci});
1138             }
1139             else
1140             {
1141             $self->{graph}->line( $x, $y, $x + $self->{x_tick_length}, $y,
1142             $self->{fgci});
1143             }
1144             }
1145              
1146             # Skip unwanted axis tick labels.
1147             next unless
1148             ($i - $self->{x_tick_offset}) % $self->{x_label_skip} == 0 or
1149             $i == $self->{_data}->num_points - 1 && !$self->{x_last_label_skip};
1150              
1151             my $text = $self->{_data}->get_x($i);
1152             if (defined $text)
1153             {
1154             $self->{gdta_x_axis}->set_text($text);
1155              
1156             # Draw the tick label
1157             my $angle = 0;
1158             if ($self->{x_labels_vertical})
1159             {
1160             $self->{gdta_x_axis}->set_align('bottom', 'center');
1161             $angle = PI/2;
1162             }
1163             else
1164             {
1165             $self->{gdta_x_axis}->set_align('center', 'right');
1166             }
1167             $self->{gdta_x_axis}->draw($x - $self->{axis_space}, $y, $angle);
1168             }
1169             elsif ($INC{'warnings.pm'} && warnings::enabled('uninitialized') || $^W )
1170             {
1171             carp("Uninitialized label value at index $i");
1172             }
1173             }
1174              
1175             return $self;
1176             }
1177              
1178             sub draw_x_ticks_v
1179             {
1180             my $self = shift;
1181              
1182             for (my $i = 0; $i < $self->{_data}->num_points; $i++)
1183             {
1184             my ($x, $y) = $self->val_to_pixel($i + 1, 0, 1);
1185              
1186             $y = $self->{bottom} unless $self->{zero_axis_only};
1187              
1188             # Skip unwanted axis ticks
1189             next unless
1190             $self->{x_all_ticks} or
1191             ($i - $self->{x_tick_offset}) % $self->{x_label_skip} == 0 or
1192             $i == $self->{_data}->num_points - 1 && !$self->{x_last_label_skip};
1193              
1194             if ($self->{x_ticks})
1195             {
1196             if ($self->{x_long_ticks})
1197             {
1198             $self->{graph}->line($x, $self->{bottom}, $x, $self->{top},
1199             $self->{fgci});
1200             }
1201             else
1202             {
1203             $self->{graph}->line($x, $y, $x, $y - $self->{x_tick_length},
1204             $self->{fgci});
1205             }
1206             }
1207              
1208             # Skip unwanted axis tick labels.
1209             next unless
1210             ($i - $self->{x_tick_offset}) % $self->{x_label_skip} == 0 or
1211             $i == $self->{_data}->num_points - 1 && !$self->{x_last_label_skip};
1212              
1213             my $text = $self->{_data}->get_x($i);
1214             if (defined $text)
1215             {
1216             $self->{gdta_x_axis}->set_text($text);
1217              
1218             my $angle = 0;
1219             if ($self->{x_labels_vertical})
1220             {
1221             $self->{gdta_x_axis}->set_align('center', 'right');
1222             $angle = PI/2;
1223             }
1224             else
1225             {
1226             $self->{gdta_x_axis}->set_align('top', 'center');
1227             }
1228             $self->{gdta_x_axis}->draw($x, $y + $self->{axis_space}, $angle);
1229             }
1230             elsif ($INC{'warnings.pm'} && warnings::enabled('uninitialized') || $^W )
1231             {
1232             carp("Uninitialized label value at index $i");
1233             }
1234             }
1235              
1236             return $self;
1237             }
1238              
1239             sub draw_x_ticks
1240             {
1241             #TODO Clean this up!
1242             $_[0]->{rotate_chart} ? goto &draw_x_ticks_h : goto &draw_x_ticks_v;
1243             }
1244              
1245             # CONTRIB Scott Prahl
1246             # Assume x array contains equally spaced x-values
1247             # and generate an appropriate axis
1248             #
1249             ####
1250             # 'True' numerical X axis addition
1251             # From: Gary Deschaines
1252             #
1253             # These modification to draw_x_ticks_number pass x-tick values to the
1254             # val_to_pixel subroutine instead of x-tick indices when ture numerical
1255             # x-axis mode is detected. Also, x_tick_offset and x_label_skip are
1256             # processed differently when true numerical x-axis mode is detected to
1257             # allow labeled major x-tick marks and un-labeled minor x-tick marks.
1258             #
1259             # For example:
1260             #
1261             # x_tick_number => 14,
1262             # x_ticks => 1,
1263             # x_long_ticks => 1,
1264             # x_tick_length => -4,
1265             # x_min_value => 100,
1266             # x_max_value => 800,
1267             # x_tick_offset => 2,
1268             # x_label_skip => 2,
1269             #
1270             #
1271             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
1272             # | | | | | | | | | | | | |
1273             # 1 -| | | | | | | | | | | | |
1274             # | | | | | | | | | | | | |
1275             # 0 _|_________|____|____|____|____|____|____|____|____|____|____|_________|
1276             # | | | | | | | | | | |
1277             # 200 300 400 500 600 700
1278             sub draw_x_ticks_number
1279             {
1280             my $self = shift;
1281              
1282             for my $i (0 .. $self->{x_tick_number})
1283             {
1284             my ($value, $x, $y);
1285              
1286             if (defined($self->{x_min_value}) && defined($self->{x_max_value}))
1287             {
1288             next if ($i - $self->{x_tick_offset}) < 0;
1289             next if ($i + $self->{x_tick_offset}) > $self->{x_tick_number};
1290             $value = $self->{x_values}[$i];
1291             ($x, $y) = $self->val_to_pixel($value, 0, 1);
1292             }
1293             else
1294             {
1295             $value = ($self->{_data}->num_points - 1)
1296             * ($self->{x_values}[$i] - $self->{true_x_min})
1297             / ($self->{true_x_max} - $self->{true_x_min});
1298             ($x, $y) = $self->val_to_pixel($value + 1, 0, 1);
1299             }
1300              
1301             $y = $self->{bottom} unless $self->{zero_axis_only};
1302              
1303             if ($self->{x_ticks})
1304             {
1305             if ($self->{x_long_ticks})
1306             {
1307             # XXX This mod needs to be done everywhere ticks are
1308             # drawn
1309             if ( $self->{x_tick_length} >= 0 )
1310             {
1311             $self->{graph}->line($x, $self->{bottom},
1312             $x, $self->{top}, $self->{fgci});
1313             }
1314             else
1315             {
1316             $self->{graph}->line(
1317             $x, $self->{bottom} - $self->{x_tick_length},
1318             $x, $self->{top}, $self->{fgci});
1319             }
1320             }
1321             else
1322             {
1323             $self->{graph}->line($x, $y,
1324             $x, $y - $self->{x_tick_length}, $self->{fgci} );
1325             }
1326             }
1327              
1328             # If we have to skip labels, we'll do it here.
1329             # Make sure to always draw the last one.
1330             next if $i % $self->{x_label_skip} and
1331             $i != $self->{_data}->num_points - 1;
1332              
1333             $self->{gdta_x_axis}->set_text($self->{x_labels}[$i]);
1334              
1335             if ($self->{x_labels_vertical})
1336             {
1337             $self->{gdta_x_axis}->set_align('center', 'right');
1338             my $yt = $y + $self->{text_space}/2;
1339             $self->{gdta_x_axis}->draw($x, $yt, PI/2);
1340             }
1341             else
1342             {
1343             $self->{gdta_x_axis}->set_align('top', 'center');
1344             my $yt = $y + $self->{text_space}/2;
1345             $self->{gdta_x_axis}->draw($x, $yt);
1346             }
1347             }
1348              
1349             return $self;
1350             }
1351              
1352             sub draw_ticks
1353             {
1354             my $self = shift;
1355              
1356             $self->draw_y_ticks() or return;
1357              
1358             return $self
1359             unless $self->{x_plot_values};
1360              
1361             if (defined $self->{x_tick_number})
1362             {
1363             $self->draw_x_ticks_number() or return;
1364             }
1365             else
1366             {
1367             $self->draw_x_ticks() or return;
1368             }
1369              
1370             return $self;
1371             }
1372              
1373             sub draw_data
1374             {
1375             my $self = shift;
1376              
1377             # Calculate bar_spacing from bar_width
1378             if ($self->{bar_width})
1379             {
1380             my $chart_width = !$self->{rotate_chart} ?
1381             $self->{right} - $self->{left} :
1382             $self->{bottom} - $self->{top};
1383             my $n_bars = $self->{_data}->num_points;
1384             my $n_sets = $self->{_data}->num_sets;
1385             my $bar_space = $chart_width/($n_bars + 1) /
1386             ($self->{overwrite} ? 1 : $n_sets);
1387             $self->{bar_spacing} = $bar_space - $self->{bar_width};
1388             $self->{bar_spacing} = 0 if $self->{bar_spacing} < 0;
1389             }
1390              
1391             # XXX is this comment still pertinent?
1392             # The drawing of 'cumulated' sets needs to be done in reverse,
1393             # for area and bar charts. This is mainly because of backward
1394             # compatibility
1395              
1396             for (my $dsn = 1; $dsn <= $self->{_data}->num_sets; $dsn++)
1397             {
1398             $self->draw_data_set($dsn) or return;
1399             }
1400              
1401             return $self
1402             }
1403              
1404             #
1405             # Draw the values of the data point with the bars, lines or markers
1406             sub draw_values
1407             {
1408             my $self = shift;
1409            
1410             return $self unless $self->{show_values};
1411            
1412             my $text_angle = $self->{values_vertical} ? PI/2 : 0;
1413             my (@bars,@others);
1414              
1415             if ($self->isa('GD::Graph::mixed') ) {
1416             # 1-indexed, like data-sets themselves
1417             my @types = $self->types;
1418             push @{'bars' eq $types[$_ - 1] ? \@bars : \@others}, $_ for 1 .. @types;
1419             $self->GD::Graph::bars::draw_values(@bars) if @bars;
1420             } else {
1421             @others = 1 .. $self->{_data}->num_sets;
1422             }
1423              
1424             foreach my $dsn ( @others )
1425             {
1426             my @values = $self->{_data}->y_values($dsn) or
1427             return $self->_set_error("Impossible illegal data set: $dsn",
1428             $self->{_data}->error);
1429             my @display = $self->{show_values}->y_values($dsn) or next;
1430              
1431             for (my $i = 0; $i < @values; $i++)
1432             {
1433             next unless defined $display[$i];
1434             my ($xp, $yp);
1435             if (defined($self->{x_min_value}) && defined($self->{x_max_value}))
1436             {
1437             ($xp, $yp) = $self->val_to_pixel(
1438             $self->{_data}->get_x($i), $values[$i], $dsn);
1439             }
1440             else
1441             {
1442             ($xp, $yp) = $self->val_to_pixel($i+1, $values[$i], $dsn);
1443             }
1444             $yp -= $self->{values_space};
1445              
1446             my $value = $display[$i];
1447             if (defined $self->{values_format})
1448             {
1449             $value = ref $self->{values_format} eq 'CODE' ?
1450             &{$self->{values_format}}($value) :
1451             sprintf($self->{values_format}, $value);
1452             }
1453              
1454             $self->{gdta_values}->set_text($value);
1455             $self->{gdta_values}->draw($xp, $yp, $text_angle);
1456             }
1457             }
1458              
1459             return $self
1460             }
1461              
1462             #
1463             # draw_data_set is in sub classes
1464             #
1465             sub draw_data_set
1466             {
1467             # ABSTRACT
1468             my $self = shift;
1469             $self->die_abstract( "sub draw_data missing, ")
1470             }
1471              
1472             #
1473             # This method corrects the minimum and maximum y values for chart
1474             # types that need to always include a zero point.
1475             # This is supposed to be called before the methods that pick
1476             # good-looking values.
1477             #
1478             # Input: current minimum and maximum.
1479             # Output: new minimum and maximum.
1480             #
1481             sub _correct_y_min_max
1482             {
1483             my $self = shift;
1484             my ($min, $max) = @_;
1485              
1486             # Make sure bars and area always have a zero offset
1487             # Only bars and areas need
1488             return ($min, $max)
1489             unless $self->isa("GD::Graph::bars") or $self->isa("GD::Graph::area");
1490              
1491             # If either $min or $max are 0, we can return
1492             return ($min, $max) if $max == 0 or $min == 0;
1493              
1494             # If $min and $max on opposite end of zero axis, no work needed
1495             return ($min, $max) unless $min/$max > 0;
1496              
1497             if ($min > 0)
1498             {
1499             $min = 0;
1500             }
1501             else
1502             {
1503             $max = 0;
1504             }
1505              
1506             return ($min, $max);
1507             }
1508              
1509             #
1510             # Figure out the maximum values for the vertical exes, and calculate
1511             # a more or less sensible number for the tops.
1512             #
1513             sub set_max_min
1514             {
1515             my $self = shift;
1516              
1517             # XXX fix to calculate min and max for each data set
1518             # independently, and store in an array. Then, based on use_axis,
1519             # pick the minimust and maximust for each axis, and use those.
1520              
1521             # First, calculate some decent values
1522             if ( $self->{two_axes} )
1523             {
1524             my $min_range_1 = defined($self->{y1_min_range})
1525             ? $self->{y1_min_range}
1526             : $self->{y_min_range};
1527             my $min_range_2 = defined($self->{y2_min_range})
1528             ? $self->{y2_min_range}
1529             : $self->{y_min_range};
1530              
1531             my(@y_min, @y_max);
1532             for my $nd (1 .. $self->{_data}->num_sets)
1533             {
1534             my $axis = $self->{use_axis}->[$nd - 1];
1535             my($y_min, $y_max) = $self->{_data}->get_min_max_y($nd);
1536             if (!defined $y_min[$axis] || $y_min[$axis] > $y_min)
1537             {
1538             $y_min[$axis] = $y_min;
1539             }
1540             if (!defined $y_max[$axis] || $y_max[$axis] < $y_max)
1541             {
1542             $y_max[$axis] = $y_max;
1543             }
1544             }
1545              
1546             (
1547             $self->{y_min}[1], $self->{y_max}[1],
1548             $self->{y_min}[2], $self->{y_max}[2],
1549             $self->{y_tick_number}
1550             ) = _best_dual_ends(
1551             $self->_correct_y_min_max($y_min[1], $y_max[1]),
1552             $min_range_1,
1553             $self->_correct_y_min_max($y_min[2], $y_max[2]),
1554             $min_range_2,
1555             $self->{y_tick_number}
1556             );
1557             }
1558             else
1559             {
1560             my ($y_min, $y_max);
1561             if ($self->{cumulate})
1562             {
1563             my $data_set = $self->{_data}->copy();
1564             $data_set->cumulate;
1565             ($y_min, $y_max) = $data_set->get_min_max_y($data_set->num_sets);
1566             }
1567             else
1568             {
1569             ($y_min, $y_max) = $self->{_data}->get_min_max_y_all;
1570             }
1571             ($y_min, $y_max) = $self->_correct_y_min_max($y_min, $y_max);
1572             ($self->{y_min}[1], $self->{y_max}[1], $self->{y_tick_number}) =
1573             _best_ends($y_min, $y_max, @$self{'y_tick_number','y_min_range'});
1574             }
1575              
1576             if (defined($self->{x_tick_number}))
1577             {
1578             if (defined($self->{x_min_value}) && defined($self->{x_max_value}))
1579             {
1580             $self->{true_x_min} = $self->{x_min_value};
1581             $self->{true_x_max} = $self->{x_max_value};
1582             }
1583             else
1584             {
1585             ($self->{true_x_min}, $self->{true_x_max}) =
1586             $self->{_data}->get_min_max_x;
1587             }
1588             ($self->{x_min}, $self->{x_max}, $self->{x_tick_number}) =
1589             _best_ends($self->{true_x_min}, $self->{true_x_max},
1590             @$self{'x_tick_number','x_min_range'});
1591             }
1592              
1593             # Overwrite these with any user supplied ones
1594             $self->{y_min}[1] = $self->{y_min_value} if defined $self->{y_min_value};
1595             $self->{y_min}[2] = $self->{y_min_value} if defined $self->{y_min_value};
1596              
1597             $self->{y_max}[1] = $self->{y_max_value} if defined $self->{y_max_value};
1598             $self->{y_max}[2] = $self->{y_max_value} if defined $self->{y_max_value};
1599              
1600             $self->{y_min}[1] = $self->{y1_min_value} if defined $self->{y1_min_value};
1601             $self->{y_max}[1] = $self->{y1_max_value} if defined $self->{y1_max_value};
1602              
1603             $self->{y_min}[2] = $self->{y2_min_value} if defined $self->{y2_min_value};
1604             $self->{y_max}[2] = $self->{y2_max_value} if defined $self->{y2_max_value};
1605              
1606             $self->{x_min} = $self->{x_min_value} if defined $self->{x_min_value};
1607             $self->{x_max} = $self->{x_max_value} if defined $self->{x_max_value};
1608              
1609             if (
1610             $self->{two_axes} && !defined $self->{y1_min_value} && !defined $self->{y2_min_value}
1611             && !defined $self->{y1_max_value} && !defined $self->{y2_max_value}
1612             )
1613             {
1614             # If we have two axes, we need to make sure that the zero is at
1615             # the same spot.
1616             # And we need to change the number of ticks on the axes
1617              
1618             my $l_range = $self->{y_max}[1] - $self->{y_min}[1];
1619             my $r_range = $self->{y_max}[2] - $self->{y_min}[2];
1620              
1621             my $l_top = $self->{y_max}[1]/$l_range;
1622             my $r_top = $self->{y_max}[2]/$r_range;
1623             my $l_bot = $self->{y_min}[1]/$l_range;
1624             my $r_bot = $self->{y_min}[2]/$r_range;
1625              
1626             if ($l_top > $r_top)
1627             {
1628             $self->{y_max}[2] = $l_top * $r_range;
1629             $self->{y_min}[1] = $r_bot * $l_range;
1630             $self->{y_tick_number} *= 1 + abs $r_bot - $l_bot;
1631             }
1632             else
1633             {
1634             $self->{y_max}[1] = $r_top * $l_range;
1635             $self->{y_min}[2] = $l_bot * $r_range;
1636             $self->{y_tick_number} *= 1 + abs $r_top - $l_top;
1637             }
1638             }
1639              
1640             # Check to see if we have sensible values
1641             if ($self->{two_axes})
1642             {
1643             for my $i (1 .. $self->{_data}->num_sets)
1644             {
1645             my ($min, $max) = $self->{_data}->get_min_max_y($i);
1646             return $self->_set_error("Minimum for y" . $i . " too large")
1647             if $self->{y_min}[$self->{use_axis}[$i-1]] > $min;
1648             return $self->_set_error("Maximum for y" . $i . " too small")
1649             if $self->{y_max}[$self->{use_axis}[$i-1]] < $max;
1650             }
1651             }
1652              
1653             return $self;
1654             }
1655              
1656             # CONTRIB Scott Prahl
1657             #
1658             # Calculate best endpoints and number of intervals for an axis and
1659             # returns ($nice_min, $nice_max, $n), where $n is the number of
1660             # intervals and
1661             #
1662             # $nice_min <= $min < $max <= $nice_max
1663             #
1664             # Usage:
1665             # ($nmin,$nmax,$nint) = _best_ends(247, 508);
1666             # ($nmin,$nmax) = _best_ends(247, 508, 5);
1667             # use 5 intervals
1668             # ($nmin,$nmax,$nint) = _best_ends(247, 508, [4..7]);
1669             # best of 4,5,6,7 intervals
1670             # ($nmin,$nmax,$nint) = _best_ends(247, 508, 'auto');
1671             # best of 3,4,5,6 intervals
1672             # ($nmin,$nmax,$nint) = _best_ends(247, 508, [2..5]);
1673             # best of 2,3,4,5 intervals
1674             sub _best_ends
1675             {
1676             my ($min, $max, $n_ref, $min_range) = @_;
1677              
1678             # Adjust for the min range if need be
1679             ($min, $max) = _fit_vals_range($min, $max, $min_range);
1680              
1681             my ($best_min, $best_max, $best_num) = ($min, $max, 1);
1682              
1683             # Check that min and max are not the same, and not 0
1684             ($min, $max) = ($min) ? ($min * 0.5, $min * 1.5) : (-1,1)
1685             if ($max == $min);
1686            
1687             # mgjv - Sometimes, for odd values, and only one data set, this will be
1688             # necessary _after_ the previous step, not before. Data sets of one
1689             # long with negative values were causing infinite loops later on.
1690             ($min, $max) = ($max, $min) if ($min > $max);
1691              
1692             my @n = ref($n_ref) ? @$n_ref : $n_ref;
1693              
1694             if (@n <= 0)
1695             {
1696             @n = (3..6);
1697             }
1698             else
1699             {
1700             @n = map { ref($_) ? @$_ : /(\d+)/i ? $1 : (3..6) } @n;
1701             }
1702              
1703             my $best_fit = 1e30;
1704             my $range = $max - $min;
1705              
1706             # create array of interval sizes
1707             my $s = 1;
1708             while ($s < $range) { $s *= 10 }
1709             while ($s > $range) { $s /= 10 }
1710             my @step = map {$_ * $s} (0.2, 0.5, 1, 2, 5);
1711              
1712             for my $n (@n)
1713             {
1714             # Try all numbers of intervals
1715             next if ($n < 1);
1716              
1717             for my $step (@step)
1718             {
1719             next if ($n != 1) and ($step < $range/$n) || ($step <= 0);
1720             # $step too small
1721              
1722             my ($nice_min, $nice_max, $fit)
1723             = _fit_interval($min, $max, $n, $step);
1724              
1725             next if $best_fit <= $fit;
1726              
1727             $best_min = $nice_min;
1728             $best_max = $nice_max;
1729             $best_fit = $fit;
1730             $best_num = $n;
1731             }
1732             }
1733             return ($best_min, $best_max, $best_num)
1734             }
1735              
1736             # CONTRIB Ben Tilly
1737             #
1738             # Calculate best endpoints and number of intervals for a pair of axes
1739             # where it is trying to line up the scale of the two intervals. It
1740             # returns ($nice_min_1, $nice_max_1, $nice_min_2, $nice_max_2, $n),
1741             # where $n is the number of intervals and
1742             #
1743             # $nice_min_1 <= $min_1 < $max_1 <= $nice_max_1
1744             # $nice_min_2 <= $min_2 < $max_2 <= $nice_max_2
1745             #
1746             # and 0 will appear at the same point on both axes.
1747             #
1748             # Usage:
1749             # ($nmin_1,$nmax_1,$nmin_2,$nmax_2,$nint) = _best_dual_ends(247, 508, undef, -1, 5, undef, [2..5]);
1750             # etc. (The usage of the last arguments just parallels _best_ends.)
1751             #
1752             sub _best_dual_ends
1753             {
1754             my ($min_1, $max_1) = _fit_vals_range(splice @_, 0, 3);
1755             my ($min_2, $max_2) = _fit_vals_range(splice @_, 0, 3);
1756             my @rem_args = @_;
1757              
1758             # Fix the situation where both min_1 and max_1 are 0, which makes it
1759             # loop forever
1760             ($min_1, $max_1) = (0, 1) unless $min_1 or $max_1;
1761              
1762             my $scale_1 = _max(abs($min_1), abs($max_1));
1763             my $scale_2 = _max(abs($min_2), abs($max_2));
1764              
1765             $scale_1 = defined($scale_2) ? $scale_2 : 1 unless defined($scale_1);
1766             $scale_2 = $scale_1 unless defined($scale_2);
1767              
1768             my $ratio = $scale_1 / ($scale_2 || 1);
1769             my $fact_1 = my $fact_2 = 1;
1770              
1771             while ($ratio < sqrt(0.1))
1772             {
1773             $ratio *= 10;
1774             $fact_2 *= 10;
1775             }
1776             while ($ratio > sqrt(10))
1777             {
1778             $ratio /= 10;
1779             $fact_1 *= 10;
1780             }
1781              
1782             my ($best_min_1, $best_max_1, $best_min_2, $best_max_2, $best_n, $best_fit)
1783             = ($min_1, $max_1, $min_2, $max_2, 1, 1e10);
1784              
1785             # Now try all of the ratios of "simple numbers" in the right size-range
1786             foreach my $frac
1787             (
1788             [1,1], [1,2], [1,3], [2,1], [2,3], [2,5],
1789             [3,1], [3,2], [3,4], [3,5], [3,8], [3,10],
1790             [4,3], [4,5], [5,2], [5,3], [5,4], [5,6],
1791             [5,8], [6,5], [8,3], [8,5], [10,3]
1792             )
1793             {
1794             my $bfact_1 = $frac->[0] * $fact_1;
1795             my $bfact_2 = $frac->[1] * $fact_2;
1796              
1797             my $min = _min( $min_1/$bfact_1, $min_2/$bfact_2 );
1798             my $max = _max( $max_1/$bfact_1, $max_2/$bfact_2 );
1799              
1800             my ($bmin, $bmax, $n) = _best_ends($min, $max, @rem_args);
1801             my ($bmin_1, $bmax_1) = ($bfact_1*$bmin, $bfact_1*$bmax);
1802             my ($bmin_2, $bmax_2) = ($bfact_2*$bmin, $bfact_2*$bmax);
1803              
1804             my $fit = _measure_interval_fit($bmin_1, $min_1, $max_1, $bmax_1)
1805             + _measure_interval_fit($bmin_2, $min_2, $max_2, $bmax_2);
1806              
1807             next if $best_fit < $fit;
1808              
1809             (
1810             $best_min_1, $best_max_1, $best_min_2, $best_max_2,
1811             $best_n, $best_fit
1812             ) = (
1813             $bmin_1, $bmax_1, $bmin_2, $bmax_2,
1814             $n, $fit
1815             );
1816             }
1817              
1818             return ($best_min_1, $best_max_1, $best_min_2, $best_max_2, $best_n);
1819             }
1820              
1821             # Takes $min, $max, $step_count, $step_size. Assumes $min <= $max and both
1822             # $step_count and $step_size are positive. Returns the fitted $min, $max,
1823             # and a $fit statistic (where smaller is better). Failure to fit the
1824             # interval results in a poor fit statistic. :-)
1825             sub _fit_interval
1826             {
1827             my ($min, $max, $step_count, $step_size) = @_;
1828              
1829             my $nice_min = $step_size * int($min/$step_size);
1830             $nice_min -= $step_size if ($nice_min > $min);
1831             my $nice_max = ($step_count == 1)
1832             ? $step_size * int($max/$step_size + 1)
1833             : $nice_min + $step_count * $step_size;
1834              
1835             my $fit = _measure_interval_fit($nice_min, $min, $max, $nice_max);
1836              
1837             # Prevent division by zero errors further up
1838             return ($min, $max, 0) if ($step_size == 0);
1839             return ($nice_min, $nice_max, $fit);
1840             }
1841              
1842             # Takes 2 values and a minimum range. Returns a min and max which holds
1843             # both values and is at least that minimum size
1844             sub _fit_vals_range
1845             {
1846             my ($min, $max, $min_range) = @_;
1847              
1848             ($min, $max) = ($max, $min) if $max < $min;
1849              
1850             if (defined($min_range) and $min_range > $max - $min)
1851             {
1852             my $nice_min = $min_range * int($min/$min_range);
1853             $nice_min = $nice_min - $min_range if $min < $nice_min;
1854             my $nice_max = $max < $nice_min + $min_range
1855             ? $nice_min + $min_range
1856             : $max;
1857             ($min, $max) = ($nice_min, $nice_max);
1858             }
1859             return (0+$min, 0+$max);
1860             }
1861              
1862             # Takes $bmin, $min, $max, $bmax and returns a fit statistic for how well
1863             # ($bmin, $bmax) encloses the interval ($min, $max). Smaller is better,
1864             # and failure to fit will be a very bad fit. Assumes that $min <= $max
1865             # and $bmin < $bmax.
1866             sub _measure_interval_fit
1867             {
1868             my ($bmin, $min, $max, $bmax) = @_;
1869             return 1000 if $bmin > $min or $bmax < $max;
1870              
1871             my $range = $max - $min;
1872             my $brange = $bmax - $bmin;
1873              
1874             return $brange < 10 * $range
1875             ? ($brange / $range)
1876             : 10;
1877             }
1878              
1879             sub _get_bottom
1880             {
1881             my $self = shift;
1882             my ($ds, $np) = @_;
1883             my $bottom = $self->{zeropoint};
1884              
1885             if ($self->{cumulate} && $ds > 1)
1886             {
1887             my $left;
1888             my $pvalue = $self->{_data}->get_y_cumulative($ds - 1, $np);
1889             ($left, $bottom) = $self->val_to_pixel($np + 1, $pvalue, $ds);
1890             $bottom = $left if $self->{rotate_chart};
1891             }
1892              
1893             return $bottom;
1894             }
1895              
1896             #
1897             # Convert value coordinates to pixel coordinates on the canvas.
1898             # TODO Clean up all the rotate_chart stuff
1899             #
1900             sub val_to_pixel # ($x, $y, $dataset) or ($x, $y, -$axis) in real coords
1901             { # return [x, y] in pixel coords
1902             my $self = shift;
1903             my ($x, $y, $i) = @_;
1904              
1905             # XXX use_axis
1906             my $axis = 1;
1907             if ( $self->{two_axes} ) {
1908             $axis = $i < 0 ? -$i : $self->{use_axis}[$i - 1];
1909             }
1910            
1911             my $y_min = $self->{y_min}[$axis];
1912             my $y_max = $self->{y_max}[$axis];
1913             my $y_range = ($y_max - $y_min) || 1;
1914             # XXX the above might be an appropriate place for a conditional warning
1915              
1916             my $y_step = $self->{rotate_chart} ?
1917             abs(($self->{right} - $self->{left}) / $y_range) :
1918             abs(($self->{bottom} - $self->{top}) / $y_range);
1919              
1920             my $ret_x;
1921             my $origin = $self->{rotate_chart} ? $self->{top} : $self->{left};
1922              
1923             if (defined($self->{x_min_value}) && defined($self->{x_max_value}))
1924             {
1925             $ret_x = $origin + ($x - $self->{x_min}) * $self->{x_step};
1926             }
1927             else
1928             {
1929             $ret_x = ($self->{x_tick_number} ? $self->{x_offset} : $origin)
1930             + $x * $self->{x_step};
1931             }
1932             my $ret_y = $self->{rotate_chart} ?
1933             $self->{left} + ($y - $y_min) * $y_step :
1934             $self->{bottom} - ($y - $y_min) * $y_step;
1935              
1936             return $self->{rotate_chart} ?
1937             (_round($ret_y), _round($ret_x)) :
1938             (_round($ret_x), _round($ret_y));
1939             }
1940              
1941             #
1942             # Legend
1943             #
1944             sub setup_legend
1945             {
1946             my $self = shift;
1947              
1948             return unless defined $self->{legend};
1949              
1950             my $maxlen = 0;
1951             my $num = 0;
1952              
1953             # Save some variables
1954             $self->{r_margin_abs} = $self->{r_margin};
1955             $self->{b_margin_abs} = $self->{b_margin};
1956              
1957             foreach my $legend (@{$self->{legend}})
1958             {
1959             if (defined($legend) and $legend ne "")
1960             {
1961             $self->{gdta_legend}->set_text($legend);
1962             my $len = $self->{gdta_legend}->get('width');
1963             $maxlen = ($maxlen > $len) ? $maxlen : $len;
1964             $num++;
1965             }
1966             last if $num >= $self->{_data}->num_sets;
1967             }
1968              
1969             $self->{lg_num} = $num or return;
1970             # not actually bug 20792 (unsure that this will ever get hit, but if it does..!)
1971              
1972             # calculate the height and width of each element
1973             my $legend_height = _max($self->{lgfh}, $self->{legend_marker_height});
1974              
1975             $self->{lg_el_width} =
1976             $maxlen + $self->{legend_marker_width} + 3 * $self->{legend_spacing};
1977             $self->{lg_el_height} = $legend_height + 2 * $self->{legend_spacing};
1978              
1979             my ($lg_pos, $lg_align) = split(//, $self->{legend_placement});
1980              
1981             if ($lg_pos eq 'R')
1982             {
1983             # Always work in one column
1984             $self->{lg_cols} = 1;
1985             $self->{lg_rows} = $num;
1986              
1987             # Just for completeness, might use this in later versions
1988             $self->{lg_x_size} = $self->{lg_cols} * $self->{lg_el_width};
1989             $self->{lg_y_size} = $self->{lg_rows} * $self->{lg_el_height};
1990              
1991             # Adjust the right margin for the rest of the graph
1992             $self->{r_margin} += $self->{lg_x_size};
1993              
1994             # Set the x starting point
1995             $self->{lg_xs} = $self->{width} - $self->{r_margin};
1996              
1997             # Set the y starting point, depending on alignment
1998             if ($lg_align eq 'T')
1999             {
2000             $self->{lg_ys} = $self->{t_margin};
2001             }
2002             elsif ($lg_align eq 'B')
2003             {
2004             $self->{lg_ys} = $self->{height} - $self->{b_margin} -
2005             $self->{lg_y_size};
2006             }
2007             else # default 'C'
2008             {
2009             my $height = $self->{height} - $self->{t_margin} -
2010             $self->{b_margin};
2011              
2012             $self->{lg_ys} =
2013             int($self->{t_margin} + $height/2 - $self->{lg_y_size}/2) ;
2014             }
2015             }
2016             else # 'B' is the default
2017             {
2018             # What width can we use
2019             my $width = $self->{width} - $self->{l_margin} - $self->{r_margin};
2020              
2021             (!defined($self->{lg_cols})) and
2022             $self->{lg_cols} = int($width/$self->{lg_el_width}) || 1; # bug 20792
2023            
2024             $self->{lg_cols} = _min($self->{lg_cols}, $num);
2025              
2026             $self->{lg_rows} =
2027             int($num / $self->{lg_cols}) + (($num % $self->{lg_cols}) ? 1 : 0);
2028              
2029             $self->{lg_x_size} = $self->{lg_cols} * $self->{lg_el_width};
2030             $self->{lg_y_size} = $self->{lg_rows} * $self->{lg_el_height};
2031              
2032             # Adjust the bottom margin for the rest of the graph
2033             $self->{b_margin} += $self->{lg_y_size};
2034              
2035             # Set the y starting point
2036             $self->{lg_ys} = $self->{height} - $self->{b_margin};
2037              
2038             # Set the x starting point, depending on alignment
2039             if ($lg_align eq 'R')
2040             {
2041             $self->{lg_xs} = $self->{width} - $self->{r_margin} -
2042             $self->{lg_x_size};
2043             }
2044             elsif ($lg_align eq 'L')
2045             {
2046             $self->{lg_xs} = $self->{l_margin};
2047             }
2048             else # default 'C'
2049             {
2050             $self->{lg_xs} =
2051             int($self->{l_margin} + $width/2 - $self->{lg_x_size}/2);
2052             }
2053             }
2054             }
2055              
2056             sub draw_legend
2057             {
2058             my $self = shift;
2059              
2060             return unless defined $self->{legend};
2061              
2062             my $xl = $self->{lg_xs} + $self->{legend_spacing};
2063             my $y = $self->{lg_ys} + $self->{legend_spacing} - 1;
2064            
2065             my $i = 0;
2066             my $row = 1;
2067             my $x = $xl; # start position of current element
2068              
2069             foreach my $legend (@{$self->{legend}})
2070             {
2071             $i++;
2072             last if $i > $self->{_data}->num_sets;
2073              
2074             my $xe = $x; # position within an element
2075              
2076             next unless defined($legend) && $legend ne "";
2077              
2078             $self->draw_legend_marker($i, $xe, $y);
2079              
2080             $xe += $self->{legend_marker_width} + $self->{legend_spacing};
2081             my $ys = int($y + $self->{lg_el_height}/2 - $self->{lgfh}/2);
2082              
2083             $self->{gdta_legend}->set_text($legend);
2084             $self->{gdta_legend}->draw($xe, $ys);
2085              
2086             $x += $self->{lg_el_width};
2087              
2088             if (++$row > $self->{lg_cols})
2089             {
2090             $row = 1;
2091             $y += $self->{lg_el_height};
2092             $x = $xl;
2093             }
2094             }
2095             }
2096              
2097             #
2098             # This will be virtual; every sub class should define their own
2099             # if this one doesn't suffice
2100             #
2101             sub draw_legend_marker # data_set_number, x, y
2102             {
2103             my $s = shift;
2104             my $n = shift;
2105             my $x = shift;
2106             my $y = shift;
2107              
2108             my $g = $s->{graph};
2109              
2110             my $ci = $s->set_clr($s->pick_data_clr($n));
2111             return unless defined $ci;
2112              
2113             $y += int($s->{lg_el_height}/2 - $s->{legend_marker_height}/2);
2114              
2115             $g->filledRectangle(
2116             $x, $y,
2117             $x + $s->{legend_marker_width}, $y + $s->{legend_marker_height},
2118             $ci
2119             );
2120              
2121             $g->rectangle(
2122             $x, $y,
2123             $x + $s->{legend_marker_width}, $y + $s->{legend_marker_height},
2124             $s->{acci}
2125             );
2126             }
2127              
2128             "Just another true value";