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 1     1   12596 use strict;
  1         1  
  1         23  
4 1     1   3 use warnings;
  1         1  
  1         19  
5              
6 1     1   3 use Carp;
  1         3  
  1         54  
7              
8 1     1   832 use Image::Magick;
  0            
  0            
9              
10             use Moo;
11              
12             require 5.006002;
13              
14             our $VERSION = '1.07';
15              
16             use Types::Standard qw/Any ArrayRef Bool Int Str/;
17              
18             has antialias =>
19             (
20             default => sub{return 0},
21             is => 'rw',
22             isa => Bool,
23             required => 0,
24             );
25              
26             has bar_width =>
27             (
28             default => sub{return 8},
29             is => 'rw',
30             isa => Int,
31             required => 0,
32             );
33              
34             has bg_color =>
35             (
36             default => sub{return 'white'},
37             is => 'rw',
38             isa => Str,
39             required => 0,
40             );
41              
42             has colorspace =>
43             (
44             default => sub{return 'RGB'},
45             is => 'rw',
46             isa => Str,
47             required => 0,
48             );
49              
50             has depth =>
51             (
52             default => sub{return 8},
53             is => 'rw',
54             isa => Int,
55             required => 0,
56             );
57              
58             has fg_color =>
59             (
60             default => sub{return 'black'},
61             is => 'rw',
62             isa => Str,
63             required => 0,
64             );
65              
66             has font =>
67             (
68             default => sub{return 'Courier'},
69             is => 'rw',
70             isa => Str,
71             required => 0,
72             );
73              
74             has frame_color =>
75             (
76             default => sub{return 'black'},
77             is => 'rw',
78             isa => Str,
79             required => 0,
80             );
81              
82             has frame_option =>
83             (
84             default => sub{return 1},
85             is => 'rw',
86             isa => Bool,
87             required => 0,
88             );
89              
90             has height =>
91             (
92             default => sub{return 0},
93             is => 'rw',
94             isa => Int,
95             required => 0,
96             );
97              
98             has image =>
99             (
100             default => sub{return ''},
101             is => 'rw',
102             isa => Any,
103             required => 0,
104             );
105              
106             has output_file_name =>
107             (
108             default => sub{return ''},
109             is => 'rw',
110             isa => Str,
111             required => 1,
112             );
113              
114             has padding =>
115             (
116             default => sub{return [30, 30, 30, 30]}, # [12 noon, 3, 6, 9].
117             is => 'rw',
118             isa => ArrayRef,
119             required => 0,
120             );
121              
122             has pointsize =>
123             (
124             default => sub{return 14},
125             is => 'rw',
126             isa => Int,
127             required => 0,
128             );
129              
130             has tick_length =>
131             (
132             default => sub{return 4},
133             is => 'rw',
134             isa => Int,
135             required => 0,
136             );
137              
138             has title =>
139             (
140             default => sub{return ''},
141             is => 'rw',
142             isa => Str,
143             required => 0,
144             );
145              
146             has width =>
147             (
148             default => sub{return 0},
149             is => 'rw',
150             isa => Int,
151             required => 0,
152             );
153              
154             has x_axis_data =>
155             (
156             default => sub{return []},
157             is => 'rw',
158             isa => ArrayRef,
159             required => 1,
160             );
161              
162             has x_axis_labels =>
163             (
164             default => sub{return []},
165             is => 'rw',
166             isa => ArrayRef,
167             required => 1,
168             );
169              
170             has x_axis_labels_option =>
171             (
172             default => sub{return 0},
173             is => 'rw',
174             isa => Bool,
175             required => 0,
176             );
177              
178             has x_axis_ticks_option =>
179             (
180             default => sub{return 0},
181             is => 'rw',
182             isa => Int, # Sic.
183             required => 0,
184             );
185              
186             has x_data =>
187             (
188             default => sub{return []},
189             is => 'rw',
190             isa => ArrayRef,
191             required => 0,
192             );
193              
194             has x_data_option =>
195             (
196             default => sub{return 1},
197             is => 'rw',
198             isa => Int,
199             required => 0,
200             );
201              
202             has x_pixels_per_unit =>
203             (
204             default => sub{return 3},
205             is => 'rw',
206             isa => Int,
207             required => 0,
208             );
209              
210             has y_axis_data =>
211             (
212             default => sub{return []},
213             is => 'rw',
214             isa => ArrayRef,
215             required => 1,
216             );
217              
218             has y_axis_labels =>
219             (
220             default => sub{return []},
221             is => 'rw',
222             isa => ArrayRef,
223             required => 1,
224             );
225              
226             has y_axis_labels_option =>
227             (
228             default => sub{return 0},
229             is => 'rw',
230             isa => Bool,
231             required => 0,
232             );
233              
234             has y_axis_labels_x =>
235             (
236             default => sub{return undef},
237             is => 'rw',
238             isa => Any,
239             required => 0,
240             );
241              
242             has y_axis_ticks_option =>
243             (
244             default => sub{return 1},
245             is => 'rw',
246             isa => Int, # Sic.
247             required => 0,
248             );
249              
250             has y_pixels_per_unit =>
251             (
252             default => sub{return 20},
253             is => 'rw',
254             isa => Int,
255             required => 0,
256             );
257              
258             # -----------------------------------------------
259              
260             sub BUILD
261             {
262             my($self) = @_;
263              
264             if ($self -> image)
265             {
266             ($self -> width, $self -> height) = $self -> image -> Get('width', 'height');
267             }
268             else
269             {
270             $self -> width(${$self -> padding}[3] + 1 + ($self -> x_pixels_per_unit * ${$self -> x_axis_data}[$#{$self -> x_axis_data}]) + ${$self -> padding}[1]);
271             $self -> height(${$self -> padding}[2] + 1 + ($self -> y_pixels_per_unit * ${$self -> y_axis_data}[$#{$self -> y_axis_data}]) + ${$self -> padding}[0]);
272             $self -> image(Image::Magick -> new(size => "$self -> width x $self -> height") );
273              
274             $self -> image -> Set(antialias => $self -> antialias) && Carp::croak("Can't set antialias: $self -> antialias");
275             $self -> image -> Set(colorspace => $self -> colorspace) && Carp::croak("Can't set colorspace: $self -> colorspace");
276             $self -> image -> Set(depth => $self -> depth) && Carp::croak("Can't set depth: $self -> depth");
277             $self -> image -> Read('xc:' . $self -> bg_color) && Carp::croak("Can't set bg_color color: $self -> bg_color");
278             }
279              
280             } # End of BUILD.
281              
282             # -----------------------------------------------
283              
284             sub draw_frame
285             {
286             my($self) = @_;
287             my($x_max) = $self -> x_pixels_per_unit * ${$self -> x_axis_data}[$#{$self -> x_axis_data}];
288              
289             $self -> image -> Draw
290             (
291             fill => 'none',
292             primitive => 'polyline',
293             stroke => $self -> frame_color,
294             points => sprintf
295             (
296             "%i,%i %i,%i %i,%i %i,%i %i,%i",
297             ${$self -> padding}[3], ${$self -> padding}[0],
298             ${$self -> padding}[3] + $x_max, ${$self -> padding}[0],
299             ${$self -> padding}[3] + $x_max, ($self -> height - ${$self -> padding}[2] - 1),
300             ${$self -> padding}[3], ($self -> height - ${$self -> padding}[2] - 1),
301             ${$self -> padding}[3], ${$self -> padding}[0]
302             ),
303             ) && Carp::croak("Can't draw frame");
304              
305             } # End of draw_frame.
306              
307             # -----------------------------------------------
308              
309             sub draw_horizontal_bars
310             {
311             my($self) = @_;
312             my($half_bar_width) = int($self -> bar_width / 2);
313             my($y_zero) = $self -> height - ${$self -> padding}[2] - 1;
314              
315             my($i, $data, @metric, $x_right, $y_top);
316              
317             for $i (0 .. $#{$self -> x_data})
318             {
319             $data = ${$self -> x_data}[$i];
320             $x_right = ${$self -> padding}[3] + ($self -> x_pixels_per_unit * $data);
321             $y_top = $y_zero - ($self -> y_pixels_per_unit * ${$self -> y_axis_data}[$i]);
322              
323             $self -> image -> Draw
324             (
325             fill => $self -> fg_color,
326             primitive => 'polyline',
327             method => 'floodfill',
328             stroke => $self -> fg_color,
329             points => sprintf
330             (
331             "%i,%i %i,%i %i,%i %i,%i",
332             ${$self -> padding}[3], $y_top - $half_bar_width,
333             $x_right, $y_top - $half_bar_width,
334             $x_right, $y_top + $half_bar_width,
335             ${$self -> padding}[3], $y_top + $half_bar_width,
336             ),
337             ) && Carp::croak("Can't draw horizontal bars");
338              
339             next if ($self -> x_data_option == 0);
340              
341             @metric = $self -> image -> QueryFontMetrics(text => $data);
342              
343             $self -> image -> Annotate
344             (
345             font => $self -> font,
346             text => $data,
347             stroke => 'black',
348             strokewidth => 1,
349             pointsize => $self -> pointsize,
350             x => $x_right + $self -> tick_length,
351             y => $y_top + int($metric[5] / 2) - 2,
352             ) && Carp::croak("Can't draw horizontal bars");
353             }
354              
355             } # End of draw_horizontal_bars.
356              
357             # -----------------------------------------------
358              
359             sub draw_title
360             {
361             my($self) = @_;
362              
363             $self -> image -> Annotate
364             (
365             font => $self -> font,
366             text => $self -> title,
367             stroke => 'black',
368             strokewidth => 1,
369             pointsize => $self -> pointsize,
370             x => int( ($self -> width - int(int($self -> pointsize / 2) * length($self -> title) ) ) / 2),
371             y => int(${$self -> padding}[0] / 2) + 2,
372             ) && Carp::croak("Can't draw title");
373              
374             } # End of draw_title.
375              
376             # -----------------------------------------------
377              
378             sub draw_x_axis_labels
379             {
380             my($self) = @_;
381             my($x_zero) = ${$self -> padding}[3];
382              
383             my($i, $text, $x_step, @metric);
384              
385             for $i (0 .. $#{$self -> x_axis_labels})
386             {
387             $text = ${$self -> x_axis_labels}[$i];
388             $x_step = $x_zero + ($self -> x_pixels_per_unit * ${$self -> x_axis_data}[$i]);
389             @metric = $self -> image -> QueryFontMetrics(text => $text);
390              
391             $self -> image -> Annotate
392             (
393             font => $self -> font,
394             text => $text,
395             stroke => $self -> frame_color,
396             strokewidth => 1,
397             pointsize => $self -> pointsize,
398             x => $x_step - int($metric[4] / 2) - 1,
399             y => $self -> height - $self -> pointsize,
400             ) && Carp::croak("Can't draw X-axis labels");
401             }
402              
403             } # End of draw_x_axis_labels.
404              
405             # -----------------------------------------------
406              
407             sub draw_x_axis_ticks
408             {
409             my($self) = @_;
410             my($x_zero) = ${$self -> padding}[3];
411             my($y_zero) = $self -> x_axis_ticks_option == 1 ? $self -> height - ${$self -> padding}[2] : ${$self -> padding}[0];
412             my($y_one) = $self -> height - ${$self -> padding}[2] + $self -> tick_length;
413              
414             my($x, $x_step);
415              
416             for $x (@{$self -> x_axis_data})
417             {
418             $x_step = $x_zero + ($self -> x_pixels_per_unit * $x);
419              
420             $self -> image -> Draw
421             (
422             primitive => 'line',
423             stroke => $self -> frame_color,
424             points => sprintf
425             (
426             "%i,%i %i,%i",
427             $x_step, $y_zero,
428             $x_step, $y_one
429             ),
430             ) && Carp::croak("Can't draw X-axis ticks");
431             }
432              
433             } # End of draw_x_axis_ticks.
434              
435             # -----------------------------------------------
436              
437             sub draw_y_axis_labels
438             {
439             my($self) = @_;
440             my($y_zero) = $self -> height - ${$self -> padding}[2] - 1;
441              
442             my($y, $offset, @metric);
443              
444             for $y (@{$self -> y_axis_labels})
445             {
446             @metric = $self -> image -> QueryFontMetrics(text => $y);
447             $offset = defined($self -> y_axis_labels_x) ? $self -> y_axis_labels_x : ${$self -> padding}[3] - $self -> pointsize - $metric[4];
448             $y_zero -= $self -> y_pixels_per_unit;
449              
450             $self -> image -> Annotate
451             (
452             font => $self -> font,
453             text => $y,
454             stroke => $self -> frame_color,
455             strokewidth => 1,
456             pointsize => $self -> pointsize,
457             x => $offset,
458             y => $y_zero + int($metric[5] / 2) - 2,
459             ) && Carp::croak("Can't draw Y-axis labels");
460             }
461              
462             } # End of draw_y_axis_labels.
463              
464             # -----------------------------------------------
465              
466             sub draw_y_axis_ticks
467             {
468             my($self) = @_;
469             my($x_max) = $self -> x_pixels_per_unit * ${$self -> x_axis_data}[$#{$self -> x_axis_data}];
470             my($x_zero) = $self -> y_axis_ticks_option == 1 ? ${$self -> padding}[3] : $x_max + ${$self -> padding}[3];
471             my($x_one) = ${$self -> padding}[3] - $self -> tick_length;
472             my($y_zero) = $self -> height - ${$self -> padding}[2] - 1;
473              
474             my($i);
475              
476             # We use _x_data here and not _y_axis_* so that the number
477             # of ticks corresponds to the number of data points, and
478             # not to the number of y-axis labels. Remember: The user
479             # can - and should - have an empty string as the last
480             # label on the y-axis, to make the image pretty.
481              
482             for $i (0 .. $#{$self -> x_data})
483             {
484             $y_zero -= $self -> y_pixels_per_unit;
485              
486             $self -> image -> Draw
487             (
488             primitive => 'line',
489             stroke => $self -> frame_color,
490             points => sprintf
491             (
492             "%i,%i %i,%i",
493             $x_zero, $y_zero,
494             $x_one, $y_zero
495             ),
496             ) && Carp::croak("Can't draw Y-axis ticks");
497             }
498              
499             } # End of draw_y_axis_ticks.
500              
501             # -----------------------------------------------
502              
503             sub write
504             {
505             my($self) = @_;
506              
507             $self -> image -> Write($self -> output_file_name) && Carp::croak("Can't write file");
508              
509             } # End of write.
510              
511             # -----------------------------------------------
512              
513             1;
514              
515             __END__