]> git.imager.perl.org - imager-graph.git/blob - Graph.pm
in progress changes:
[imager-graph.git] / Graph.pm
1 package Imager::Graph;
2 require 5.005;
3
4 =head1 NAME
5
6 Imager::Graph - Perl extension for producing Graphs using the Imager library.
7
8 =head1 SYNOPSIS
9
10   use Imager::Graph::Sub_class;
11   my $chart = Imager::Graph::Sub_class->new;
12   my $img = $chart->draw(data=> \@data, ...)
13     or die $chart->error;
14   $img->write(file => 'image.png');
15
16 =head1 DESCRIPTION
17
18 Imager::Graph provides style information to its base classes.  It
19 defines the colors, text display information and fills based on both
20 built-in styles and modifications supplied by the user to the draw()
21 method.
22
23 =over
24
25 =cut
26
27 use strict;
28 use vars qw($VERSION);
29 use Imager qw(:handy);
30 use Imager::Fountain;
31
32 $VERSION = '0.07';
33
34 # the maximum recursion depth in determining a color, fill or number
35 use constant MAX_DEPTH => 10;
36
37 my $NUM_RE = '(?:[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]\d+?)?)';
38
39 =item new
40
41 This is a simple constructor.  No parameters required.
42
43 =cut
44
45 sub new {
46   bless {}, $_[0];
47 }
48
49 =item set_graph_size($size)
50
51 Sets the size of the graph (in pixels) within the image.  The size of the image defaults to 1.5 * $graph_size.
52
53 =cut
54
55 sub set_graph_size {
56   $_[0]->{'custom_style'}->{'size'} = $_[1];
57 }
58
59 =item set_image_width($width)
60
61 Sets the width of the image in pixels.
62
63 =cut
64
65 sub set_image_width {
66   $_[0]->{'custom_style'}->{'width'} = $_[1];
67 }
68
69 =item set_image_height($height)
70
71 Sets the height of the image in pixels.
72
73 =cut
74
75 sub set_image_height {
76   $_[0]->{'custom_style'}->{'height'} = $_[1];
77 }
78
79 =item add_data_series([8, 6, 7, 5, 3, 0, 9], 'Series Name');
80
81 Adds a data series to the graph.  For L<Imager::Graph::Pie>, only one data series can be added.
82
83 =cut
84
85 sub add_data_series {
86   my $self = shift;
87   my $data_ref = shift;
88   my $series_name = shift;
89
90   my $graph_data = $self->{'graph_data'} || [];
91
92   push @$graph_data, { data => $data_ref, series_name => $series_name };
93   if (defined $series_name) {
94     push @{$self->{'labels'}}, $series_name;
95   }
96
97   $self->{'graph_data'} = $graph_data;
98   return;
99 }
100
101 sub _get_data_series {
102   my ($self, $opts) = @_;
103
104   # return the data supplied to draw() if any.
105   if ($opts->{data}) {
106     # one or multiple series?
107     my $data = $opts->{data};
108     if (@$data && ref $data->[0] && ref $data->[0] =~ /ARRAY/) {
109       return $data;
110     }
111     else {
112       return [ { data => $data } ];
113     }
114   }
115
116   return $self->{'graph_data'};
117 }
118
119 =item set_labels(['label1', 'label2' ... ])
120
121 Labels the specific data points.  For line/bar graphs, this is the x-axis.  For pie graphs, it is the label for the wedges.
122
123 =cut
124
125 sub set_labels {
126   $_[0]->{'labels'} = $_[1];
127 }
128
129 sub _get_labels {
130   my ($self, $opts) = @_;
131
132   $opts->{labels}
133     and return $opts->{labels};
134
135   return $_[0]->{'labels'}
136 }
137
138 =item set_title($title)
139
140 Sets the title of the graph.  Requires setting a font.
141
142 =cut
143
144 sub set_title {
145   $_[0]->{'custom_style'}->{'title'}->{'text'} = $_[1];
146 }
147
148 =item set_font($font)
149
150 Sets the font to use for text.  Takes an L<Imager::Font> object.
151
152 =cut
153
154 sub set_font {
155   $_[0]->{'custom_style'}->{'font'} = $_[1];
156 }
157
158 =item set_style($style_name)
159
160 Sets the style to be used for the graph.  Imager::Graph comes with several pre-defined styles: fount_lin (default), fount_rad, mono, primary_red, and primary.
161
162 =cut
163
164 sub set_style {
165   $_[0]->{'style'} = $_[1];
166 }
167
168 sub _get_style {
169   my ($self, $opts) = @_;
170
171   $opts->{style}
172     and return $opts->{style};
173
174   return $self->{'style'};
175 }
176
177 =item error
178
179 Returns an error message.  Only valid if the draw() method returns false.
180
181 =cut
182
183 sub error {
184   $_[0]->{_errstr};
185 }
186
187 =item draw
188
189 Creates a new image, draws the chart onto that image and returns it.
190
191 Optionally, instead of using the api methods to configure your chart,
192 you can supply a C<data> parameter in the format
193 required by that particular graph, and if your graph will use any
194 text, a C<font> parameter
195
196 You can also supply many different parameters which control the way
197 the graph looks.  These are supplied as keyword, value pairs, where
198 the value can be a hashref containing sub values.
199
200 The C<style> parameter will selects a basic color set, and possibly
201 sets other related parameters.  See L</"STYLES">.
202
203  my $font = Imager::Font->new(file => 'ImUgly.ttf');
204  my $img = $chart->draw(
205                  data    => \@data,
206                  font    => $font,
207                  title   => {
208                                  text  => "Hello, World!",
209                                  size  => 36,
210                                  color => 'FF0000'
211                             }
212                  );
213
214 When referring to a single sub-value this documentation will refer to
215 'title.color' rather than 'the color element of title'.
216
217 Returns the graph image on success, or false on failure.
218
219 =back
220
221 =head1 STYLES
222
223 The currently defined styles are:
224
225 =over
226
227 =item primary
228
229 a light grey background with no outlines.  Uses primary colors for the
230 data fills.
231
232 =item primary_red
233
234 a light red background with no outlines.  Uses primary colors for the
235 data fills.
236
237 Graphs drawn using this style should save well as a gif, even though
238 some graphs may perform a slight blur.
239
240 This was the default style, but the red was too loud.
241
242 =item mono
243
244 designed for monochrome output, such as most laser printers, this uses
245 hatched fills for the data, and no colors.  The returned image is a
246 one channel image (which can be overridden with the C<channels>
247 parameter.)
248
249 You can also override the colors used by all components for background
250 or drawing by supplying C<fg> and/or C<bg> parameters.  ie.  if you
251 supply C<<fg=>'FF0000', channels=>3>> then the hash fills and anything
252 else will be drawn in red.  Another use might be to set a transparent
253 background, by supplying C<<bg=>'00000000', channels=>4>>.
254
255 This style outlines the legend if present and outlines the hashed fills.
256
257 =item fount_lin
258
259 designed as a "pretty" style this uses linear fountain fills for the
260 background and data fills, and adds a drop shadow.
261
262 You can override the value used for text and outlines by setting the
263 C<fg> parameter.
264
265 This is the default style.
266
267 =item fount_rad
268
269 also designed as a "pretty" style this uses radial fountain fills for
270 the data and a linear blue to green fill for the background.
271
272 =back
273
274 =head1 Style API
275
276 To set or override styles, you can use the following methods:
277
278 =over 4
279
280 =item set_image_background
281
282 =cut
283
284 sub set_image_background {
285   $_[0]->{'custom_style'}->{'back'} = $_[1];
286 }
287
288 =item set_channels
289
290 =cut
291
292 sub set_channels {
293   $_[0]->{'custom_style'}->{'channels'} = $_[1];
294 }
295
296 =item set_line_color
297
298 =cut
299
300 sub set_line_color {
301   $_[0]->{'custom_style'}->{'line'} = $_[1];
302 }
303
304 =item set_title_font_size
305
306 =cut
307
308 sub set_title_font_size {
309   $_[0]->{'custom_style'}->{'title'}->{'size'} = $_[1];
310 }
311
312 =item set_title_font_color
313
314 =cut
315
316 sub set_title_font_color {
317   $_[0]->{'custom_style'}->{'title'}->{'color'} = $_[1];
318 }
319
320 =item set_title_horizontal_align
321
322 =cut
323
324 sub set_title_horizontal_align {
325   $_[0]->{'custom_style'}->{'title'}->{'halign'} = $_[1];
326 }
327
328 =item set_title_vertical_align
329
330 =cut
331
332 sub set_title_vertical_align {
333   $_[0]->{'custom_style'}->{'title'}->{'valign'} = $_[1];
334 }
335
336 =item set_text_font_color
337
338 =cut
339
340 sub set_text_font_color {
341   $_[0]->{'custom_style'}->{'text'}->{'color'} = $_[1];
342 }
343
344 =item set_text_font_size
345
346 =cut
347
348 sub set_text_font_size {
349   $_[0]->{'custom_style'}->{'text'}->{'size'} = $_[1];
350 }
351
352 =item set_graph_background_color
353
354 =cut
355
356 sub set_graph_background_color {
357   $_[0]->{'custom_style'}->{'bg'} = $_[1];
358 }
359
360 =item set_graph_foreground_color
361
362 =cut
363
364 sub set_graph_foreground_color {
365   $_[0]->{'custom_style'}->{'fg'} = $_[1];
366 }
367
368 =item set_legend_font_color
369
370 =cut
371
372 sub set_legend_font_color {
373   $_[0]->{'custom_style'}->{'legend'}->{'color'} = $_[1];
374 }
375
376 =item set_legend_font
377
378 =cut
379
380 sub set_legend_font {
381   $_[0]->{'custom_style'}->{'legend'}->{'font'} = $_[1];
382 }
383
384 =item set_legend_font_size
385
386 =cut
387
388 sub set_legend_font_size {
389   $_[0]->{'custom_style'}->{'legend'}->{'size'} = $_[1];
390 }
391
392 =item set_legend_patch_size
393
394 =cut
395
396 sub set_legend_patch_size {
397   $_[0]->{'custom_style'}->{'legend'}->{'patchsize'} = $_[1];
398 }
399
400 =item set_legend_patch_gap
401
402 =cut
403
404 sub set_legend_patch_gap {
405   $_[0]->{'custom_style'}->{'legend'}->{'patchgap'} = $_[1];
406 }
407
408 =item set_legend_horizontal_align
409
410 =cut
411
412 sub set_legend_horizontal_align {
413   $_[0]->{'custom_style'}->{'legend'}->{'halign'} = $_[1];
414 }
415
416 =item set_legend_vertical_align
417
418 =cut
419
420 sub set_legend_vertical_align {
421   $_[0]->{'custom_style'}->{'legend'}->{'valign'} = $_[1];
422 }
423
424 =item set_legend_padding
425
426 =cut
427
428 sub set_legend_padding {
429   $_[0]->{'custom_style'}->{'legend'}->{'padding'} = $_[1];
430 }
431
432 =item set_legend_outside_padding
433
434 =cut
435
436 sub set_legend_outside_padding {
437   $_[0]->{'custom_style'}->{'legend'}->{'outsidepadding'} = $_[1];
438 }
439
440 =item set_legend_fill
441
442 =cut
443
444 sub set_legend_fill {
445   $_[0]->{'custom_style'}->{'legend'}->{'fill'} = $_[1];
446 }
447
448 =item set_legend_border
449
450 =cut
451
452 sub set_legend_border {
453   $_[0]->{'custom_style'}->{'legend'}->{'border'} = $_[1];
454 }
455
456 =item set_legend_orientation
457
458 =cut
459
460 sub set_legend_orientation {
461   $_[0]->{'custom_style'}->{'legend'}->{'orientation'} = $_[1];
462 }
463
464 =item set_callout_font_color
465
466 =cut
467
468 sub set_callout_font_color {
469   $_[0]->{'custom_style'}->{'callout'}->{'color'} = $_[1];
470 }
471
472 =item set_callout_font
473
474 =cut
475
476 sub set_callout_font {
477   $_[0]->{'custom_style'}->{'callout'}->{'font'} = $_[1];
478 }
479
480 =item set_callout_font_size
481
482 =cut
483
484 sub set_callout_font_size {
485   $_[0]->{'custom_style'}->{'callout'}->{'size'} = $_[1];
486 }
487
488 =item set_callout_line_color
489
490 =cut
491
492 sub set_callout_line_color {
493   $_[0]->{'custom_style'}->{'callout'}->{'line'} = $_[1];
494 }
495
496 =item set_callout_leader_inside_length
497
498 =cut
499
500 sub set_callout_leader_inside_length {
501   $_[0]->{'custom_style'}->{'callout'}->{'inside'} = $_[1];
502 }
503
504 =item set_callout_leader_outside_length
505
506 =cut
507
508 sub set_callout_leader_outside_length {
509   $_[0]->{'custom_style'}->{'callout'}->{'outside'} = $_[1];
510 }
511
512 =item set_callout_leader_length
513
514 =cut
515
516 sub set_callout_leader_length {
517   $_[0]->{'custom_style'}->{'callout'}->{'leadlen'} = $_[1];
518 }
519
520 =item set_callout_gap
521
522 =cut
523
524 sub set_callout_gap {
525   $_[0]->{'custom_style'}->{'callout'}->{'gap'} = $_[1];
526 }
527
528 =item set_label_font_color
529
530 =cut
531
532 sub set_label_font_color {
533   $_[0]->{'custom_style'}->{'label'}->{'color'} = $_[1];
534 }
535
536 =item set_label_font
537
538 =cut
539
540 sub set_label_font {
541   $_[0]->{'custom_style'}->{'label'}->{'font'} = $_[1];
542 }
543
544 =item set_label_font_size
545
546 =cut
547
548 sub set_label_font_size {
549   $_[0]->{'custom_style'}->{'label'}->{'size'} = $_[1];
550 }
551
552 =item set_drop_shadow_fill_color
553
554 =cut
555
556 sub set_drop_shadow_fill_color {
557   $_[0]->{'custom_style'}->{'dropshadow'}->{'fill'} = $_[1];
558 }
559
560 =item set_drop_shadow_offset
561
562 =cut
563
564 sub set_drop_shadow_offset {
565   $_[0]->{'custom_style'}->{'dropshadow'}->{'off'} = $_[1];
566 }
567
568 =item set_drop_shadowXOffset
569
570 =cut
571
572 sub set_drop_shadowXOffset {
573   $_[0]->{'custom_style'}->{'dropshadow'}->{'offx'} = $_[1];
574 }
575
576 =item set_drop_shadowYOffset
577
578 =cut
579
580 sub set_drop_shadowYOffset {
581   $_[0]->{'custom_style'}->{'dropshadow'}->{'offy'} = $_[1];
582 }
583
584 =item set_drop_shadow_filter
585
586 =cut
587
588 sub set_drop_shadow_filter {
589   $_[0]->{'custom_style'}->{'dropshadow'}->{'filter'} = $_[1];
590 }
591
592 =item set_outline_color
593
594 =cut
595
596 sub set_outline_color {
597   $_[0]->{'custom_style'}->{'outline'}->{'line'} = $_[1];
598 }
599
600 =item set_data_area_fills
601
602 =cut
603
604 sub set_data_area_fills {
605   $_[0]->{'custom_style'}->{'fills'} = $_[1];
606 }
607
608 =item set_data_line_colors
609
610 =cut
611
612 sub set_data_line_colors {
613   $_[0]->{'custom_style'}->{'colors'} = $_[1];
614 }
615
616 =back
617
618 =head1 FEATURES
619
620 Each graph type has a number of features.  These are used to add
621 various items that are displayed in the graph area.  Some common
622 methods are:
623
624 =over
625
626 =item show_legend()
627
628 adds a box containing boxes filled with the data filess, with
629 the labels provided to the draw method.  The legend will only be
630 displayed if both the legend feature is enabled and labels are
631 supplied.
632
633 =cut
634
635 sub show_legend {
636     $_[0]->{'custom_style'}->{'features'}->{'legend'} = 1;
637 }
638
639 =item show_outline()
640
641 draws a border around the data areas.
642
643 =cut
644
645 sub show_outline {
646     $_[0]->{'custom_style'}->{'features'}->{'outline'} = 1;
647 }
648
649 =item show_labels()
650
651 labels each data fill, usually by including text inside the data fill.
652 If the text does not fit in the fill, they could be displayed in some
653 other form, eg. as callouts in a pie graph.  There usually isn't much
654 point in including both labels and a legend.
655
656 =cut
657
658 sub show_labels {
659     $_[0]->{'custom_style'}->{'features'}->{'labels'} = 1;
660 }
661
662 =item show_drop_shadow()
663
664 a simple drop shadow is shown behind some of the graph elements.
665
666 =cut
667
668 sub show_drop_shadow {
669     $_[0]->{'custom_style'}->{'features'}->{'dropshadow'} = 1;
670 }
671
672 =item reset_features()
673
674 Unsets all of the features
675
676 =cut
677
678 sub reset_features {
679     $_[0]->{'custom_style'}->{'features'} = {};
680     $_[0]->{'custom_style'}->{'features'}->{'reset'} = 1;
681 }
682
683 =back
684
685 Additionally, features can be set by passing them into the draw() method:
686
687 =over
688
689 =item legend
690
691 adds a box containing boxes filled with the data filess, with
692 the labels provided to the draw method.  The legend will only be
693 displayed if both the legend feature is enabled and labels are
694 supplied.
695
696 =item labels
697
698 labels each data fill, usually by including text inside the data fill.
699 If the text does not fit in the fill, they could be displayed in some
700 other form, eg. as callouts in a pie graph.  There usually isn't much
701 point in including both labels and a legend.
702
703 =item dropshadow
704
705 a simple drop shadow is shown behind some of the graph elements.
706
707 =back
708
709 Each graph also has features specific to that graph.
710
711 =head1 COMMON PARAMETERS
712
713 When referring to a single sub-value this documentation will refer to
714 'title.color' rather than 'the color element of title'.
715
716 Normally, except for the font parameter, these are controlled by
717 styles, but these are the style parameters I'd mostly likely expect
718 you want to use:
719
720 =over
721
722 =item font
723
724 the Imager font object used to draw text on the chart.
725
726 =item back
727
728 the background fill for the graph.  Default depends on the style.
729
730 =item size
731
732 the base size of the graph image.  Default: 256
733
734 =item width
735
736 the width of the graph image.  Default: 1.5 * size (384)
737
738 =item height
739
740 the height of the graph image.  Default: size (256)
741
742 =item channels
743
744 the number of channels in the image.  Default: 3 (the 'mono' style
745 sets this to 1).
746
747 =item line
748
749 the color used for drawing lines, such as outlines or callouts.
750 Default depends on the current style.  Set to undef to remove the
751 outline from a style.
752
753 =item title
754
755 the text used for a graph title.  Default: no title.  Note: this is
756 the same as the title=>{ text => ... } field.
757
758 =over
759
760 =item halign
761
762 horizontal alignment of the title in the graph, one of 'left',
763 'center' or 'right'. Default: center
764
765 =item valign
766
767 vertical alignment of the title, one of 'top', 'center' or 'right'.
768 Default: top.  It's probably a bad idea to set this to 'center' unless
769 you have a very short title.
770
771 =back
772
773 =item text
774
775 This contains basic defaults used in drawing text.
776
777 =over
778
779 =item color
780
781 the default color used for all text, defaults to the fg color.
782
783 =item size
784
785 the base size used for text, also used to scale many graph elements.
786 Default: 14.
787
788 =back
789
790 =back
791
792 =head1 BEYOND STYLES
793
794 In most cases you will want to use just the styles, but you may want
795 to exert more control over the way your chart looks.  This section
796 describes the options you can use to control the way your chart looks.
797
798 Hopefully you don't need to read this.
799
800 =over
801
802 =item back
803
804 The background of the graph.
805
806 =item bg
807
808 =item fg
809
810 Used to define basic background and foreground colors for the graph.
811 The bg color may be used for the background of the graph, and is used
812 as a default for the background of hatcheed fills.  The fg is used as
813 the default for line and text colors.
814
815 =item font
816
817 The default font used by the graph.  Normally you should supply this
818 if your graph as any text.
819
820 =item line
821
822 The default line color.
823
824 =item text
825
826 defaults for drawing text.  Other textual graph elements will inherit
827 or modify these values.
828
829 =over
830
831 =item color
832
833 default text color, defaults to the I<fg> color.
834
835 =item size
836
837 default text size. Default: 14.  This is used to scale many graph
838 elements, including padding and leader sizes.  Other text elements
839 will either use or scale this value.
840
841 =item font
842
843 default font object.  Inherited from I<font>, which should have been
844 supplied by the caller.
845
846 =back
847
848 =item title
849
850 If you supply a scalar value for this element, it will be stored in
851 the I<text> field.
852
853 Defines the text, font and layout information for the title.
854
855 =over
856
857 =item color
858
859 The color of the title, inherited from I<text.color>.
860
861 =item font
862
863 The font object used for the title, inherited from I<text.font>.
864
865 =item size
866
867 size of the title text. Default: double I<text.size>
868
869 =item halign
870
871 =item valign
872
873 The horizontal and vertical alignment of the title.
874
875 =back
876
877 =item legend
878
879 defines attributes of the graph legend, if present.
880
881 =over
882
883 =item color
884
885 =item font
886
887 =item size
888
889 text attributes for the labels used in the legend.
890
891 =item patchsize
892
893 the width and height of the color patch in the legend.  Defaults to
894 90% of the legend text size.
895
896 =item patchgap
897
898 the minimum gap between patches in pixels.  Defaults to 30% of the
899 patchsize.
900
901 =item patchborder
902
903 the color of the border drawn around each patch.  Inherited from I<line>.
904
905 =item halign
906
907 =item valign
908
909 the horizontal and vertical alignment of the legend within the graph.
910 Defaults to 'right' and 'top'.
911
912 =item padding
913
914 the gap between the legend patches and text and the outside of it's
915 box, or to the legend border, if any.
916
917 =item outsidepadding
918
919 the gap between the border and the outside of the legend's box.  This
920 is only used if the I<legend.border> attribute is defined.
921
922 =item fill
923
924 the background fill for the legend.  Default: none
925
926 =item border
927
928 the border color of the legend.  Default: none (no border is drawn
929 around the legend.)
930
931 =item orientation
932
933 The orientation of the legend.  If this is C<vertical> the the patches
934 and labels are stacked on top of each other.  If this is C<horizontal>
935 the patchs and labels are word wrapped across the image.  Default:
936 vertical.
937
938 =back
939
940 For example to create a horizontal legend with borderless patches,
941 darker than the background, you might do:
942
943   my $im = $chart->draw
944     (...,
945     legend =>
946     {
947       patchborder => undef,
948       orientation => 'horizontal',
949       fill => { solid => Imager::Color->new(0, 0, 0, 32), }
950     },
951     ...);
952
953 =item callout
954
955 defines attributes for graph callouts, if any are present.  eg. if the
956 pie graph cannot fit the label into the pie graph segement it will
957 present it as a callout.
958
959 =over
960
961 =item color
962
963 =item font
964
965 =item size
966
967 the text attributes of the callout label.  Inherited from I<text>.
968
969 =item line
970
971 the color of the callout lines.  Inherited from I<line>
972
973 =item inside
974
975 =item outside
976
977 the length of the leader on the inside and the outside of the fill,
978 usually at some angle.  Both default to the size of the callout text.
979
980 =item leadlen
981
982 the length of the horizontal portion of the leader.  Default:
983 I<callout.size>.
984
985 =item gap
986
987 the gap between the callout leader and the callout text.  Defaults to
988 30% of the text callout size.
989
990 =back
991
992 =item label
993
994 defines attributes for labels drawn into the data areas of a graph.
995
996 =over
997
998 =item color
999
1000 =item font
1001
1002 =item size
1003
1004 The text attributes of the labels.  Inherited from I<text>.
1005
1006 =back
1007
1008 =item dropshadow
1009
1010 the attributes of the graph's drop shadow
1011
1012 =over
1013
1014 =item fill
1015
1016 the fill used for the drop shadow.  Default: '404040' (dark gray)
1017
1018 =item off
1019
1020 the offset of the drop shadow.  A convenience value inherited by offx
1021 and offy.  Default: 40% of I<text.size>.
1022
1023 =item offx
1024
1025 =item offy
1026
1027 the horizontal and vertical offsets of the drop shadow.  Both
1028 inherited from I<dropshadow.off>.
1029
1030 =item filter
1031
1032 the filter description passed to Imager's filter method to blur the
1033 drop shadow.  Default: an 11 element convolution filter.
1034
1035 =back
1036
1037 =item outline
1038
1039 describes the lines drawn around filled data areas, such as the
1040 segments of a pie chart.
1041
1042 =over
1043
1044 =item line
1045
1046 the line color of the outlines, inherited from I<line>.
1047
1048 =back
1049
1050 =item fills
1051
1052 a reference to an array containing fills for each data item.
1053
1054 You can mix fill types, ie. using a simple color for the first item, a
1055 hatched fill for the second and a fountain fill for the next.
1056
1057 =back
1058
1059 =head1 HOW VALUES WORK
1060
1061 Internally rather than specifying literal color, fill, or font objects
1062 or literal sizes for each element, Imager::Graph uses a number of
1063 special values to inherit or modify values taken from other graph
1064 element names.
1065
1066 =head2 Specifying colors
1067
1068 You can specify colors by either supplying an Imager::Color object, by
1069 supplying lookup of another color, or by supplying a single value that
1070 Imager::Color::new can use as an initializer.  The most obvious is
1071 just a 6 or 8 digit hex value representing the red, green, blue and
1072 optionally alpha channels of the image.
1073
1074 You can lookup another color by using the lookup() "function", for
1075 example if you give a color as "lookup(fg)" then Imager::Graph will
1076 look for the fg element in the current style (or as overridden by
1077 you.)  This is used internally by Imager::Graph to set up the
1078 relationships between the colors of various elements, for example the
1079 default style information contains:
1080
1081    text=>{
1082           color=>'lookup(fg)',
1083           ...
1084          },
1085    legend =>{
1086              color=>'lookup(text.color)',
1087              ...
1088             },
1089
1090 So by setting the I<fg> color, you also set the default text color,
1091 since each text element uses lookup(text.color) as its value.
1092
1093 =head2 Specifying fills
1094
1095 Fills can be used for the graph background color, the background color
1096 for the legend block and for the fills used for each data element.
1097
1098 You can specify a fill as a L<color value|Specifying colors> or as a
1099 general fill, see L<Imager::Fill> for details.
1100
1101 You don't need (or usually want) to call Imager::Fill::new yourself,
1102 since the various fill functions will call it for you, and
1103 Imager::Graph provides some hooks to make them more useful.
1104
1105 =over
1106
1107 =item *
1108
1109 with hatched fills, if you don't supply a 'fg' or 'bg' parameter,
1110 Imager::Graph will supply the current graph fg and bg colors.
1111
1112 =item *
1113
1114 with fountain fill, you can supply the xa_ratio, ya_ratio, xb_ratio
1115 and yb_ratio parameters, and they will be scaled in the fill area to
1116 define the fountain fills xa, ya, xb and yb parameters.
1117
1118 =back
1119
1120 As with colors, you can use lookup(name) or lookup(name1.name2) to
1121 have one element to inherit the fill of another.
1122
1123 Imager::Graph defaults the fill combine value to C<'normal'>.  This
1124 doesn't apply to simple color fills.
1125
1126 =head2 Specifying numbers
1127
1128 You can specify various numbers, usually representing the size of
1129 something, commonly text, but sometimes the length of a line or the
1130 size of a gap.
1131
1132 You can use the same lookup mechanism as with colors and fills, but
1133 you can also scale values.  For example, 'scale(0.5,text.size)' will
1134 return half the size of the normal text size.
1135
1136 As with colors, this is used internally to scale graph elements based
1137 on the base text size.  If you change the base text size then other
1138 graph elements will scale as well.
1139
1140 =head2 Specifying other elements
1141
1142 Other elements, such as fonts, or parameters for a filter, can also
1143 use the lookup(name) mechanism.
1144
1145 =head1 INTERNAL METHODS
1146
1147 Only useful if you need to fix bugs, add features or create a new
1148 graph class.
1149
1150 =over
1151
1152 =cut
1153
1154 my %style_defs =
1155   (
1156    back=> 'lookup(bg)',
1157    line=> 'lookup(fg)',
1158    aa => 1,
1159    text=>{
1160           color => 'lookup(fg)',
1161           font  => 'lookup(font)',
1162           size  => 14,
1163           aa    => 'lookup(aa)',
1164          },
1165    title=>{ 
1166            color  => 'lookup(text.color)', 
1167            font   => 'lookup(text.font)',
1168            halign => 'center', 
1169            valign => 'top',
1170            size   => 'scale(text.size,2.0)',
1171            aa     => 'lookup(text.aa)',
1172           },
1173    legend =>{
1174              color          => 'lookup(text.color)',
1175              font           => 'lookup(text.font)',
1176              aa             => 'lookup(text.aa)',
1177              size           => 'lookup(text.size)',
1178              patchsize      => 'scale(legend.size,0.9)',
1179              patchgap       => 'scale(legend.patchsize,0.3)',
1180              patchborder    => 'lookup(line)',
1181              halign         => 'right',
1182              valign         => 'top',
1183              padding        => 'scale(legend.size,0.3)',
1184              outsidepadding => 'scale(legend.padding,0.4)',
1185             },
1186    callout => {
1187                color    => 'lookup(text.color)',
1188                font     => 'lookup(text.font)',
1189                size     => 'lookup(text.size)',
1190                line     => 'lookup(line)',
1191                inside   => 'lookup(callout.size)',
1192                outside  => 'lookup(callout.size)',
1193                leadlen  => 'scale(0.8,callout.size)',
1194                gap      => 'scale(callout.size,0.3)',
1195                aa       => 'lookup(text.aa)',
1196                lineaa   => 'lookup(lineaa)',
1197               },
1198    label => {
1199              font          => 'lookup(text.font)',
1200              size          => 'lookup(text.size)',
1201              color         => 'lookup(text.color)',
1202              hpad          => 'lookup(label.pad)',
1203              vpad          => 'lookup(label.pad)',
1204              pad           => 'scale(label.size,0.2)',
1205              pcformat      => sub { sprintf "%s (%.0f%%)", $_[0], $_[1] },
1206              pconlyformat  => sub { sprintf "%.1f%%", $_[0] },
1207              aa            => 'lookup(text.aa)',
1208              lineaa        => 'lookup(lineaa)',
1209              },
1210    dropshadow => {
1211                   fill    => { solid => Imager::Color->new(0, 0, 0, 96) },
1212                   off     => 'scale(0.4,text.size)',
1213                   offx    => 'lookup(dropshadow.off)',
1214                   offy    => 'lookup(dropshadow.off)',
1215                   filter  => { type=>'conv', 
1216                               # this needs a fairly heavy blur
1217                               coef=>[0.1, 0.2, 0.4, 0.6, 0.7, 0.9, 1.2, 
1218                                      0.9, 0.7, 0.6, 0.4, 0.2, 0.1 ] },
1219                  },
1220    # controls the outline of graph elements representing data, eg. pie
1221    # slices, bars or columns
1222    outline => {
1223                line =>'lookup(line)',
1224                lineaa => 'lookup(lineaa)',
1225               },
1226    # controls the outline and background of the data area of the chart
1227    graph =>
1228    {
1229     fill => "lookup(bg)",
1230     outline => "lookup(fg)",
1231    },
1232    size=>256,
1233    width=>'scale(1.5,size)',
1234    height=>'lookup(size)',
1235
1236    # yes, the handling of fill and line AA is inconsistent, lack of
1237    # forethought, unfortunately
1238    fill => {
1239             aa => 'lookup(aa)',
1240            },
1241    lineaa => 'lookup(aa)',
1242   );
1243
1244 =item _error($message)
1245
1246 Sets the error field of the object and returns an empty list or undef,
1247 depending on context.  Should be used for error handling, since it may
1248 provide some user hooks at some point.
1249
1250 The intended usage is:
1251
1252   some action
1253     or return $self->_error("error description");
1254
1255 You should almost always return the result of _error() or return
1256 immediately afterwards.
1257
1258 =cut
1259
1260 sub _error {
1261   my ($self, $error) = @_;
1262
1263   $self->{_errstr} = $error;
1264
1265   return;
1266 }
1267
1268
1269 =item _style_defs()
1270
1271 Returns the style defaults, such as the relationships between line
1272 color and text color.
1273
1274 Intended to be over-ridden by base classes to provide graph specific
1275 defaults.
1276
1277 =cut
1278
1279 sub _style_defs {
1280   \%style_defs;
1281 }
1282
1283 # Let's make the default something that looks really good, so folks will be interested enough to customize the style.
1284 my $def_style = 'fount_lin';
1285
1286 my %styles =
1287   (
1288    primary =>
1289    {
1290     fills=>
1291     [
1292      qw(FF0000 00FF00 0000FF C0C000 00C0C0 FF00FF)
1293     ],
1294     fg=>'000000',
1295     negative_bg=>'EEEEEE',
1296     bg=>'E0E0E0',
1297     legend=>
1298     {
1299      #patchborder=>'000000'
1300     },
1301    },
1302    primary_red =>
1303    {
1304     fills=>
1305     [
1306      qw(FF0000 00FF00 0000FF C0C000 00C0C0 FF00FF)
1307     ],
1308     fg=>'000000',
1309     negative_bg=>'EEEEEE',
1310     bg=>'C08080',
1311     legend=>
1312     {
1313      patchborder=>'000000'
1314     },
1315    },
1316    mono =>
1317    {
1318     fills=>
1319     [
1320      { hatch=>'slash2' },
1321      { hatch=>'slosh2' },
1322      { hatch=>'vline2' },
1323      { hatch=>'hline2' },
1324      { hatch=>'cross2' },
1325      { hatch=>'grid2' },
1326      { hatch=>'stipple3' },
1327      { hatch=>'stipple2' },
1328     ],
1329     channels=>1,
1330     bg=>'FFFFFF',
1331     fg=>'000000',
1332     negative_bg=>'EEEEEE',
1333     features=>{ outline=>1 },
1334     pie =>{
1335            blur=>undef,
1336           },
1337     aa => 0,
1338    },
1339    fount_lin =>
1340    {
1341     fills=>
1342     [
1343      { fountain=>'linear',
1344        xa_ratio=>0.13, ya_ratio=>0.13, xb_ratio=>0.87, yb_ratio=>0.87,
1345        segments => Imager::Fountain->simple(positions=>[0, 1],
1346                                             colors=>[ NC('FFC0C0'), NC('FF0000') ]),
1347      },
1348      { fountain=>'linear',
1349        xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1350        segments => Imager::Fountain->simple(positions=>[0, 1],
1351                                             colors=>[ NC('C0FFC0'), NC('00FF00') ]),
1352      },
1353      { fountain=>'linear',
1354        xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1355        segments => Imager::Fountain->simple(positions=>[0, 1],
1356                                             colors=>[ NC('C0C0FF'), NC('0000FF') ]),
1357      },
1358      { fountain=>'linear',
1359        xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1360        segments => Imager::Fountain->simple(positions=>[0, 1],
1361                                             colors=>[ NC('FFFFC0'), NC('FFFF00') ]),
1362      },
1363      { fountain=>'linear',
1364        xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1365        segments => Imager::Fountain->simple(positions=>[0, 1],
1366                                             colors=>[ NC('C0FFFF'), NC('00FFFF') ]),
1367      },
1368      { fountain=>'linear',
1369        xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1370        segments => Imager::Fountain->simple(positions=>[0, 1],
1371                                             colors=>[ NC('FFC0FF'), NC('FF00FF') ]),
1372      },
1373     ],
1374     colors  => [
1375      qw(FF0000 00FF00 0000FF C0C000 00C0C0 FF00FF)
1376     ],
1377     line_markers =>[
1378       { shape => 'circle',   radius => 4 },
1379       { shape => 'square',   radius => 4 },
1380       { shape => 'diamond',  radius => 4 },
1381       { shape => 'triangle', radius => 4 },
1382       { shape => 'x',        radius => 4 },
1383       { shape => 'plus',     radius => 4 },
1384     ],
1385     back=>{ fountain=>'linear',
1386             xa_ratio=>0, ya_ratio=>0,
1387             xb_ratio=>1.0, yb_ratio=>1.0,
1388             segments=>Imager::Fountain->simple
1389             ( positions=>[0, 1],
1390               colors=>[ NC('6060FF'), NC('60FF60') ]) },
1391     fg=>'000000',
1392     negative_bg=>'EEEEEE',
1393     bg=>'FFFFFF',
1394     features=>{ dropshadow=>1 },
1395    },
1396    fount_rad =>
1397    {
1398     fills=>
1399     [
1400      { fountain=>'radial',
1401        xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
1402        segments => Imager::Fountain->simple(positions=>[0, 1],
1403                                             colors=>[ NC('FF8080'), NC('FF0000') ]),
1404      },
1405      { fountain=>'radial',
1406        xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
1407        segments => Imager::Fountain->simple(positions=>[0, 1],
1408                                             colors=>[ NC('80FF80'), NC('00FF00') ]),
1409      },
1410      { fountain=>'radial',
1411        xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
1412        segments => Imager::Fountain->simple(positions=>[0, 1],
1413                                             colors=>[ NC('808080FF'), NC('0000FF') ]),
1414      },
1415      { fountain=>'radial',
1416        xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
1417        segments => Imager::Fountain->simple(positions=>[0, 1],
1418                                             colors=>[ NC('FFFF80'), NC('FFFF00') ]),
1419      },
1420      { fountain=>'radial',
1421        xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
1422        segments => Imager::Fountain->simple(positions=>[0, 1],
1423                                             colors=>[ NC('80FFFF'), NC('00FFFF') ]),
1424      },
1425      { fountain=>'radial',
1426        xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
1427        segments => Imager::Fountain->simple(positions=>[0, 1],
1428                                             colors=>[ NC('FF80FF'), NC('FF00FF') ]),
1429      },
1430     ],
1431     colors  => [
1432      qw(FF0000 00FF00 0000FF C0C000 00C0C0 FF00FF)
1433     ],
1434     back=>{ fountain=>'linear',
1435             xa_ratio=>0, ya_ratio=>0,
1436             xb_ratio=>1.0, yb_ratio=>1.0,
1437             segments=>Imager::Fountain->simple
1438             ( positions=>[0, 1],
1439               colors=>[ NC('6060FF'), NC('60FF60') ]) },
1440     fg=>'000000',
1441     negative_bg=>'EEEEEE',
1442     bg=>'FFFFFF',
1443    }
1444   );
1445
1446 $styles{'ocean'} = {
1447     fills  => [
1448              {
1449               fountain =>'linear',
1450               xa_ratio => 0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1451               segments => Imager::Fountain->simple(
1452                                                     positions=>[0, 1],
1453                                                     colors=>[ NC('EFEDCF'), NC('E6E2AF') ]),
1454             },
1455              {
1456               fountain =>'linear',
1457               xa_ratio => 0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1458               segments => Imager::Fountain->simple(
1459                                                     positions=>[0, 1],
1460                                                     colors=>[ NC('DCD7AB'), NC('A7A37E') ]),
1461             },
1462              {
1463               fountain =>'linear',
1464               xa_ratio => 0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1465               segments => Imager::Fountain->simple(
1466                                                     positions=>[0, 1],
1467                                                     colors=>[ NC('B2E5D4'), NC('80B4A2') ]),
1468             },
1469             {
1470               fountain =>'linear',
1471               xa_ratio => 0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1472               segments => Imager::Fountain->simple(
1473                                                     positions=>[0, 1],
1474                                                     colors=>[ NC('7aaab9'), NC('046380') ]),
1475             },
1476             {
1477               fountain =>'linear',
1478               xa_ratio => 0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1479               segments => Imager::Fountain->simple(
1480                                                     positions=>[0, 1],
1481                                                     colors=>[ NC('c3b8e9'), NC('877EA7') ]),
1482             },
1483             {
1484               fountain =>'linear',
1485               xa_ratio => 0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1486               segments => Imager::Fountain->simple(
1487                                                     positions=>[0, 1],
1488                                                     colors=>[ NC('A3DF9A'), NC('67A35E') ]),
1489             },
1490             {
1491               fountain =>'linear',
1492               xa_ratio => 0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
1493               segments => Imager::Fountain->simple(
1494                                                     positions=>[0, 1],
1495                                                     colors=>[ NC('E19C98'), NC('B4726F') ]),
1496             },
1497     ],
1498     colors  => [
1499      qw(E6E2AF A7A37E 80B4A2 046380 877EA7 67A35E B4726F)
1500     ],
1501     fg=>'000000',
1502     negative_bg=>'EEEEEE',
1503     bg=>'FFFFFF',
1504     features=>{ dropshadow=>1 },
1505
1506 };
1507
1508 $styles{'ocean_flat'} = {
1509     fills=>
1510     [
1511      qw(E6E2AF A7A37E 80B4A2 046380 877EA7 67A35E B4726F)
1512     ],
1513     colors  => [
1514      qw(E6E2AF A7A37E 80B4A2 046380 877EA7 67A35E B4726F)
1515     ],
1516     fg=>'000000',
1517     negative_bg=>'EEEEEE',
1518     bg=>'FFFFFF',
1519     features=>{ dropshadow=>1 },
1520
1521 };
1522
1523
1524 =item $self->_style_setup(\%opts)
1525
1526 Uses the values from %opts to build a customized hash describing the
1527 way the graph should be drawn.
1528
1529 =cut
1530
1531 sub _style_setup {
1532   my ($self, $opts) = @_;
1533   my $style_defs = $self->_style_defs;
1534   my $style;
1535
1536   my $pre_def_style = $self->_get_style($opts);
1537   my $api_style = $self->{'custom_style'} || {};
1538   $style = $styles{$pre_def_style} if $pre_def_style;
1539
1540   $style ||= $styles{$def_style};
1541
1542   my @search_list = ( $style_defs, $style, $api_style, $opts);
1543   my %work;
1544
1545   my @composite = $self->_composite();
1546   my %composite;
1547   @composite{@composite} = @composite;
1548
1549   for my $src (@search_list) {
1550     for my $key (keys %$src) {
1551       if ($composite{$key}) {
1552         $work{$key} = {} unless exists $work{$key};
1553         if (ref $src->{$key}) {
1554           # some keys have sub values, especially text
1555           @{$work{$key}}{keys %{$src->{$key}}} = values %{$src->{$key}};
1556         }
1557         else {
1558           # assume it's the text for a title or something
1559           $work{$key}{text} = $src->{$key};
1560         }
1561       }
1562       else {
1563         $work{$key} = $src->{$key}
1564           if defined $src->{$key}; # $opts with pmichauds new accessor handling
1565       }
1566     }
1567   }
1568
1569   # features are handled specially
1570   my %features;
1571   $work{features} = \%features;
1572   for my $src (@search_list) {
1573     if ($src->{features}) {
1574       if (ref $src->{features}) {
1575         if (ref($src->{features}) =~ /ARRAY/) {
1576           # just set those features
1577           for my $feature (@{$src->{features}}) {
1578             if ($feature =~ /^no(.+)$/) {
1579               delete $features{$1};
1580             }
1581             else {
1582               $features{$feature} = 1;
1583             }
1584           }
1585         }
1586         elsif (ref($src->{features}) =~ /HASH/) {
1587           if ($src->{features}{reset}) {
1588             $work{features} = {}; # only the ones the user specifies
1589           }
1590           @{$work{features}}{keys %{$src->{features}}} =
1591             values(%{$src->{features}});
1592         }
1593       }
1594       else {
1595         # just set that single feature
1596         if ($src->{features} =~ /^no(.+)$/) {
1597           delete $features{$1};
1598         }
1599         else {
1600           $features{$src->{features}} = 1;
1601         }
1602       }
1603     }
1604   }
1605
1606   $self->{_style} = \%work;
1607 }
1608
1609 =item $self->_get_thing($name)
1610
1611 Retrieve some general 'thing'.
1612
1613 Supports the 'lookup(foo)' mechanism.
1614
1615 Returns an empty list on failure.
1616
1617 =cut
1618
1619 sub _get_thing {
1620   my ($self, $name, @depth) = @_;
1621
1622   push(@depth, $name);
1623   my $what;
1624   if ($name =~ /^(\w+)\.(\w+)$/) {
1625     $what = $self->{_style}{$1}{$2};
1626   }
1627   else {
1628     $what = $self->{_style}{$name};
1629   }
1630   defined $what or
1631     return;
1632   if (ref $what) {
1633     return $what;
1634   }
1635   elsif ($what =~ /^lookup\((\w+(?:\.\w+)?)\)$/) {
1636     @depth < MAX_DEPTH
1637       or return $self->_error("too many levels of recursion in lookup(@depth)");
1638     return $self->_get_thing($1, @depth);
1639   }
1640   else {
1641     return $what;
1642   }
1643 }
1644
1645 =item $self->_get_number($name)
1646
1647 Retrieves a number from the style.  The value in the style can be the
1648 number, or one of two functions:
1649
1650 =over
1651
1652 =item lookup(newname)
1653
1654 Recursively looks up I<newname> in the style.
1655
1656 =item scale(value1,value2)
1657
1658 Each value can be a number or a name.  Names are recursively looked up
1659 in the style and the product is returned.
1660
1661 =back
1662
1663 =cut
1664
1665 sub _get_number {
1666   my ($self, $name, @depth) = @_;
1667
1668   push(@depth, $name);
1669   my $what;
1670   if ($name =~ /^(\w+)\.(\w+)$/) {
1671     $what = $self->{_style}{$1}{$2};
1672   }
1673   else {
1674     $what = $self->{_style}{$name};
1675   }
1676   defined $what or
1677     return $self->_error("$name is undef (@depth)");
1678
1679   if (ref $what) {
1680     if ($what =~ /CODE/) {
1681       $what = $what->($self, $name);
1682     }
1683   }
1684   else {
1685     if ($what =~ /^lookup\(([\w.]+)\)$/) {
1686       @depth < MAX_DEPTH
1687         or return $self->_error("too many levels of recursion in lookup (@depth)");
1688       return $self->_get_number($1, @depth);
1689     }
1690     elsif ($what =~ /^scale\(
1691                     ((?:[a-z][\w.]*)|$NUM_RE)
1692                     ,
1693                     ((?:[a-z][\w.]*)|$NUM_RE)\)$/x) {
1694       my ($left, $right) = ($1, $2);
1695       unless ($left =~ /^$NUM_RE$/) {
1696         @depth < MAX_DEPTH 
1697           or return $self->_error("too many levels of recursion in scale (@depth)");
1698         $left = $self->_get_number($left, @depth);
1699       }
1700       unless ($right =~ /^$NUM_RE$/) {
1701         @depth < MAX_DEPTH 
1702           or return $self->_error("too many levels of recursion in scale (@depth)");
1703         $right = $self->_get_number($right, @depth);
1704       }
1705       return $left * $right;
1706     }
1707     else {
1708       return $what+0;
1709     }
1710   }
1711 }
1712
1713 =item $self->_get_integer($name)
1714
1715 Retrieves an integer from the style.  This is a simple wrapper around
1716 _get_number() that rounds the result to an integer.
1717
1718 Returns an empty list on failure.
1719
1720 =cut
1721
1722 sub _get_integer {
1723   my ($self, $name, @depth) = @_;
1724
1725   my $number = $self->_get_number($name, @depth)
1726     or return;
1727
1728   return sprintf("%.0f", $number);
1729 }
1730
1731 =item _get_color($name)
1732
1733 Returns a color object of the given name from the style hash.
1734
1735 Uses Imager::Color->new to translate normal scalars into color objects.
1736
1737 Allows the lookup(name) mechanism.
1738
1739 Returns an empty list on failure.
1740
1741 =cut
1742
1743 sub _get_color {
1744   my ($self, $name, @depth) = @_;
1745
1746   push(@depth, $name);
1747   my $what;
1748   if ($name =~ /^(\w+)\.(\w+)$/) {
1749     $what = $self->{_style}{$1}{$2};
1750   }
1751   else {
1752     $what = $self->{_style}{$name};
1753   }
1754
1755   defined($what)
1756     or return $self->_error("$name was undefined (@depth)");
1757
1758   unless (ref $what) {
1759     if ($what =~ /^lookup\((\w+(?:\.\w+)?)\)$/) {
1760       @depth < MAX_DEPTH or
1761         return $self->_error("too many levels of recursion in lookup (@depth)");
1762
1763       return $self->_get_color($1, @depth);
1764     }
1765     $what = Imager::Color->new($what);
1766   }
1767
1768   $what;
1769 }
1770
1771 =item _translate_fill($what, $box)
1772
1773 Given the value of a fill, either attempts to convert it into a fill
1774 list (one of C<<color=>$color_value, filled=>1>> or C<<fill=>{ fill
1775 parameters }>>), or to lookup another fill that is referred to with
1776 the 'lookup(name)' mechanism.
1777
1778 This function does the fg and bg initialization for hatched fills, and
1779 translation of *_ratio for fountain fills (using the $box parameter).
1780
1781 Returns an empty list on failure.
1782
1783 =cut
1784
1785 sub _translate_fill {
1786   my ($self, $what, $box, @depth) = @_;
1787
1788   if (ref $what) {
1789     if (UNIVERSAL::isa($what, "Imager::Color")) {
1790       return ( color=>Imager::Color->new($what), filled=>1 );
1791     }
1792     else {
1793       # a general fill
1794       # default to normal combine mode
1795       my %work = ( combine => 'normal', %$what );
1796       if ($what->{hatch}) {
1797         if (!$work{fg}) {
1798           $work{fg} = $self->_get_color('fg')
1799             or return;
1800         }
1801         if (!$work{bg}) {
1802           $work{bg} = $self->_get_color('bg')
1803             or return;
1804         }
1805         return ( fill=>\%work );
1806       }
1807       elsif ($what->{fountain}) {
1808         for my $key (qw(xa ya xb yb)) {
1809           if (exists $work{"${key}_ratio"}) {
1810             if ($key =~ /^x/) {
1811               $work{$key} = $box->[0] + $work{"${key}_ratio"} 
1812                 * ($box->[2] - $box->[0]);
1813             }
1814             else {
1815               $work{$key} = $box->[1] + $work{"${key}_ratio"} 
1816                 * ($box->[3] - $box->[1]);
1817             }
1818           }
1819         }
1820         return ( fill=>\%work );
1821       }
1822       else {
1823         return ( fill=> \%work );
1824       }
1825     }
1826   }
1827   else {
1828     if ($what =~ /^lookup\((\w+(?:\.\w+)?)\)$/) {
1829       return $self->_get_fill($1, $box, @depth);
1830     }
1831     else {
1832       # assumed to be an Imager::Color single value
1833       return ( color=>Imager::Color->new($what), filled=>1 );
1834     }
1835   }
1836 }
1837
1838 =item _data_fill($index, $box)
1839
1840 Retrieves the fill parameters for a data area fill.
1841
1842 =cut
1843
1844 sub _data_fill {
1845   my ($self, $index, $box) = @_;
1846
1847   my $fills = $self->{_style}{fills};
1848   return $self->_translate_fill($fills->[$index % @$fills], $box,
1849                                 "data.$index");
1850 }
1851
1852 sub _data_color {
1853   my ($self, $index) = @_;
1854
1855   my $colors = $self->{'_style'}{'colors'} || [];
1856   my $fills  = $self->{'_style'}{'fills'} || [];
1857
1858   # Try to just use a fill, so non-fountain styles don't need
1859   # to have a duplicated set of fills and colors
1860   my $fill = $fills->[$index % @$fills];
1861   if (!ref $fill) {
1862     return $fill;
1863   }
1864
1865   if (@$colors) {
1866     return $colors->[$index % @$colors] || '000000';
1867   }
1868   return '000000';
1869 }
1870
1871 =item _get_fill($name, $box)
1872
1873 Retrieves fill parameters for a named fill.
1874
1875 =cut
1876
1877 sub _get_fill {
1878   my ($self, $name, $box, @depth) = @_;
1879
1880   push(@depth, $name);
1881   my $what;
1882   if ($name =~ /^(\w+)\.(\w+)$/) {
1883     $what = $self->{_style}{$1}{$2};
1884   }
1885   else {
1886     $what = $self->{_style}{$name};
1887   }
1888
1889   defined($what)
1890     or return $self->_error("no fill $name found");
1891
1892   return $self->_translate_fill($what, $box, @depth);
1893 }
1894
1895 =item _get_line($name)
1896
1897 Return color (and possibly other) parameters for drawing a line with
1898 the _line() method.
1899
1900 =cut
1901
1902 sub _get_line {
1903   my ($self, $name, @depth) = @_;
1904
1905   push (@depth, $name);
1906   my $what;
1907   if ($name =~ /^(\w+)\.(\w+)$/) {
1908     $what = $self->{_style}{$1}{$2};
1909   }
1910   else {
1911     $what = $self->{_style}{$name};
1912   }
1913
1914   defined($what)
1915     or return $self->_error("no line style $name found");
1916
1917   if (ref $what) {
1918     if (eval { $what->isa("Imager::Color") }) {
1919       return $what;
1920     }
1921     if (ref $what eq "HASH") {
1922       # allow each kep to be looked up
1923       my %work = %$what;
1924
1925       if ($work{color} =~ /^lookup\((.*)\)$/) {
1926         $work{color} = $self->_get_color($1, @depth);
1927       }
1928       for my $key (keys %work) {
1929         $key eq "color" and next;
1930
1931         if ($work{$key} =~ /^lookup\((.*)\)$/) {
1932           $work{$key} = $self->_get_thing($1);
1933         }
1934       }
1935
1936       return %work;
1937     }
1938     return ( color => Imager::Color->new(@$what) );
1939   }
1940   else {
1941     if ($what =~ /^lookup\((\w+(?:\.\w+)?)\)$/) {
1942       @depth < MAX_DEPTH
1943         or return $self->_error("too many levels of recursion in lookup (@depth)");
1944       return $self->_get_line($1, @depth);
1945     }
1946     else {
1947       # presumably a text color
1948       my $color = Imager::Color->new($what)
1949         or return $self->_error("Could not translate $what as a color: ".Imager->errstr);
1950
1951       return ( color => $color );
1952     }
1953   }
1954 }
1955
1956 =item _make_img()
1957
1958 Builds the image object for the graph and fills it with the background
1959 fill.
1960
1961 =cut
1962
1963 sub _make_img {
1964   my ($self) = @_;
1965
1966   my $width = $self->_get_number('width') || 256;
1967   my $height = $self->_get_number('height') || 256;
1968   my $channels = $self->{_style}{channels};
1969
1970   $channels ||= 3;
1971
1972   my $img = Imager->new(xsize=>$width, ysize=>$height, channels=>$channels)
1973     or return $self->_error("Error creating image: " . Imager->errstr);
1974
1975   $img->box($self->_get_fill('back', [ 0, 0, $width-1, $height-1]));
1976
1977   $self->{_image} = $img;
1978
1979   return $img;
1980 }
1981
1982 sub _get_image {
1983   my $self = shift;
1984
1985   return $self->{'_image'};
1986 }
1987
1988 =item _text_style($name)
1989
1990 Returns parameters suitable for calls to Imager::Font's bounding_box()
1991 and draw() methods intended for use in defining text styles.
1992
1993 Returns an empty list on failure.
1994
1995 Returns the following attributes: font, color, size, aa, sizew
1996 (optionally)
1997
1998 =cut
1999
2000 sub _text_style {
2001   my ($self, $name) = @_;
2002
2003   my %work;
2004
2005   if ($self->{_style}{$name}) {
2006     %work = %{$self->{_style}{$name}};
2007   }
2008   else {
2009     %work = %{$self->{_style}{text}};
2010   }
2011   $work{font}
2012     or return $self->_error("$name has no font parameter");
2013
2014   $work{font} = $self->_get_thing("$name.font")
2015     or return $self->_error("No $name.font defined, either set $name.font or font to a font");
2016   UNIVERSAL::isa($work{font}, "Imager::Font")
2017       or return $self->_error("$name.font is not a font");
2018   if ($work{color} && !ref $work{color}) {
2019     $work{color} = $self->_get_color("$name.color")
2020       or return;
2021   }
2022   $work{size} = $self->_get_number("$name.size");
2023   $work{sizew} = $self->_get_number("$name.sizew")
2024     if $work{sizew};
2025   $work{aa} = $self->_get_number("$name.aa");
2026
2027   %work;
2028 }
2029
2030 =item _text_bbox($text, $name)
2031
2032 Returns a bounding box for the specified $text as styled by $name.
2033
2034 Returns an empty list on failure.
2035
2036 =cut
2037
2038 sub _text_bbox {
2039   my ($self, $text, $name) = @_;
2040
2041   my %text_info = $self->_text_style($name)
2042     or return;
2043
2044   my @bbox = $text_info{font}->bounding_box(%text_info, string=>$text,
2045                                             canon=>1);
2046
2047   return @bbox[0..3];
2048 }
2049
2050 =item _line_style($name)
2051
2052 Return parameters suitable for calls to Imager's line(), polyline(),
2053 and box() methods.
2054
2055 For now this returns only color and aa parameters, but future releases
2056 of Imager may support extra parameters.
2057
2058 =cut
2059
2060 sub _line_style {
2061   my ($self, $name) = @_;
2062
2063   my %line;
2064   $line{color} = $self->_get_color("$name.line")
2065     or return;
2066   $line{aa} = $self->_get_number("$name.lineaa");
2067   defined $line{aa} or $line{aa} = $self->_get_number("aa");
2068
2069   return %line;
2070 }
2071
2072 sub _align_box {
2073   my ($self, $box, $chart_box, $name) = @_;
2074
2075   my $halign = $self->{_style}{$name}{halign}
2076     or $self->_error("no halign for $name");
2077   my $valign = $self->{_style}{$name}{valign};
2078
2079   if ($halign eq 'right') {
2080     $box->[0] += $chart_box->[2] - $box->[2];
2081   }
2082   elsif ($halign eq 'left') {
2083     $box->[0] = $chart_box->[0];
2084   }
2085   elsif ($halign eq 'center' || $halign eq 'centre') {
2086     $box->[0] = ($chart_box->[0] + $chart_box->[2] - $box->[2])/2;
2087   }
2088   else {
2089     return $self->_error("invalid halign $halign for $name");
2090   }
2091
2092   if ($valign eq 'top') {
2093     $box->[1] = $chart_box->[1];
2094   }
2095   elsif ($valign eq 'bottom') {
2096     $box->[1] = $chart_box->[3] - $box->[3];
2097   }
2098   elsif ($valign eq 'center' || $valign eq 'centre') {
2099     $box->[1] = ($chart_box->[1] + $chart_box->[3] - $box->[3])/2;
2100   }
2101   else {
2102     return $self->_error("invalid valign $valign for $name");
2103   }
2104   $box->[2] += $box->[0];
2105   $box->[3] += $box->[1];
2106 }
2107
2108 sub _remove_box {
2109   my ($self, $chart_box, $object_box) = @_;
2110
2111   my $areax;
2112   my $areay;
2113   if ($object_box->[0] - $chart_box->[0] 
2114       < $chart_box->[2] - $object_box->[2]) {
2115     $areax = ($object_box->[2] - $chart_box->[0]) 
2116       * ($chart_box->[3] - $chart_box->[1]);
2117   }
2118   else {
2119     $areax = ($chart_box->[2] - $object_box->[0]) 
2120       * ($chart_box->[3] - $chart_box->[1]);
2121   }
2122
2123   if ($object_box->[1] - $chart_box->[1] 
2124       < $chart_box->[3] - $object_box->[3]) {
2125     $areay = ($object_box->[3] - $chart_box->[1]) 
2126       * ($chart_box->[2] - $chart_box->[0]);
2127   }
2128   else {
2129     $areay = ($chart_box->[3] - $object_box->[1]) 
2130       * ($chart_box->[2] - $chart_box->[0]);
2131   }
2132
2133   if ($areay < $areax) {
2134     if ($object_box->[1] - $chart_box->[1] 
2135         < $chart_box->[3] - $object_box->[3]) {
2136       $chart_box->[1] = $object_box->[3];
2137     }
2138     else {
2139       $chart_box->[3] = $object_box->[1];
2140     }
2141   }
2142   else {
2143     if ($object_box->[0] - $chart_box->[0] 
2144         < $chart_box->[2] - $object_box->[2]) {
2145       $chart_box->[0] = $object_box->[2];
2146     }
2147     else {
2148       $chart_box->[2] = $object_box->[0];
2149     }
2150   }
2151 }
2152
2153 sub _draw_legend {
2154   my ($self, $img, $labels, $chart_box) = @_;
2155
2156   my $orient = $self->_get_thing('legend.orientation');
2157   defined $orient or $orient = 'vertical';
2158
2159   if ($orient eq 'vertical') {
2160     return $self->_draw_legend_vertical($img, $labels, $chart_box);
2161   }
2162   elsif ($orient eq 'horizontal') {
2163     return $self->_draw_legend_horizontal($img, $labels, $chart_box);
2164   }
2165   else {
2166     return $self->_error("Unknown legend.orientation $orient");
2167   }
2168 }
2169
2170 sub _draw_legend_horizontal {
2171   my ($self, $img, $labels, $chart_box) = @_;
2172
2173   defined(my $padding = $self->_get_integer('legend.padding'))
2174     or return;
2175   my $patchsize = $self->_get_integer('legend.patchsize')
2176     or return;
2177   defined(my $gap = $self->_get_integer('legend.patchgap'))
2178     or return;
2179
2180   my $minrowsize = $patchsize + $gap;
2181   my ($width, $height) = (0,0);
2182   my $row_height = $minrowsize;
2183   my $pos = 0;
2184   my @sizes;
2185   my @offsets;
2186   for my $label (@$labels) {
2187     my @text_box = $self->_text_bbox($label, 'legend')
2188       or return;
2189     push(@sizes, \@text_box);
2190     my $entry_width = $patchsize + $gap + $text_box[2];
2191     if ($pos == 0) {
2192       # never re-wrap the first entry
2193       push @offsets, [ 0, $height ];
2194     }
2195     else {
2196       if ($pos + $gap + $entry_width > $chart_box->[2]) {
2197         $pos = 0;
2198         $height += $row_height;
2199       }
2200       push @offsets, [ $pos, $height ];
2201     }
2202     my $entry_right = $pos + $entry_width;
2203     $pos += $gap + $entry_width;
2204     $entry_right > $width and $width = $entry_right;
2205     if ($text_box[3] > $row_height) {
2206       $row_height = $text_box[3];
2207     }
2208   }
2209   $height += $row_height;
2210   my @box = ( 0, 0, $width + $padding * 2, $height + $padding * 2 );
2211   my $outsidepadding = 0;
2212   if ($self->{_style}{legend}{border}) {
2213     defined($outsidepadding = $self->_get_integer('legend.outsidepadding'))
2214       or return;
2215     $box[2] += 2 * $outsidepadding;
2216     $box[3] += 2 * $outsidepadding;
2217   }
2218   $self->_align_box(\@box, $chart_box, 'legend')
2219     or return;
2220   if ($self->{_style}{legend}{fill}) {
2221     $img->box(xmin=>$box[0]+$outsidepadding, 
2222               ymin=>$box[1]+$outsidepadding, 
2223               xmax=>$box[2]-$outsidepadding, 
2224               ymax=>$box[3]-$outsidepadding,
2225              $self->_get_fill('legend.fill', \@box));
2226   }
2227   $box[0] += $outsidepadding;
2228   $box[1] += $outsidepadding;
2229   $box[2] -= $outsidepadding;
2230   $box[3] -= $outsidepadding;
2231   my %text_info = $self->_text_style('legend')
2232     or return;
2233   my $patchborder;
2234   if ($self->{_style}{legend}{patchborder}) {
2235     $patchborder = $self->_get_color('legend.patchborder')
2236       or return;
2237   }
2238   
2239   my $dataindex = 0;
2240   for my $label (@$labels) {
2241     my ($left, $top) = @{$offsets[$dataindex]};
2242     $left += $box[0] + $padding;
2243     $top += $box[1] + $padding;
2244     my $textpos = $left + $patchsize + $gap;
2245     my @patchbox = ( $left, $top,
2246                      $left + $patchsize, $top + $patchsize );
2247     my @fill = $self->_data_fill($dataindex, \@patchbox)
2248       or return;
2249     $img->box(xmin=>$left, ymin=>$top, xmax=>$left + $patchsize,
2250                ymax=>$top + $patchsize, @fill);
2251     if ($self->{_style}{legend}{patchborder}) {
2252       $img->box(xmin=>$left, ymin=>$top, xmax=>$left + $patchsize,
2253                 ymax=>$top + $patchsize,
2254                 color=>$patchborder);
2255     }
2256     $img->string(%text_info, x=>$textpos, 'y'=>$top + $patchsize, 
2257                  text=>$label);
2258
2259     ++$dataindex;
2260   }
2261   if ($self->{_style}{legend}{border}) {
2262     my $border_color = $self->_get_color('legend.border')
2263       or return;
2264     $img->box(xmin=>$box[0], ymin=>$box[1], xmax=>$box[2], ymax=>$box[3],
2265               color=>$border_color);
2266   }
2267   $self->_remove_box($chart_box, \@box);
2268   1;
2269 }
2270
2271 sub _draw_legend_vertical {
2272   my ($self, $img, $labels, $chart_box) = @_;
2273
2274   defined(my $padding = $self->_get_integer('legend.padding'))
2275     or return;
2276   my $patchsize = $self->_get_integer('legend.patchsize')
2277     or return;
2278   defined(my $gap = $self->_get_integer('legend.patchgap'))
2279     or return;
2280   my $minrowsize = $patchsize + $gap;
2281   my ($width, $height) = (0,0);
2282   my @sizes;
2283   for my $label (@$labels) {
2284     my @box = $self->_text_bbox($label, 'legend')
2285       or return;
2286     push(@sizes, \@box);
2287     $width = $box[2] if $box[2] > $width;
2288     if ($minrowsize > $box[3]) {
2289       $height += $minrowsize;
2290     }
2291     else {
2292       $height += $box[3];
2293     }
2294   }
2295   my @box = (0, 0, 
2296              $width + $patchsize + $padding * 2 + $gap,
2297              $height + $padding * 2 - $gap);
2298   my $outsidepadding = 0;
2299   if ($self->{_style}{legend}{border}) {
2300     defined($outsidepadding = $self->_get_integer('legend.outsidepadding'))
2301       or return;
2302     $box[2] += 2 * $outsidepadding;
2303     $box[3] += 2 * $outsidepadding;
2304   }
2305   $self->_align_box(\@box, $chart_box, 'legend')
2306     or return;
2307   if ($self->{_style}{legend}{fill}) {
2308     $img->box(xmin=>$box[0]+$outsidepadding, 
2309               ymin=>$box[1]+$outsidepadding, 
2310               xmax=>$box[2]-$outsidepadding, 
2311               ymax=>$box[3]-$outsidepadding,
2312              $self->_get_fill('legend.fill', \@box));
2313   }
2314   $box[0] += $outsidepadding;
2315   $box[1] += $outsidepadding;
2316   $box[2] -= $outsidepadding;
2317   $box[3] -= $outsidepadding;
2318   my $ypos = $box[1] + $padding;
2319   my $patchpos = $box[0]+$padding;
2320   my $textpos = $patchpos + $patchsize + $gap;
2321   my %text_info = $self->_text_style('legend')
2322     or return;
2323   my $patchborder;
2324   if ($self->{_style}{legend}{patchborder}) {
2325     $patchborder = $self->_get_color('legend.patchborder')
2326       or return;
2327   }
2328   my $dataindex = 0;
2329   for my $label (@$labels) {
2330     my @patchbox = ( $patchpos - $patchsize/2, $ypos - $patchsize/2,
2331                      $patchpos + $patchsize * 3 / 2, $ypos + $patchsize*3/2 );
2332
2333     my @fill;
2334     if ($self->_draw_flat_legend()) {
2335       @fill = (color => $self->_data_color($dataindex), filled => 1);
2336     }
2337     else {
2338       @fill = $self->_data_fill($dataindex, \@patchbox)
2339         or return;
2340     }
2341     $img->box(xmin=>$patchpos, ymin=>$ypos, xmax=>$patchpos + $patchsize,
2342                ymax=>$ypos + $patchsize, @fill);
2343     if ($self->{_style}{legend}{patchborder}) {
2344       $img->box(xmin=>$patchpos, ymin=>$ypos, xmax=>$patchpos + $patchsize,
2345                 ymax=>$ypos + $patchsize,
2346                 color=>$patchborder);
2347     }
2348     $img->string(%text_info, x=>$textpos, 'y'=>$ypos + $patchsize, 
2349                  text=>$label);
2350
2351     my $step = $patchsize + $gap;
2352     if ($minrowsize < $sizes[$dataindex][3]) {
2353       $ypos += $sizes[$dataindex][3];
2354     }
2355     else {
2356       $ypos += $minrowsize;
2357     }
2358     ++$dataindex;
2359   }
2360   if ($self->{_style}{legend}{border}) {
2361     my $border_color = $self->_get_color('legend.border')
2362       or return;
2363     $img->box(xmin=>$box[0], ymin=>$box[1], xmax=>$box[2], ymax=>$box[3],
2364               color=>$border_color);
2365   }
2366   $self->_remove_box($chart_box, \@box);
2367   1;
2368 }
2369
2370 sub _draw_title {
2371   my ($self, $img, $chart_box) = @_;
2372
2373   my $title = $self->{_style}{title}{text};
2374   my @box = $self->_text_bbox($title, 'title')
2375     or return;
2376   my $yoff = $box[1];
2377   @box[0,1] = (0,0);
2378   $self->_align_box(\@box, $chart_box, 'title');
2379   my %text_info = $self->_text_style('title')
2380     or return;
2381   $img->string(%text_info, x=>$box[0], 'y'=>$box[3] + $yoff, text=>$title);
2382   $self->_remove_box($chart_box, \@box);
2383   1;
2384 }
2385
2386 sub _small_extent {
2387   my ($self, $box) = @_;
2388
2389   if ($box->[2] - $box->[0] > $box->[3] - $box->[1]) {
2390     return $box->[3] - $box->[1];
2391   }
2392   else {
2393     return $box->[2] - $box->[0];
2394   }
2395 }
2396
2397 sub _draw_flat_legend {
2398   return 0;
2399 }
2400
2401 =item _composite()
2402
2403 Returns a list of style fields that are stored as composites, and
2404 should be merged instead of just being replaced.
2405
2406 =cut
2407
2408 sub _composite {
2409   qw(title legend text label dropshadow outline callout graph);
2410 }
2411
2412 sub _filter_region {
2413   my ($self, $img, $left, $top, $right, $bottom, $filter) = @_;
2414
2415   unless (ref $filter) {
2416     my $name = $filter;
2417     $filter = $self->_get_thing($name)
2418       or return;
2419     $filter->{type}
2420       or return $self->_error("no type for filter $name");
2421   }
2422
2423   $left > 0 or $left = 0;
2424   $top > 0 or $top = 0;
2425
2426   my $masked = $img->masked(left=>$left, top=>$top,
2427                             right=>$right, bottom=>$bottom);
2428   $masked->filter(%$filter);
2429 }
2430
2431 =item _line(x1 => $x1, y1 => $y1, ..., style => $style)
2432
2433 Wrapper for line drawing, implements styles Imager doesn't.
2434
2435 Currently styles are limited to horizontal and vertical lines.
2436
2437 =cut
2438
2439 sub _line {
2440   my ($self, %opts) = @_;
2441
2442   my $img = delete $opts{img}
2443     or die "No img supplied to _line()";
2444   my $style = delete $opts{style} || "solid";
2445
2446   if ($style eq "solid" || ($opts{x1} != $opts{x2} && $opts{y1} != $opts{y2})) {
2447     return $img->line(%opts);
2448   }
2449   elsif ($style eq 'dashed' || $style eq 'dotted') {
2450     my ($x1, $y1, $x2, $y2) = delete @opts{qw/x1 y1 x2 y2/};
2451     # the line is vertical or horizontal, so swapping doesn't hurt
2452     $x1 > $x2 and ($x1, $x2) = ($x2, $x1);
2453     $y1 > $y2 and ($y1, $y2) = ($y2, $y1);
2454     my ($stepx, $stepy) = ( 0, 0 );
2455     my $step_size = $style eq "dashed" ? 8 : 2;
2456     my ($counter, $count_end);
2457     if ($x1 == $x2) {
2458       $stepy = $step_size;
2459       ($counter, $count_end) = ($y1, $y2);
2460     }
2461     else {
2462       $stepx = $step_size;
2463       ($counter, $count_end) = ($x1, $x2);
2464     }
2465     my ($x, $y) = ($x1, $y1);
2466     while ($counter < $count_end) {
2467       if ($style eq "dotted") {
2468         $img->setpixel(x => $x, y => $y, color => $opts{color});
2469       }
2470       else {
2471         my $xe = $stepx ? $x + $stepx / 2 - 1 : $x;
2472         $xe > $x2 and $xe = $x2;
2473         my $ye = $stepy ? $y + $stepy / 2 - 1 : $y;
2474         $ye > $y2 and $ye = $y2;
2475         $img->line(x1 => $x, y1 => $y, x2 => $xe, y2 => $ye, %opts);
2476       }
2477       $counter += $step_size;
2478       $x += $stepx;
2479       $y += $stepy;
2480     }
2481
2482     return 1;
2483   }
2484   else {
2485     $self->_error("Unknown line style $style");
2486     return;
2487   }
2488 }
2489
2490 =item _box(xmin ..., style => $style)
2491
2492 A wrapper for drawing styled box outlines.
2493
2494 =cut
2495
2496 sub _box {
2497   my ($self, %opts) = @_;
2498
2499   my $style = delete $opts{style} || "solid";
2500   my $img = delete $opts{img}
2501     or die "No img supplied to _box";
2502
2503   if ($style eq "solid") {
2504     return $img->box(%opts);
2505   }
2506   else {
2507     my $box = delete $opts{box};
2508     # replicate Imager's defaults
2509     my %work_opts = ( xmin => 0, ymin => 0, xmax => $img->getwidth() - 1, ymax => $img->getheight() -1, %opts, style => $style, img => $img );
2510     my ($xmin, $ymin, $xmax, $ymax) = delete @work_opts{qw/xmin ymin xmax ymax/};
2511     if ($box) {
2512       ($xmin, $ymin, $xmax, $ymax) = @$box;
2513     }
2514     $xmin > $xmax and ($xmin, $xmax) = ($xmax, $xmin);
2515     $ymin > $ymax and ($ymin, $ymax) = ($ymax, $ymin);
2516
2517     if ($xmax - $xmin > 1) {
2518       $self->_line(x1 => $xmin+1, y1 => $ymin, x2 => $xmax-1, y2 => $ymin, %work_opts);
2519       $self->_line(x1 => $xmin+1, y1 => $ymax, x2 => $xmax-1, y2 => $ymax, %work_opts);
2520     }
2521     $self->_line(x1 => $xmin, y1 => $ymin, x2 => $xmin, y2 => $ymax, %work_opts);
2522     return $self->_line(x1 => $xmin, y1 => $ymin, x2 => $xmin, y2 => $ymax, %work_opts);
2523   }
2524 }
2525
2526 1;
2527 __END__
2528
2529 =back
2530
2531 =head1 SEE ALSO
2532
2533 Imager::Graph::Pie(3), Imager(3), perl(1).
2534
2535 =head1 AUTHOR
2536
2537 Tony Cook <tony@develop-help.com>
2538
2539 =head1 LICENSE
2540
2541 Imager::Graph is licensed under the same terms as perl itself.
2542
2543 =head1 BLAME
2544
2545 Addi for producing a cool imaging module. :)
2546
2547 =cut