Imager::Graph 0.03
[imager-graph.git] / Graph.pm
CommitLineData
35574351
TC
1package Imager::Graph;
2
3=head1 NAME
4
5Imager::Graph - Perl extension for producing Graphs using the Imager library.
6
7=head1 SYNOPSIS
8
9 use Imager::Graph::SubClass;
10 my $chart = Imager::Graph::SubClass->new;
11 my $img = $chart->draw(data=>..., ...)
12 or die $chart->error;
13
14=head1 DESCRIPTION
15
16Imager::Graph provides style information to its base classes. It
17defines the colors, text display information and fills based on both
18built-in styles and modifications supplied by the user to the draw()
19method.
20
21For best results you need a version of Imager after 0.38. At the time
22of writing this is only available via CVS:
23
24 cvs -d :pserver:anoncvs@cvs.imager.perl.org:/u02/cvsroot login
25 cvs -d :pserver:anoncvs@cvs.imager.perl.org:/u02/cvsroot co Imager
26
27This provides extra file format support, fountain (gradient), hatch
28and image fills, and masked images.
29
30=over
31
32=cut
33
34use strict;
35use vars qw($VERSION);
36use Imager qw(:handy);
37
38$VERSION = '0.03';
39
40my $fancy_fills = 0;
41my ($im_version) = ($Imager::VERSION =~ /(\d\.[\d_]+)/);
42if ($im_version > 0.38) {
43 ++$fancy_fills;
44 require 'Imager/Fountain.pm';
45}
46
47# the maximum recursion depth in determining a color, fill or number
48use constant MAX_DEPTH => 10;
49
50my $NUM_RE = '(?:[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]\d+?)?)';
51
52=item new
53
54This is a simple constructor. No parameters required.
55
56=cut
57
58sub new {
59 bless {}, $_[0];
60}
61
62=item error
63
64Returns an error message. Only value if the draw() method returns false.
65
66=cut
67
68sub error {
69 $_[0]->{_errstr};
70}
71
72=item draw
73
74Creates a new image, draws the chart onto that image and returns it.
75
76Typically you will need to supply a C<data> parameter in the format
77required by that particular graph, and if your graph will use any
78text, a C<font> parameter
79
80You can also supply many different parameters which control the way
81the graph looks. These are supplied as keyword, value pairs, where
82the value can be a hashref containing sub values.
83
84The C<style> parameter will selects a basic color set, and possibly
85sets other related parameters. See L</"STYLES">.
86
87 my $img = $graph->draw(data=>\@data,
88 title=>{ text=>"Hello, World!",
89 size=>36,
90 color=>'FF0000' });
91
92When referring to a single sub-value this documentation will refer to
93'title.color' rather than 'the color element of title'.
94
95Returns the graph image on success, or false on failure.
96
97=back
98
99=head1 STYLES
100
101The currently defined styles are:
102
103=over
104
105=item primary_red
106
107a light red background with no outlines. Uses primary colors for the
108data fills. This style is compatible with all versions of Imager.
109
110Graphs drawn using this style should save well as a gif, even though
111some graphs may perform a slight blur.
112
113This is the default style.
114
115=item mono
116
117designed for monochrome output, such as most laser printers, this uses
118hatched fills for the data, and no colors. The returned image is a
119one channel image (which can be overridden with the C<channels>
120parameter.)
121
122You can also override the colors used by all components for background
123or drawing by supplying C<fg> and/or C<bg> parameters. ie. if you
124supply C<<fg=>'FF0000', channels=>3>> then the hash fills and anything
125else will be drawn in red. Another use might be to set a transparent
126background, by supplying C<<bg=>'00000000', channels=>4>>.
127
128This style outlines the legend if present and outlines the hashed fills.
129
130This and following styles require versions of Imager after 0.38.
131
132=item fount_lin
133
134designed as a "pretty" style this uses linear fountain fills for the
135background and data fills, and adds a drop shadow.
136
137You can override the value used for text and outlines by setting the
138C<fg> parameter.
139
140=item fount_rad
141
142also designed as a "pretty" style this uses radial fountain fills for
143the data and a linear blue to green fill for the background.
144
145=back
146
147=head1 FEATURES
148
149Each graph type has a number of features. These are used to add
150various items that are displayed in the graph area. Some common
151features are:
152
153=over
154
155=item legend
156
157adds a box containing boxes filled with the data filess, with
158the labels provided to the draw method. The legend will only be
159displayed if both the legend feature is enabled and labels are
160supplied.
161
162=item labels
163
164labels each data fill, usually by including text inside the data fill.
165If the text does not fit in the fill, they could be displayed in some
166other form, eg. as callouts in a pie graph. There usually isn't much
167point in including both labels and a legend.
168
169=item dropshadow
170
171a simple drop shadow is shown behind some of the graph elements.
172
173=back
174
175Each graph also has features specific to that graph.
176
177=head1 COMMON PARAMETERS
178
179When referring to a single sub-value this documentation will refer to
180'title.color' rather than 'the color element of title'.
181
182Normally, except for the font parameter, these are controlled by
183styles, but these are the style parameters I'd mostly likely expect
184you want to use:
185
186=over
187
188=item font
189
190the Imager font object used to draw text on the chart.
191
192=item back
193
194the background fill for the graph. Default depends on the style.
195
196=item size
197
198the base size of the graph image. Default: 256
199
200=item width
201
202the width of the graph image. Default: 1.5 * size (384)
203
204=item height
205
206the height of the graph image. Default: size (256)
207
208=item channels
209
210the number of channels in the image. Default: 3 (the 'mono' style
211sets this to 1).
212
213=item line
214
215the color used for drawing lines, such as outlines or callouts.
216Default depends on the current style. Set to undef to remove the
217outline from a style.
218
219=item title
220
221the text used for a graph title. Default: no title. Note: this is
222the same as the title=>{ text => ... } field.
223
224=over
225
226=item halign
227
228horizontal alignment of the title in the graph, one of 'left',
229'center' or 'right'. Default: center
230
231=item valign
232
233vertical alignment of the title, one of 'top', 'center' or 'right'.
234Default: top. It's probably a bad idea to set this to 'center' unless
235you have a very short title.
236
237=back
238
239=item text
240
241This contains basic defaults used in drawing text.
242
243=over
244
245=item color
246
247the default color used for all text, defaults to the fg color.
248
249=item size
250
251the base size used for text, also used to scale many graph elements.
252Default: 14.
253
254=back
255
256=back
257
258=head1 BEYOND STYLES
259
260In most cases you will want to use just the styles, but you may want
261to exert more control over the way your chart looks. This section
262describes the options you can use to control the way your chart looks.
263
264Hopefully you don't need to read this.
265
266=over
267
268=item back
269
270The background of the graph.
271
272=item bg
273
274=item fg
275
276Used to define basic background and foreground colors for the graph.
277The bg color may be used for the background of the graph, and is used
278as a default for the background of hatcheed fills. The fg is used as
279the default for line and text colors.
280
281=item font
282
283The default font used by the graph. Normally you should supply this
284if your graph as any text.
285
286=item line
287
288The default line color.
289
290=item text
291
292defaults for drawing text. Other textual graph elements will inherit
293or modify these values.
294
295=over
296
297=item color
298
299default text color, defaults to the I<fg> color.
300
301=item size
302
303default text size. Default: 14. This is used to scale many graph
304elements, including padding and leader sizes. Other text elements
305will either use or scale this value.
306
307=item font
308
309default font object. Inherited from I<font>, which should have been
310supplied by the caller.
311
312=back
313
314=item title
315
316If you supply a scalar value for this element, it will be stored in
317the I<text> field.
318
319Defines the text, font and layout information for the title.
320
321=over
322
323=item color
324
325The color of the title, inherited from I<text.color>.
326
327=item font
328
329The font object used for the title, inherited from I<text.font>.
330
331=item size
332
333size of the title text. Default: double I<text.size>
334
335=item halign
336
337=item valign
338
339The horizontal and vertical alignment of the title.
340
341=back
342
343=item legend
344
345defines attributes of the graph legend, if present.
346
347=over
348
349=item color
350
351=item font
352
353=item size
354
355text attributes for the labels used in the legend.
356
357=item patchsize
358
359the width and height of the color patch in the legend. Defaults to
36090% of the legend text size.
361
362=item patchgap
363
364the minimum gap between patches in pixels. Defaults to 30% of the
365patchsize.
366
367=item patchborder
368
369the color of the border drawn around each patch. Inherited from I<line>.
370
371=item halign
372
373=item valign
374
375the horizontal and vertical alignment of the legend within the graph.
376Defaults to 'right' and 'top'.
377
378=item padding
379
380the gap between the legend patches and text and the outside of it's
381box, or to the legend border, if any.
382
383=item outsidepadding
384
385the gap between the border and the outside of the legend's box. This
386is only used if the I<legend.border> attribute is defined.
387
388=item fill
389
390the background fill for the legend. Default: none
391
392=item border
393
394the border color of the legend. Default: none (no border is drawn
395around the legend.)
396
397=back
398
399=item callout
400
401defines attributes for graph callouts, if any are present. eg. if the
402pie graph cannot fit the label into the pie graph segement it will
403present it as a callout.
404
405=over
406
407=item color
408
409=item font
410
411=item size
412
413the text attributes of the callout label. Inherited from I<text>.
414
415=item line
416
417the color of the callout lines. Inherited from I<line>
418
419=item inside
420
421=item outside
422
423the length of the leader on the inside and the outside of the fill,
424usually at some angle. Both default to the size of the callout text.
425
426=item leadlen
427
428the length of the horizontal portion of the leader. Default:
429I<callout.size>.
430
431=item gap
432
433the gap between the callout leader and the callout text. Defaults to
43430% of the text callout size.
435
436=back
437
438=item label
439
440defines attributes for labels drawn into the data areas of a graph.
441
442=over
443
444=item color
445
446=item font
447
448=item size
449
450The text attributes of the labels. Inherited from I<text>.
451
452=back
453
454=item dropshadow
455
456the attributes of the graph's drop shadow
457
458=over
459
460=item fill
461
462the fill used for the drop shadow. Default: '404040' (dark gray)
463
464=item off
465
466the offset of the drop shadow. A convenience value inherited by offx
467and offy. Default: 40% of I<text.size>.
468
469=item offx
470
471=item offy
472
473the horizontal and vertical offsets of the drop shadow. Both
474inherited from I<dropshadow.off>.
475
476=item filter
477
478the filter description passed to Imager's filter method to blur the
479drop shadow. Default: an 11 element convolution filter.
480
481=back
482
483=item outline
484
485describes the lines drawn around filled data areas, such as the
486segments of a pie chart.
487
488=over
489
490=item line
491
492the line color of the outlines, inherited from I<line>.
493
494=back
495
496=item fills
497
498a reference to an array containing fills for each data item.
499
500You can mix fill types, ie. using a simple color for the first item, a
501hatched fill for the second and a fountain fill for the next.
502
503=back
504
505=head1 HOW VALUES WORK
506
507Internally rather than specifying literal color, fill, or font objects
508or literal sizes for each element, Imager::Graph uses a number of
509special values to inherit or modify values taken from other graph
510element names.
511
512=head2 Specifying colors
513
514You can specify colors by either supplying an Imager::Color object, by
515supplying lookup of another color, or by supplying a single value that
516Imager::Color::new can use as an initializer. The most obvious is
517just a 6 or 8 digit hex value representing the red, green, blue and
518optionally alpha channels of the image.
519
520You can lookup another color by using the lookup() "function", for
521example if you give a color as "lookup(fg)" then Imager::Graph will
522look for the fg element in the current style (or as overridden by
523you.) This is used internally by Imager::Graph to set up the
524relationships between the colors of various elements, for example the
525default style information contains:
526
527 text=>{
528 color=>'lookup(fg)',
529 ...
530 },
531 legend =>{
532 color=>'lookup(text.color)',
533 ...
534 },
535
536So by setting the I<fg> color, you also set the default text color,
537since each text element uses lookup(text.color) as its value.
538
539=head2 Specifying fills
540
541Fills can be used for the graph background color, the background color
542for the legend block and for the fills used for each data element.
543
544You can specify a fill as a L<color value|Specifying colors> or as a
545general fill, see L<Imager::Fill> for details. To use a general fill
546you need a version of Imager after 0.38.
547
548You don't need (or usually want) to call Imager::Fill::new yourself,
549since the various fill functions will call it for you, and
550Imager::Graph provides some hooks to make them more useful.
551
552=over
553
554=item *
555
556with hatched fills, if you don't supply a 'fg' or 'bg' parameter,
557Imager::Graph will supply the current graph fg and bg colors.
558
559=item *
560
561with fountain fill, you can supply the xa_ratio, ya_ratio, xb_ratio
562and yb_ratio parameters, and they will be scaled in the fill area to
563define the fountain fills xa, ya, xb and yb parameters.
564
565=back
566
567As with colors, you can use lookup(name) or lookup(name1.name2) to
568have one element to inherit the fill of another.
569
570=head2 Specifying numbers
571
572You can specify various numbers, usually representing the size of
573something, commonly text, but sometimes the length of a line or the
574size of a gap.
575
576You can use the same lookup mechanism as with colors and fills, but
577you can also scale values. For example, 'scale(0.5,text.size)' will
578return half the size of the normal text size.
579
580As with colors, this is used internally to scale graph elements based
581on the base text size. If you change the base text size then other
582graph elements will scale as well.
583
584=head2 Specifying other elements
585
586Other elements, such as fonts, or parameters for a filter, can also
587use the lookup(name) mechanism.
588
589=head1 INTERNAL METHODS
590
591Only useful if you need to fix bugs, add features or create a new
592graph class.
593
594=over
595
596=cut
597
598my %style_defs =
599 (
600 back=> 'lookup(bg)',
601 line=> 'lookup(fg)',
602 text=>{
603 color => 'lookup(fg)',
604 font => 'lookup(font)',
605 size => 14,
606 },
607 title=>{
608 color => 'lookup(text.color)',
609 font => 'lookup(text.font)',
610 halign => 'center',
611 valign => 'top',
612 size => 'scale(text.size,2.0)',
613 },
614 legend =>{
615 color => 'lookup(text.color)',
616 font => 'lookup(text.font)',
617 size => 'lookup(text.size)',
618 patchsize => 'scale(legend.size,0.9)',
619 patchgap => 'scale(legend.patchsize,0.3)',
620 patchborder => 'lookup(line)',
621 halign => 'right',
622 valign => 'top',
623 padding => 'scale(legend.size,0.3)',
624 outsidepadding => 'scale(legend.padding,0.4)',
625 },
626 callout => {
627 color => 'lookup(text.color)',
628 font => 'lookup(text.font)',
629 size => 'lookup(text.size)',
630 line => 'lookup(line)',
631 inside => 'lookup(callout.size)',
632 outside => 'lookup(callout.size)',
633 leadlen => 'scale(0.8,callout.size)',
634 gap => 'scale(callout.size,0.3)',
635 },
636 label => {
637 font => 'lookup(text.font)',
638 size => 'lookup(text.size)',
639 color => 'lookup(text.color)',
640 hpad => 'lookup(label.pad)',
641 vpad => 'lookup(label.pad)',
642 pad => 'scale(label.size,0.2)',
643 pcformat => sub { sprintf "%s (%.0f%%)", $_[0], $_[1] },
644 pconlyformat => sub { sprintf "%.1f%%", $_[0] },
645 },
646 dropshadow => {
647 fill => '404040',
648 off => 'scale(0.4,text.size)',
649 offx => 'lookup(dropshadow.off)',
650 offy => 'lookup(dropshadow.off)',
651 filter => { type=>'conv',
652 # this needs a fairly heavy blur
653 coef=>[0.1, 0.2, 0.4, 0.6, 0.7, 0.9, 1.2,
654 0.9, 0.7, 0.6, 0.4, 0.2, 0.1 ] },
655 },
656 outline => {
657 line =>'lookup(line)',
658 },
659 size=>256,
660 width=>'scale(1.5,size)',
661 height=>'lookup(size)',
662 );
663
664=item _error($message)
665
666Sets the error field of the object and returns an empty list or undef,
667depending on context. Should be used for error handling, since it may
668provide some user hooks at some point.
669
670=cut
671
672sub _error {
673 my ($self, $error) = @_;
674
675 $self->{_errstr} = $error;
676
677 return;
678}
679
680
681=item _style_defs()
682
683Returns the style defaults, such as the relationships between line
684color and text color.
685
686Intended to be over-ridden by base classes to provide graph specific
687defaults.
688
689=cut
690
691sub _style_defs {
692 \%style_defs;
693}
694
695my $def_style = 'primary_red';
696
697my %styles =
698 (
699 primary_red =>
700 {
701 fills=>
702 [
703 qw(FF0000 00FF00 0000FF C0C000 00C0C0 FF00FF)
704 ],
705 fg=>'000000',
706 bg=>'C08080',
707 legend=>
708 {
709 patchborder=>'000000'
710 },
711 },
712 mono =>
713 {
714 fills=>
715 [
716 { hatch=>'slash2' },
717 { hatch=>'slosh2' },
718 { hatch=>'vline2' },
719 { hatch=>'hline2' },
720 { hatch=>'cross2' },
721 { hatch=>'grid2' },
722 { hatch=>'stipple3' },
723 { hatch=>'stipple2' },
724 ],
725 channels=>1,
726 bg=>'FFFFFF',
727 fg=>'000000',
728 features=>{ outline=>1 },
729 pie =>{
730 blur=>undef,
731 },
732 },
733 );
734
735if ($fancy_fills) {
736 $styles{fount_lin} =
737 {
738 fills=>
739 [
740 { fountain=>'linear',
741 xa_ratio=>0.13, ya_ratio=>0.13, xb_ratio=>0.87, yb_ratio=>0.87,
742 repeat=>'sawtooth',
743 segments => Imager::Fountain->simple(positions=>[0, 1],
744 colors=>[ NC('FFC0C0'), NC('FF0000') ]),
745 },
746 { fountain=>'linear',
747 xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
748 segments => Imager::Fountain->simple(positions=>[0, 1],
749 colors=>[ NC('C0FFC0'), NC('00FF00') ]),
750 },
751 { fountain=>'linear',
752 xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
753 segments => Imager::Fountain->simple(positions=>[0, 1],
754 colors=>[ NC('C0C0FF'), NC('0000FF') ]),
755 },
756 { fountain=>'linear',
757 xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
758 segments => Imager::Fountain->simple(positions=>[0, 1],
759 colors=>[ NC('FFFFC0'), NC('FFFF00') ]),
760 },
761 { fountain=>'linear',
762 xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
763 segments => Imager::Fountain->simple(positions=>[0, 1],
764 colors=>[ NC('C0FFFF'), NC('00FFFF') ]),
765 },
766 { fountain=>'linear',
767 xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
768 segments => Imager::Fountain->simple(positions=>[0, 1],
769 colors=>[ NC('FFC0FF'), NC('FF00FF') ]),
770 },
771 ],
772 back=>{ fountain=>'linear',
773 xa_ratio=>0, ya_ratio=>0,
774 xb_ratio=>1.0, yb_ratio=>1.0,
775 segments=>Imager::Fountain->simple
776 ( positions=>[0, 1],
777 colors=>[ NC('6060FF'), NC('60FF60') ]) },
778 fg=>'000000',
779 bg=>'FFFFFF',
780 features=>{ dropshadow=>1 },
781 };
782 $styles{fount_rad} =
783 {
784 fills=>
785 [
786 { fountain=>'radial',
787 xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
788 segments => Imager::Fountain->simple(positions=>[0, 1],
789 colors=>[ NC('FF8080'), NC('FF0000') ]),
790 },
791 { fountain=>'radial',
792 xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
793 segments => Imager::Fountain->simple(positions=>[0, 1],
794 colors=>[ NC('80FF80'), NC('00FF00') ]),
795 },
796 { fountain=>'radial',
797 xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
798 segments => Imager::Fountain->simple(positions=>[0, 1],
799 colors=>[ NC('808080FF'), NC('0000FF') ]),
800 },
801 { fountain=>'radial',
802 xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
803 segments => Imager::Fountain->simple(positions=>[0, 1],
804 colors=>[ NC('FFFF80'), NC('FFFF00') ]),
805 },
806 { fountain=>'radial',
807 xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
808 segments => Imager::Fountain->simple(positions=>[0, 1],
809 colors=>[ NC('80FFFF'), NC('00FFFF') ]),
810 },
811 { fountain=>'radial',
812 xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
813 segments => Imager::Fountain->simple(positions=>[0, 1],
814 colors=>[ NC('FF80FF'), NC('FF00FF') ]),
815 },
816 ],
817 back=>{ fountain=>'linear',
818 xa_ratio=>0, ya_ratio=>0,
819 xb_ratio=>1.0, yb_ratio=>1.0,
820 segments=>Imager::Fountain->simple
821 ( positions=>[0, 1],
822 colors=>[ NC('6060FF'), NC('60FF60') ]) },
823 fg=>'000000',
824 bg=>'FFFFFF',
825 };
826}
827
828=item $self->_style_setup(\%opts)
829
830Uses the values from %opts to build a customized hash describing the
831way the graph should be drawn.
832
833=cut
834
835sub _style_setup {
836 my ($self, $opts) = @_;
837 my $style_defs = $self->_style_defs;
838 my $style;
839 $style = $styles{$opts->{style}} if $opts->{style};
840 $style ||= $styles{$def_style};
841
842 my @search_list = ( $style_defs, $style, $opts);
843 my %work;
844
845 my @composite = $self->_composite();
846 my %composite;
847 @composite{@composite} = @composite;
848
849 for my $src (@search_list) {
850 for my $key (keys %$src) {
851 if ($composite{$key}) {
852 $work{$key} = {} unless exists $work{$key};
853 if (ref $src->{$key}) {
854 # some keys have sub values, especially text
855 @{$work{$key}}{keys %{$src->{$key}}} = values %{$src->{$key}};
856 }
857 else {
858 # assume it's the text for a title or something
859 $work{$key}{text} = $src->{$key};
860 }
861 }
862 else {
863 $work{$key} = $src->{$key};
864 }
865 }
866 }
867
868 # features are handled specially
869 $work{features} = {};
870 for my $src (@search_list) {
871 if ($src->{features}) {
872 if (ref $src->{features}) {
873 if (ref($src->{features}) =~ /ARRAY/) {
874 # just set those features
875 for my $feature (@{$src->{features}}) {
876 $work{features}{$feature} = 1;
877 }
878 }
879 elsif (ref($src->{features}) =~ /HASH/) {
880 if ($src->{features}{reset}) {
881 $work{features} = {}; # only the ones the user specifies
882 }
883 @{$work{features}}{keys %{$src->{features}}} =
884 values(%{$src->{features}});
885 }
886 }
887 else {
888 # just set that single feature
889 $work{features}{$src->{features}} = 1;
890 }
891 }
892 }
893 #use Data::Dumper;
894 #print Dumper(\%work);
895
896 $self->{_style} = \%work;
897}
898
899=item $self->_get_thing($name)
900
901Retrieve some general 'thing'.
902
903Supports the 'lookup(foo)' mechanism.
904
905=cut
906
907sub _get_thing {
908 my ($self, $name, @depth) = @_;
909
910 push(@depth, $name);
911 my $what;
912 if ($name =~ /^(\w+)\.(\w+)$/) {
913 $what = $self->{_style}{$1}{$2};
914 }
915 else {
916 $what = $self->{_style}{$name};
917 }
918 defined $what or
919 return;
920 if (ref $what) {
921 return $what;
922 }
923 elsif ($what =~ /^lookup\((\w+(?:\.\w+)?)\)$/) {
924 @depth < MAX_DEPTH
925 or return $self->_error("too many levels of recursion in lookup(@depth)");
926 return $self->_get_thing($1, @depth);
927 }
928 else {
929 return $what;
930 }
931}
932
933=item $self->_get_number($name)
934
935Retrieves a number from the style. The value in the style can be the
936number, or one of two functions:
937
938=over
939
940=item lookup(newname)
941
942Recursively looks up I<newname> in the style.
943
944=item scale(value1,value2)
945
946Each value can be a number or a name. Names are recursively looks up
947in the style and the product is returned.
948
949=back
950
951=cut
952sub _get_number {
953 my ($self, $name, @depth) = @_;
954
955 push(@depth, $name);
956 my $what;
957 if ($name =~ /^(\w+)\.(\w+)$/) {
958 $what = $self->{_style}{$1}{$2};
959 }
960 else {
961 $what = $self->{_style}{$name};
962 }
963 defined $what or
964 return $self->_error("$name is undef (@depth)");
965
966 if (ref $what) {
967 if ($what =~ /CODE/) {
968 $what = $what->($self, $name);
969 }
970 }
971 else {
972 if ($what =~ /^lookup\(([\w.]+)\)$/) {
973 @depth < MAX_DEPTH
974 or return $self->_error("too many levels of recursion in lookup (@depth)");
975 return $self->_get_number($1, @depth);
976 }
977 elsif ($what =~ /^scale\(
978 ((?:[a-z][\w.]*)|$NUM_RE)
979 ,
980 ((?:[a-z][\w.]*)|$NUM_RE)\)$/x) {
981 my ($left, $right) = ($1, $2);
982 unless ($left =~ /^$NUM_RE$/) {
983 @depth < MAX_DEPTH
984 or return $self->_error("too many levels of recursion in scale (@depth)");
985 $left = $self->_get_number($left, @depth);
986 }
987 unless ($right =~ /^$NUM_RE$/) {
988 @depth < MAX_DEPTH
989 or return $self->_error("too many levels of recursion in scale (@depth)");
990 $right = $self->_get_number($right, @depth);
991 }
992 return $left * $right;
993 }
994 else {
995 return $what+0;
996 }
997 }
998}
999
1000=item _get_color($name)
1001
1002Returns a color object of the given name from the style hash.
1003
1004Uses Imager::Color->new to translate normal scalars into color objects.
1005
1006Allows the lookup(name) mechanism.
1007
1008=cut
1009
1010sub _get_color {
1011 my ($self, $name, @depth) = @_;
1012
1013 push(@depth, $name);
1014 my $what;
1015 if ($name =~ /^(\w+)\.(\w+)$/) {
1016 $what = $self->{_style}{$1}{$2};
1017 }
1018 else {
1019 $what = $self->{_style}{$name};
1020 }
1021
1022 defined($what)
1023 or return $self->_error("$name was undefined (@depth)");
1024
1025 unless (ref $what) {
1026 if ($what =~ /^lookup\((\w+(?:\.\w+)?)\)$/) {
1027 @depth < MAX_DEPTH or
1028 return $self->_error("too many levels of recursion in lookup (@depth)");
1029
1030 return $self->_get_color($1, @depth);
1031 }
1032 $what = Imager::Color->new($what);
1033 }
1034
1035 $what;
1036}
1037
1038=item _translate_fill($what, $box)
1039
1040Given the value of a fill, either attempts to convert it into a fill
1041list (one of C<<color=>$color_value, filled=>1>> or C<<fill=>{ fill
1042parameters }>>), or to lookup another fill that is referred to with
1043the 'lookup(name)' mechanism.
1044
1045This function does the fg and bg initialization for hatched fills, and
1046translation of *_ratio for fountain fills (using the $box parameter).
1047
1048=cut
1049
1050sub _translate_fill {
1051 my ($self, $what, $box, @depth) = @_;
1052
1053 if (ref $what) {
1054 if (UNIVERSAL::isa($what, "Imager::Color")) {
1055 return ( color=>Imager::Color->new($what), filled=>1 );
1056 }
1057 else {
1058 # a general fill
1059 if ($what->{hatch}) {
1060 my %work = %$what;
1061 if (!$work{fg}) {
1062 $work{fg} = $self->_get_color('fg')
1063 or return;
1064 }
1065 if (!$work{bg}) {
1066 $work{bg} = $self->_get_color('bg')
1067 or return;
1068 }
1069 return ( fill=>\%work );
1070 }
1071 elsif ($what->{fountain}) {
1072 my %work = %$what;
1073 for my $key (qw(xa ya xb yb)) {
1074 if (exists $work{"${key}_ratio"}) {
1075 if ($key =~ /^x/) {
1076 $work{$key} = $box->[0] + $work{"${key}_ratio"}
1077 * ($box->[2] - $box->[0]);
1078 }
1079 else {
1080 $work{$key} = $box->[1] + $work{"${key}_ratio"}
1081 * ($box->[3] - $box->[1]);
1082 }
1083 }
1084 }
1085 return ( fill=>\%work );
1086 }
1087 else {
1088 return ( fill=> $what );
1089 }
1090 }
1091 }
1092 else {
1093 if ($what =~ /^lookup\((\w+(?:\.\w+)?)\)$/) {
1094 return $self->_get_fill($1, $box, @depth);
1095 }
1096 else {
1097 # assumed to be an Imager::Color single value
1098 return ( color=>Imager::Color->new($what), filled=>1 );
1099 }
1100 }
1101}
1102
1103=item _data_fill($index, $box)
1104
1105Retrieves the fill parameters for a data area fill.
1106
1107=cut
1108
1109sub _data_fill {
1110 my ($self, $index, $box) = @_;
1111
1112 my $fills = $self->{_style}{fills};
1113 return $self->_translate_fill($fills->[$index % @$fills], $box,
1114 "data.$index");
1115}
1116
1117=item _get_fill($index, $box)
1118
1119Retrieves fill parameters for a named fill.
1120
1121=cut
1122
1123sub _get_fill {
1124 my ($self, $name, $box, @depth) = @_;
1125
1126 push(@depth, $name);
1127 my $what;
1128 if ($name =~ /^(\w+)\.(\w+)$/) {
1129 $what = $self->{_style}{$1}{$2};
1130 }
1131 else {
1132 $what = $self->{_style}{$name};
1133 }
1134
1135 defined($what)
1136 or return $self->_error("no fill $name found");
1137
1138 return $self->_translate_fill($what, $box, @depth);
1139}
1140
1141=item _make_img()
1142
1143Builds the image object for the graph and fills it with the background
1144fill.
1145
1146=cut
1147
1148sub _make_img {
1149 my ($self) = @_;
1150
1151 my ($width, $height) = (256, 256);
1152
1153 $width = $self->_get_number('width');
1154 $height = $self->_get_number('height');
1155 my $channels = $self->{_style}{channels};
1156
1157 $channels ||= 3;
1158
1159 my $img = Imager->new(xsize=>$width, ysize=>$height, channels=>$channels);
1160
1161 $img->box($self->_get_fill('back', [ 0, 0, $width-1, $height-1]));
1162
1163 $img;
1164}
1165
1166sub _text_style {
1167 my ($self, $name) = @_;
1168
1169 my %work;
1170
1171 if ($self->{_style}{$name}) {
1172 %work = %{$self->{_style}{$name}};
1173 }
1174 else {
1175 %work = %{$self->{_style}{text}};
1176 }
1177 $work{font}
1178 or return $self->_error("$name has no font parameter");
1179
1180 $work{font} = $self->_get_thing("$name.font")
1181 or return $self->_error("invalid font");
1182 UNIVERSAL::isa($work{font}, "Imager::Font")
1183 or return $self->_error("$name.font is not a font");
1184 if ($work{color} && !ref $work{color}) {
1185 $work{color} = $self->_get_color("$name.color")
1186 or return;
1187 }
1188 $work{size} = $self->_get_number("$name.size");
1189 $work{sizew} = $self->_get_number("$name.sizew")
1190 if $work{sizew};
1191
1192 %work;
1193}
1194
1195sub _text_bbox {
1196 my ($self, $text, $name) = @_;
1197
1198 my %text_info = $self->_text_style($name);
1199
1200 my @bbox = $text_info{font}->bounding_box(%text_info, string=>$text,
1201 canon=>1);
1202
1203 return @bbox[0..3];
1204}
1205
1206sub _align_box {
1207 my ($self, $box, $chart_box, $name) = @_;
1208
1209 my $halign = $self->{_style}{$name}{halign}
1210 or $self->_error("no halign for $name");
1211 my $valign = $self->{_style}{$name}{valign};
1212
1213 if ($halign eq 'right') {
1214 $box->[0] += $chart_box->[2] - $box->[2];
1215 }
1216 elsif ($halign eq 'left') {
1217 $box->[0] = $chart_box->[0];
1218 }
1219 elsif ($halign eq 'center' || $halign eq 'centre') {
1220 $box->[0] = ($chart_box->[0] + $chart_box->[2] - $box->[2])/2;
1221 }
1222 else {
1223 return $self->_error("invalid halign $halign for $name");
1224 }
1225
1226 if ($valign eq 'top') {
1227 $box->[1] = $chart_box->[1];
1228 }
1229 elsif ($valign eq 'bottom') {
1230 $box->[1] = $chart_box->[3] - $box->[3];
1231 }
1232 elsif ($valign eq 'center' || $valign eq 'centre') {
1233 $box->[1] = ($chart_box->[1] + $chart_box->[3] - $box->[3])/2;
1234 }
1235 else {
1236 return $self->_error("invalid valign $valign for $name");
1237 }
1238 $box->[2] += $box->[0];
1239 $box->[3] += $box->[1];
1240}
1241
1242sub _remove_box {
1243 my ($self, $chart_box, $object_box) = @_;
1244
1245 my $areax;
1246 my $areay;
1247 if ($object_box->[0] - $chart_box->[0]
1248 < $chart_box->[2] - $object_box->[2]) {
1249 $areax = ($object_box->[2] - $chart_box->[0])
1250 * ($chart_box->[3] - $chart_box->[1]);
1251 }
1252 else {
1253 $areax = ($chart_box->[2] - $object_box->[0])
1254 * ($chart_box->[3] - $chart_box->[1]);
1255 }
1256
1257 if ($object_box->[1] - $chart_box->[1]
1258 < $chart_box->[3] - $object_box->[3]) {
1259 $areay = ($object_box->[3] - $chart_box->[1])
1260 * ($chart_box->[2] - $chart_box->[0]);
1261 }
1262 else {
1263 $areay = ($chart_box->[3] - $object_box->[1])
1264 * ($chart_box->[2] - $chart_box->[0]);
1265 }
1266
1267 if ($areay < $areax) {
1268 if ($object_box->[1] - $chart_box->[1]
1269 < $chart_box->[3] - $object_box->[3]) {
1270 $chart_box->[1] = $object_box->[3];
1271 }
1272 else {
1273 $chart_box->[3] = $object_box->[1];
1274 }
1275 }
1276 else {
1277 if ($object_box->[0] - $chart_box->[0]
1278 < $chart_box->[2] - $object_box->[2]) {
1279 $chart_box->[0] = $object_box->[2];
1280 }
1281 else {
1282 $chart_box->[2] = $object_box->[0];
1283 }
1284 }
1285}
1286
1287sub _draw_legend {
1288 my ($self, $img, $labels, $chart_box) = @_;
1289
1290 defined(my $padding = $self->_get_number('legend.padding'))
1291 or return;
1292 my $patchsize = $self->_get_number('legend.patchsize')
1293 or return;
1294 defined(my $gap = $self->_get_number('legend.patchgap'))
1295 or return;
1296 my $minrowsize = $patchsize + $gap;
1297 my ($width, $height) = (0,0);
1298 my @sizes;
1299 for my $label (@$labels) {
1300 my @box = $self->_text_bbox($label, 'legend');
1301 push(@sizes, \@box);
1302 $width = $box[2] if $box[2] > $width;
1303 if ($minrowsize > $box[3]) {
1304 $height += $minrowsize;
1305 }
1306 else {
1307 $height += $box[3];
1308 }
1309 }
1310 my @box = (0, 0,
1311 $width + $patchsize + $padding * 2 + $gap,
1312 $height + $padding * 2 - $gap);
1313 my $outsidepadding = 0;
1314 if ($self->{_style}{legend}{border}) {
1315 defined($outsidepadding = $self->_get_number('legend.outsidepadding'))
1316 or return;
1317 $box[2] += 2 * $outsidepadding;
1318 $box[3] += 2 * $outsidepadding;
1319 }
1320 $self->_align_box(\@box, $chart_box, 'legend')
1321 or return;
1322 if ($self->{_style}{legend}{fill}) {
1323 $img->box(xmin=>$box[0]+$outsidepadding,
1324 ymin=>$box[1]+$outsidepadding,
1325 xmax=>$box[2]-$outsidepadding,
1326 ymax=>$box[3]-$outsidepadding,
1327 $self->_get_fill('legend.fill', \@box));
1328 }
1329 $box[0] += $outsidepadding;
1330 $box[1] += $outsidepadding;
1331 $box[2] -= $outsidepadding;
1332 $box[3] -= $outsidepadding;
1333 my $ypos = $box[1] + $padding;
1334 my $patchpos = $box[0]+$padding;
1335 my $textpos = $patchpos + $patchsize + $gap;
1336 my %text_info = $self->_text_style('legend')
1337 or return;
1338 my $patchborder;
1339 if ($self->{_style}{legend}{patchborder}) {
1340 $patchborder = $self->_get_color('legend.patchborder')
1341 or return;
1342 }
1343 my $dataindex = 0;
1344 for my $label (@$labels) {
1345 my @patchbox = ( $patchpos - $patchsize/2, $ypos - $patchsize/2,
1346 $patchpos + $patchsize * 3 / 2, $ypos + $patchsize*3/2 );
1347 my @fill = $self->_data_fill($dataindex, \@patchbox)
1348 or return;
1349 $img->box(xmin=>$patchpos, ymin=>$ypos, xmax=>$patchpos + $patchsize,
1350 ymax=>$ypos + $patchsize, @fill);
1351 if ($self->{_style}{legend}{patchborder}) {
1352 $img->box(xmin=>$patchpos, ymin=>$ypos, xmax=>$patchpos + $patchsize,
1353 ymax=>$ypos + $patchsize,
1354 color=>$patchborder);
1355 }
1356 $img->string(%text_info, x=>$textpos, 'y'=>$ypos + $patchsize,
1357 text=>$label);
1358
1359 my $step = $patchsize + $gap;
1360 if ($minrowsize < $sizes[$dataindex][3]) {
1361 $ypos += $sizes[$dataindex][3];
1362 }
1363 else {
1364 $ypos += $minrowsize;
1365 }
1366 ++$dataindex;
1367 }
1368 if ($self->{_style}{legend}{border}) {
1369 my $border_color = $self->_get_color('legend.border')
1370 or return;
1371 $img->box(xmin=>$box[0], ymin=>$box[1], xmax=>$box[2], ymax=>$box[3],
1372 color=>$border_color);
1373 }
1374 $self->_remove_box($chart_box, \@box);
1375 1;
1376}
1377
1378sub _draw_title {
1379 my ($self, $img, $chart_box) = @_;
1380
1381 my $title = $self->{_style}{title}{text};
1382 my @box = $self->_text_bbox($title, 'title');
1383 my $yoff = $box[1];
1384 @box[0,1] = (0,0);
1385 $self->_align_box(\@box, $chart_box, 'title');
1386 my %text_info = $self->_text_style('title');
1387 $img->string(%text_info, x=>$box[0], 'y'=>$box[3] + $yoff, text=>$title);
1388 $self->_remove_box($chart_box, \@box);
1389 1;
1390}
1391
1392sub _small_extent {
1393 my ($self, $box) = @_;
1394
1395 if ($box->[2] - $box->[0] > $box->[3] - $box->[1]) {
1396 return $box->[3] - $box->[1];
1397 }
1398 else {
1399 return $box->[2] - $box->[0];
1400 }
1401}
1402
1403=item _composite()
1404
1405Returns a list of style fields that are stored as composites, and
1406should be merged instead of just being replaced.
1407
1408=cut
1409
1410sub _composite {
1411 qw(title legend text label dropshadow outline callout);
1412}
1413
1414sub _filter_region {
1415 my ($self, $img, $left, $top, $right, $bottom, $filter) = @_;
1416
1417 unless (ref $filter) {
1418 my $name = $filter;
1419 $filter = $self->_get_thing($name)
1420 or return;
1421 $filter->{type}
1422 or return $self->_error("no type for filter $name");
1423 }
1424
1425 $left > 0 or $left = 0;
1426 $top > 0 or $top = 0;
1427
1428 # newer versions of Imager let you work on just part of an image
1429 if ($img->can('masked') && !$self->{_style}{features}{_debugblur}) {
1430 my $masked = $img->masked(left=>$left, top=>$top,
1431 right=>$right, bottom=>$bottom);
1432 $masked->filter(%$filter);
1433 }
1434 else {
1435 # for older versions of Imager
1436 my $subset = $img->crop(left=>$left, top=>$top,
1437 right=>$right, bottom=>$bottom);
1438 $subset->filter(%$filter);
1439 $img->paste(left=>$left, top=>$top, img=>$subset);
1440 }
1441}
1442
14431;
1444__END__
1445
1446=back
1447
1448=head1 SEE ALSO
1449
1450Imager::Graph::Pie(3), Imager(3), perl(1).
1451
1452=head1 AUTHOR
1453
1454Tony Cook <tony@develop-help.com>
1455
1456=head1 BLAME
1457
1458Addi for producing a cool imaging module. :)
1459
1460=cut