File Coverage

blib/lib/Image/Magick/Chart.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Image::Magick::Chart;
2              
3             # Name:
4             # Image::Magick::Slice.
5             #
6             # Documentation:
7             # POD-style documentation is at the end. Extract it with pod2html.*.
8             #
9             # Reference:
10             # Object Oriented Perl
11             # Damian Conway
12             # Manning
13             # 1-884777-79-1
14             # P 114
15             #
16             # Note:
17             # o Tab = 4 spaces || die.
18             #
19             # Author:
20             # Ron Savage
21             # Home page: http://savage.net.au/index.html
22             #
23             # Licence:
24             # Australian copyright (c) 2005 Ron Savage.
25             #
26             # All Programs of mine are 'OSI Certified Open Source Software';
27             # you can redistribute them and/or modify them under the terms of
28             # The Artistic License, a copy of which is available at:
29             # http://www.opensource.org/licenses/index.html
30              
31 1     1   26156 use strict;
  1         3  
  1         43  
32 1     1   5 use warnings;
  1         2  
  1         28  
33              
34 1     1   5 use Carp;
  1         1  
  1         98  
35 1     1   402 use Image::Magick;
  0            
  0            
36              
37             require 5.005_62;
38              
39             require Exporter;
40              
41             our @ISA = qw(Exporter);
42              
43             # Items to export into callers namespace by default. Note: do not export
44             # names by default without a very good reason. Use EXPORT_OK instead.
45             # Do not simply export all your public functions/methods/constants.
46              
47             # This allows declaration use Image::Magick::Chart ':all';
48             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
49             # will save memory.
50             our %EXPORT_TAGS = ( 'all' => [ qw(
51              
52             ) ] );
53              
54             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
55              
56             our @EXPORT = qw(
57              
58             );
59             our $VERSION = '1.06';
60              
61             # -----------------------------------------------
62              
63             # Preloaded methods go here.
64              
65             # -----------------------------------------------
66              
67             # Encapsulated class data.
68              
69             {
70             my(%_attr_data) =
71             (
72             _antialias => 0, # 0 => No antialias; 1 => Antialias.
73             _bar_width => 8, # Pixels.
74             _bg_color => 'white',
75             _colorspace => 'RGB',
76             _depth => 8, # Bits per channel.
77             _fg_color => 'black',
78             _font => 'Courier',
79             _frame_color => 'black',
80             _frame_option => 1, # 0 => None; 1 => Draw it.
81             _height => 0,
82             _image => '', # To specify the padding in pixels around the frame
83             _output_file_name => '', # I use CSS-style for the 4 sides of the image:
84             _padding => [30, 30, 30, 30], # [12 noon, 3, 6, 9].
85             _pointsize => 14, # Points.
86             _tick_length => 4, # Pixels.
87             _title => '',
88             _width => 0,
89             _x_axis_data => [],
90             _x_axis_labels => [],
91             _x_axis_labels_option => 1, # 0 => None; 1 => Draw them.
92             _x_axis_ticks_option => 2, # 0 => None; 1 => Below x-axis; 2 => Across frame.
93             _x_data => [],
94             _x_data_option => 1,
95             _x_pixels_per_unit => 3, # Horizontal width of each data unit.
96             _y_axis_data => [],
97             _y_axis_labels => [],
98             _y_axis_labels_option => 1, # 0 => None; 1 => Draw them.
99             _y_axis_labels_x => undef, # undef => Ignore; Other => Use.
100             _y_axis_ticks_option => 1, # 0 => None; 1 => Left of y-axis; 2 => Across frame.
101             _y_pixels_per_unit => 20,
102             );
103              
104             sub _default_for
105             {
106             my($self, $attr_name) = @_;
107              
108             $_attr_data{$attr_name};
109             }
110              
111             sub _standard_keys
112             {
113             keys %_attr_data;
114             }
115              
116             } # End of encapsulated class data.
117              
118             # -----------------------------------------------
119              
120             sub draw_frame
121             {
122             my($self) = @_;
123             my($x_max) = $$self{'_x_pixels_per_unit'} * $$self{'_x_axis_data'}[$#{$$self{'_x_axis_data'} }];
124              
125             $$self{'_image'} -> Draw
126             (
127             fill => 'none',
128             primitive => 'polyline',
129             stroke => $$self{'_frame_color'},
130             points => sprintf
131             (
132             "%i,%i %i,%i %i,%i %i,%i %i,%i",
133             $$self{'_padding'}[3], $$self{'_padding'}[0],
134             $$self{'_padding'}[3] + $x_max, $$self{'_padding'}[0],
135             $$self{'_padding'}[3] + $x_max, ($$self{'_height'} - $$self{'_padding'}[2] - 1),
136             $$self{'_padding'}[3], ($$self{'_height'} - $$self{'_padding'}[2] - 1),
137             $$self{'_padding'}[3], $$self{'_padding'}[0]
138             ),
139             ) && Carp::croak("Can't draw frame");
140              
141             } # End of draw_frame.
142              
143             # -----------------------------------------------
144              
145             sub draw_horizontal_bars
146             {
147             my($self) = @_;
148             my($half_bar_width) = int($$self{'_bar_width'} / 2);
149             my($y_zero) = $$self{'_height'} - $$self{'_padding'}[2] - 1;
150              
151             my($i, $data, @metric, $x_right, $y_top);
152              
153             for $i (0 .. $#{$$self{'_x_data'} })
154             {
155             $data = $$self{'_x_data'}[$i];
156             $x_right = $$self{'_padding'}[3] + ($$self{'_x_pixels_per_unit'} * $data);
157             $y_top = $y_zero - ($$self{'_y_pixels_per_unit'} * $$self{'_y_axis_data'}[$i]);
158              
159             $$self{'_image'} -> Draw
160             (
161             fill => $$self{'_fg_color'},
162             primitive => 'polyline',
163             method => 'floodfill',
164             stroke => $$self{'_fg_color'},
165             points => sprintf
166             (
167             "%i,%i %i,%i %i,%i %i,%i",
168             $$self{'_padding'}[3], $y_top - $half_bar_width,
169             $x_right, $y_top - $half_bar_width,
170             $x_right, $y_top + $half_bar_width,
171             $$self{'_padding'}[3], $y_top + $half_bar_width,
172             ),
173             ) && Carp::croak("Can't draw horizontal bars");
174              
175             next if ($$self{'_x_data_option'} == 0);
176              
177             @metric = $$self{'_image'} -> QueryFontMetrics(text => $data);
178              
179             $$self{'_image'} -> Annotate
180             (
181             font => $$self{'_font'},
182             text => $data,
183             stroke => 'black',
184             strokewidth => 1,
185             pointsize => $$self{'_pointsize'},
186             x => $x_right + $$self{'_tick_length'},
187             y => $y_top + int($metric[5] / 2) - 2,
188             ) && Carp::croak("Can't draw horizontal bars");
189             }
190              
191             } # End of draw_horizontal_bars.
192              
193             # -----------------------------------------------
194              
195             sub draw_title
196             {
197             my($self) = @_;
198              
199             $$self{'_image'} -> Annotate
200             (
201             font => $$self{'_font'},
202             text => $$self{'_title'},
203             stroke => 'black',
204             strokewidth => 1,
205             pointsize => $$self{'_pointsize'},
206             x => int( ($$self{'_width'} - int(int($$self{'_pointsize'} / 2) * length($$self{'_title'}) ) ) / 2),
207             y => int($$self{'_padding'}[0] / 2) + 2,
208             ) && Carp::croak("Can't draw title");
209              
210             } # End of draw_title.
211              
212             # -----------------------------------------------
213              
214             sub draw_x_axis_labels
215             {
216             my($self) = @_;
217             my($x_zero) = $$self{'_padding'}[3];
218              
219             my($i, $text, $x_step, @metric);
220              
221             for $i (0 .. $#{$$self{'_x_axis_labels'} })
222             {
223             $text = $$self{'_x_axis_labels'}[$i];
224             $x_step = $x_zero + ($$self{'_x_pixels_per_unit'} * $$self{'_x_axis_data'}[$i]);
225             @metric = $$self{'_image'} -> QueryFontMetrics(text => $text);
226              
227             $$self{'_image'} -> Annotate
228             (
229             font => $$self{'_font'},
230             text => $text,
231             stroke => $$self{'_frame_color'},
232             strokewidth => 1,
233             pointsize => $$self{'_pointsize'},
234             x => $x_step - int($metric[4] / 2) - 1,
235             y => $$self{'_height'} - $$self{'_pointsize'},
236             ) && Carp::croak("Can't draw X-axis labels");
237             }
238              
239             } # End of draw_x_axis_labels.
240              
241             # -----------------------------------------------
242              
243             sub draw_x_axis_ticks
244             {
245             my($self) = @_;
246             my($x_zero) = $$self{'_padding'}[3];
247             my($y_zero) = $$self{'_x_axis_ticks_option'} == 1 ? $$self{'_height'} - $$self{'_padding'}[2] : $$self{'_padding'}[0];
248             my($y_one) = $$self{'_height'} - $$self{'_padding'}[2] + $$self{'_tick_length'};
249              
250             my($x, $x_step);
251              
252             for $x (@{$$self{'_x_axis_data'} })
253             {
254             $x_step = $x_zero + ($$self{'_x_pixels_per_unit'} * $x);
255              
256             $$self{'_image'} -> Draw
257             (
258             primitive => 'line',
259             stroke => $$self{'_frame_color'},
260             points => sprintf
261             (
262             "%i,%i %i,%i",
263             $x_step, $y_zero,
264             $x_step, $y_one
265             ),
266             ) && Carp::croak("Can't draw X-axis ticks");
267             }
268              
269             } # End of draw_x_axis_ticks.
270              
271             # -----------------------------------------------
272              
273             sub draw_y_axis_labels
274             {
275             my($self) = @_;
276             my($y_zero) = $$self{'_height'} - $$self{'_padding'}[2] - 1;
277              
278             my($y, $offset, @metric);
279              
280             for $y (@{$$self{'_y_axis_labels'} })
281             {
282             @metric = $$self{'_image'} -> QueryFontMetrics(text => $y);
283             $offset = defined($$self{'_y_axis_labels_x'}) ? $$self{'_y_axis_labels_x'} : $$self{'_padding'}[3] - $$self{'_pointsize'} - $metric[4];
284             $y_zero -= $$self{'_y_pixels_per_unit'};
285              
286             $$self{'_image'} -> Annotate
287             (
288             font => $$self{'_font'},
289             text => $y,
290             stroke => $$self{'_frame_color'},
291             strokewidth => 1,
292             pointsize => $$self{'_pointsize'},
293             x => $offset,
294             y => $y_zero + int($metric[5] / 2) - 2,
295             ) && Carp::croak("Can't draw Y-axis labels");
296             }
297              
298             } # End of draw_y_axis_labels.
299              
300             # -----------------------------------------------
301              
302             sub draw_y_axis_ticks
303             {
304             my($self) = @_;
305             my($x_max) = $$self{'_x_pixels_per_unit'} * $$self{'_x_axis_data'}[$#{$$self{'_x_axis_data'} }];
306             my($x_zero) = $$self{'_y_axis_ticks_option'} == 1 ? $$self{'_padding'}[3] : $x_max + $$self{'_padding'}[3];
307             my($x_one) = $$self{'_padding'}[3] - $$self{'_tick_length'};
308             my($y_zero) = $$self{'_height'} - $$self{'_padding'}[2] - 1;
309              
310             my($i);
311              
312             # We use _x_data here and not _y_axis_* so that the number
313             # of ticks corresponds to the number of data points, and
314             # not to the number of y-axis labels. Remember: The user
315             # can - and should - have an empty string as the last
316             # label on the y-axis, to make the image pretty.
317              
318             for $i (0 .. $#{$$self{'_x_data'} })
319             {
320             $y_zero -= $$self{'_y_pixels_per_unit'};
321              
322             $$self{'_image'} -> Draw
323             (
324             primitive => 'line',
325             stroke => $$self{'_frame_color'},
326             points => sprintf
327             (
328             "%i,%i %i,%i",
329             $x_zero, $y_zero,
330             $x_one, $y_zero
331             ),
332             ) && Carp::croak("Can't draw Y-axis ticks");
333             }
334              
335             } # End of draw_y_axis_ticks.
336              
337             # -----------------------------------------------
338              
339             sub new
340             {
341             my($class, %arg) = @_;
342             my($self) = bless({}, $class);
343              
344             for my $attr_name ($self -> _standard_keys() )
345             {
346             my($arg_name) = $attr_name =~ /^_(.*)/;
347              
348             if (exists($arg{$arg_name}) )
349             {
350             $$self{$attr_name} = $arg{$arg_name};
351             }
352             else
353             {
354             $$self{$attr_name} = $self -> _default_for($attr_name);
355             }
356             }
357              
358             if ($$self{'_image'})
359             {
360             ($$self{'_width'}, $$self{'_height'}) = $$self{'_image'} -> Get('width', 'height');
361             }
362             else
363             {
364             $$self{'_width'} = $$self{'_padding'}[3] + 1 + ($$self{'_x_pixels_per_unit'} * $$self{'_x_axis_data'}[$#{$$self{'_x_axis_data'} }]) + $$self{'_padding'}[1];
365             $$self{'_height'} = $$self{'_padding'}[2] + 1 + ($$self{'_y_pixels_per_unit'} * $$self{'_y_axis_data'}[$#{$$self{'_y_axis_data'} }]) + $$self{'_padding'}[0];
366             $$self{'_image'} = Image::Magick -> new(size => "$$self{'_width'} x $$self{'_height'}");
367              
368             $$self{'_image'} -> Set(antialias => $$self{'_antialias'}) && Carp::croak("Can't set antialias: $$self{'_antialias'}");
369             $$self{'_image'} -> Set(colorspace => $$self{'_colorspace'}) && Carp::croak("Can't set colorspace: $$self{'_colorspace'}");
370             $$self{'_image'} -> Set(depth => $$self{'_depth'}) && Carp::croak("Can't set depth: $$self{'_depth'}");
371             $$self{'_image'} -> Read("xc:$$self{'_bg_color'}") && Carp::croak("Can't set bg_color color: $$self{'_bg_color'}");
372             }
373              
374             return $self;
375              
376             } # End of new.
377              
378             # -----------------------------------------------
379              
380             sub write
381             {
382             my($self) = @_;
383              
384             $$self{'_image'} -> Write($$self{'_output_file_name'}) && Carp::croak("Can't write file");
385              
386             } # End of write.
387              
388             # -----------------------------------------------
389              
390             1;
391              
392             __END__