File Coverage

blib/lib/Imager/Chart/Radial.pm
Criterion Covered Total %
statement 9 183 4.9
branch 0 42 0.0
condition 0 6 0.0
subroutine 3 9 33.3
pod 6 6 100.0
total 18 246 7.3


line stmt bran cond sub pod time code
1             package Radial;
2              
3 1     1   8453 use strict;
  1         2  
  1         43  
4 1     1   6 use vars qw($VERSION);
  1         2  
  1         55  
5              
6 1     1   1569 use Imager qw(init);
  1         48518  
  1         7  
7              
8             $VERSION = 0.1;
9              
10             my %colours = (
11             black => Imager::Color->new("#000000"),
12             white => Imager::Color->new("#ffffff"),
13             grey => Imager::Color->new("#9f9f9f"),
14             blue => Imager::Color->new("#0707cc"),
15             red => Imager::Color->new("#cc0707"),
16             green => Imager::Color->new("#07cc07"),
17             darkblue => Imager::Color->new("#0202bb"),
18             darkred => Imager::Color->new("#bb0202"),
19             green => Imager::Color->new("#02bb02"),
20             );
21              
22             #-------------------------------------------------------------------------------
23             # set up new radial chart object
24              
25             sub new {
26 0     0 1   my $class = shift;
27 0           my %arguments = @_;
28              
29             # instantiate Chart
30 0           my $Chart = {};
31 0   0       bless($Chart, ref($class) || $class);
32              
33             # initialise Chart
34 0           $Chart->{_debug} = 1;
35 0           $Chart->{PI} = 4 * atan2 1, 1;
36 0           $Chart->{scale} = { style=>'notch', Max=>15, Divisions=>5 };
37              
38             # initialise image
39 0           init();
40 0           $Chart->{_image} = Imager->new(xsize=>400,ysize=>500,);
41 0           $Chart->{_image}->box(color=>$colours{white},xmin=>0,ymin=>0,xmax=>400,ymax=>500,filled=>1);
42              
43             # use arguments if provided
44 0           foreach my $arg (qw(axis colours fonts filename)) {
45 0           $Chart->{$arg} = $arguments{$arg};
46             }
47              
48 0           return $Chart;
49             }
50              
51             sub plot_axis {
52 0     0 1   my $self=shift;
53 0           my $i = 0;
54 0           my ($x_centre, $y_centre) = ( 200, 300 );
55 0           foreach my $axis (@{$self->{axis}}) {
  0            
56 0           my $proportion;
57             my $theta;
58 0           my $x;
59 0           my $y;
60 0 0         if ($i > 0) {
61 0           $proportion = $i / scalar (@{$self->{axis}});
  0            
62 0           $theta = (360 * $proportion) + 2;
63 0           $axis->{theta} = $theta;
64 0           $theta *= ((2 * $self->{PI}) / 360);
65 0           $x = cos $theta - (2 * $theta);
66 0           $y = sin $theta - (2 * $theta);
67             } else {
68 0           $x = 1;
69 0           $y = 0;
70 0           $axis->{theta} = 0;
71             }
72 0           my $x_outer = ($x * 100) + $x_centre;
73 0 0         my $x_proportion = ($x >= 0) ? $x : $x - (2 * $x) ;
74 0 0         my $x_label = ($x_outer >= $x_centre) ?
75             $x_outer + (0.9 * (14 * $x_proportion)) : $x_outer - ((length ( $axis->{Label} ) * 5) + (3 * $x_proportion));
76 0           my $y_outer = ($y * 100) + $y_centre;
77 0 0         my $y_proportion = ($y >= 0) ? $y : $y - (2 * $y) ;
78 0 0         my $y_label = ($y_outer >= $y_centre) ? $y_outer + 1.5 + (2 * $y_proportion) : $y_outer - (9 * $y_proportion);
79              
80 0           $axis->{X} = $x;
81 0           $axis->{Y} = $y;
82              
83             # round down coords
84 0           $x_outer =~ s/(\d+)\..*/$1/;
85 0           $y_outer =~ s/(\d+)\..*/$1/;
86 0           $x_label =~ s/(\d+)\..*/$1/;
87 0           $y_label =~ s/(\d+)\..*/$1/;
88              
89 0           warn "drawing axis ..\n";
90             # draw axis
91 0           $self->{_image}->line(color=>$colours{black}, x1=>$x_outer,x2=>$x_centre,
92             y1=>$y_outer,y2=>$y_centre,antialias=>1);
93              
94             # add label for axis
95 0           $self->{_image}->string( font => $self->{fonts}{label}, text => $axis->{Label},
96             x => $x_label, y => $y_label, size => 10 , color => $colours{darkblue},
97             aa => 1);
98 0           $i++;
99             }
100              
101             # loop through adding scale, and values
102              
103 0           my $r = 0;
104 0           $i = 0;
105 0           my %scale = %{$self->{scale}};
  0            
106 0           foreach my $axis (@{$self->{axis}}) {
  0            
107 0           my $x = $axis->{X};
108 0           my $y = $axis->{Y};
109             # draw scale
110 0           my $theta1;
111             my $theta2;
112 0 0         if ($self->{scale}{style} eq "notch") {
113 0           $theta1 = $axis->{theta} + 90;
114 0           $theta2 = $axis->{theta} - 90;
115             # convert theta to radians
116 0           $theta1 *= ((2 * $self->{PI}) / 360);
117 0           $theta2 *= ((2 * $self->{PI}) / 360);
118 0           for (my $j = 0 ; $j <= $scale{Max} ; $j+= int($scale{Max} / $scale{Divisions})) {
119 0 0         next if ($j == 0);
120 0           my $x_interval = $x_centre + ($x * (100 / $scale{Max}) * $j);
121 0           my $y_interval = $y_centre + ($y * (100 / $scale{Max}) * $j);
122 0           my $x1 = cos $theta1 - (2 *$theta1);
123 0           my $y1 = sin $theta1 - (2 * $theta1);
124 0           my $x2 = cos $theta2 - (2 *$theta2);
125 0           my $y2 = sin $theta2 - (2 * $theta2);
126 0           my $x1_outer = ($x1 * 3 * ($j / $scale{Max})) + $x_interval;
127 0           my $y1_outer = ($y1 * 3 * ($j / $scale{Max})) + $y_interval;
128 0           my $x2_outer = ($x2 * 3 * ($j / $scale{Max})) + $x_interval;
129 0           my $y2_outer = ($y2 * 3 * ($j / $scale{Max})) + $y_interval;
130 0           $self->{_image}->line(color=>$colours{grey}, x1=>$x1_outer,x2=>$x_interval,
131             y1=>$y1_outer,y2=>$y_interval,antialias=>1);
132 0           $self->{_image}->line(color=>$colours{grey}, x1=>$x2_outer,x2=>$x_interval,
133             y1=>$y2_outer,y2=>$y_interval,antialias=>1);
134              
135             # Add Numbers to scale
136 0 0         if ($i == 0) {
137 0           $self->{_image}->string( font => $self->{fonts}{text}, text => $j,
138             x => $x_interval , y => $y_interval , size => 10,
139             color => $colours{grey}, aa => 1);
140             }
141             }
142             }
143 0 0         if ($scale{style} eq "Polygon") {
144 0           for (my $j = 0 ; $j <= $scale{Max} ; $j+=($scale{Max} / $scale{Divisions})) {
145 0 0         next if ($j == 0);
146 0           my $x_interval_1 = $x_centre + ($x * (100 / $scale{Max}) * $j);
147 0           my $y_interval_1= $y_centre + ($y * (100 / $scale{Max}) * $j);
148 0           my $x_interval_2 = $x_centre + ($self->{axis}[$i-1]->{X} * (100 / $scale{Max}) * $j);
149 0           my $y_interval_2= $y_centre + ($self->{axis}[$i-1]->{Y} * (100 / $scale{Max}) * $j);
150             # Add Numbers to scale
151 0 0         if ($i == 0) {
152 0           $self->{_image}->string( font => $self->{fonts}{text}, text => $j,
153             x => $x_interval_1 + 2, y => $y_interval_1 - 11, size => 10,
154             color => $colours{grey}, aa => 1);
155             } else {
156 0           $self->{_image}->line(color=>$colours{grey}, x1=>$x_interval_1,x2=>$x_interval_2,
157             y1=>$y_interval_1,y2=>$y_interval_2,antialias=>1);
158              
159 0 0         if ($i == scalar @{$self->{axis}} -1) {
  0            
160 0           my $x_interval_2 = $x_centre + ($self->{axis}[0]->{X} * (100 / $scale{Max}) * $j);
161 0           my $y_interval_2= $y_centre + ($self->{axis}[0]->{Y} * (100 / $scale{Max}) * $j);
162 0           $self->{_image}->line(color=>$colours{grey}, x1=>$x_interval_1,x2=>$x_interval_2,
163             y1=>$y_interval_1,y2=>$y_interval_2,antialias=>1);
164             }
165             }
166             }
167             }
168 0           $i++;
169             }
170             }
171              
172             sub plot_values {
173 0     0 1   my $self = shift;
174 0           $self->{records} = shift;
175 0           my $i = 0;
176 0           my ($x_centre, $y_centre) = ( 200, 300 );
177 0           my $r = 0;
178 0           my %scale = %{$self->{scale}};
  0            
179 0           foreach my $axis (@{$self->{axis}}) {
  0            
180 0           my $proportion;
181             my $theta;
182 0           my $x;
183 0           my $y;
184 0 0         if ($i > 0) {
185 0           $proportion = $i / scalar (@{$self->{axis}});
  0            
186 0           $theta = (360 * $proportion) + 2;
187 0           $axis->{theta} = $theta;
188 0           $theta *= ((2 * $self->{PI}) / 360);
189 0           $x = cos $theta - (2 * $theta);
190 0           $y = sin $theta - (2 * $theta);
191             } else {
192 0           $axis->{theta} = 0;
193 0           $theta = $axis->{theta};
194 0           $x = 1;
195 0           $y = 0;
196             }
197 0           my $x_outer = ($x * 100) + $x_centre;
198 0 0         my $x_proportion = ($x >= 0) ? $x : $x - (2 * $x) ;
199 0 0         my $x_label = ($x_outer >= $x_centre) ?
200             $x_outer + 3 : $x_outer - ((length ( $axis->{Label} ) * 5) + (3 * $x_proportion));
201 0           my $y_outer = ($y * 100) + $y_centre;
202 0 0         my $y_proportion = ($y >= 0) ? $y : $y - (2 * $y) ;
203 0 0         my $y_label = ($y_outer >= $y_centre) ? $y_outer + (3 * $y_proportion) : $y_outer - (9 * $y_proportion);
204              
205 0           $axis->{X} = $x;
206 0           $axis->{Y} = $y;
207              
208             # round down coords
209 0           $x_outer =~ s/(\d+)\..*/$1/;
210 0           $y_outer =~ s/(\d+)\..*/$1/;
211 0           $x_label =~ s/(\d+)\..*/$1/;
212 0           $y_label =~ s/(\d+)\..*/$1/;
213             # draw value
214 0 0         if ($i != 0) {
215 0           my $r = 0;
216 0           foreach my $record (@{$self->{records}}) {
  0            
217 0           my $value = $record->{Values}->{$axis->{Label}};
218 0           my $last_value = $record->{Values}->{$self->{axis}[$i-1]->{Label}};
219 0           my $colour = $colours{$record->{Colour}};
220 0           my $x_interval_1 = $x_centre + ($x * (100 / $scale{Max}) * $value);
221 0           my $y_interval_1= $y_centre + ($y * (100 / $scale{Max}) * $value);
222 0           my $shape = $record->{Shape};
223             # $self->draw_shape($x_interval_1,$y_interval_1,$record->{Colour}, $r);
224 0           my $x_interval_2 = $x_centre + ($self->{axis}[$i-1]->{X} * (100 / $scale{Max}) * $last_value);
225 0           my $y_interval_2= $y_centre + ($self->{axis}[$i-1]->{Y} * (100 / $scale{Max}) * $last_value);
226 0           $self->{_image}->line(color=>$colour, x1=>$x_interval_1,x2=>$x_interval_2,
227             y1=>$y_interval_1,y2=>$y_interval_2,antialias=>1);
228              
229             # $self->{_im}->line($x_interval_1,$y_interval_1,$x_interval_2,$y_interval_2,$colour);
230 0 0         if ($i == scalar @{$self->{axis}} -1) {
  0            
231 0           my $first_value = $record->{Values}->{$self->{axis}[0]->{Label}};
232 0           my $x_interval_2 = $x_centre + ($self->{axis}[0]->{X} * (100 / $scale{Max}) * $first_value);
233 0           my $y_interval_2= $y_centre + ($self->{axis}[0]->{Y} * (100 / $scale{Max}) * $first_value);
234 0           $self->{_image}->line(color=>$colour, x1=>$x_interval_1,x2=>$x_interval_2,
235             y1=>$y_interval_1,y2=>$y_interval_2,antialias=>1);
236             # $self->{_im}->line($x_interval_1,$y_interval_1,$x_interval_2,$y_interval_2,$colour);
237             # $self->draw_shape($x_interval_2,$y_interval_2,$record->{Colour}, $r);
238             }
239 0           $r++;
240             }
241             }
242 0           $i++;
243             }
244 0           return;
245             }
246              
247             sub add_title {
248 0     0 1   my ($self,$title) = @_;
249 0 0         if (length $title > 30) {
250 0           $_ = $title;
251 0           my ($part_a,$part_b) = m/^(.{25,35})\s+(.*)$/;
252 0           $self->{_image}->string( font => $self->{fonts}{header}, text => $part_a,
253             x => 45, y => 50, color => $colours{black}, aa => 1);
254 0           $self->{_image}->string( font => $self->{fonts}{header}, text => $part_b,
255             x => 45, y => 75, color => $colours{black}, aa => 1);
256             } else {
257 0           $self->{_image}->string( font => $self->{fonts}{header}, text => $title,
258             x => 50, y => 50, color => $colours{black}, aa => 1);
259             }
260 0           return;
261             }
262              
263              
264             sub add_legend {
265 0     0 1   my $Chart = shift;
266 0           my $starty = 490;
267 0           my $endy = 470 - (scalar @{$Chart->{records}} * 18);
  0            
268 0           $Chart->{_image}->box( color=>$colours{black},xmin=>45,ymin=>$endy,
269             xmax=>250,ymax=>$starty+2,filled=>0);
270 0           $Chart->{_image}->string( font => $Chart->{fonts}{label}, text => "Legend :",
271             x => 50, y => $endy+19, color => $colours{black}, aa => 1);
272 0           foreach my $record (@{$Chart->{records}}) {
  0            
273 0           $Chart->{_image}->string( font => $Chart->{fonts}{label},
274             text => "$record->{Label} : $record->{Colour}",
275             x => 50, y => $starty, color => $colours{$record->{Colour}}, aa => 1);
276 0           $starty-=18;
277             }
278             }
279              
280             sub print {
281 0     0 1   my $Chart = shift;
282 0   0       my $filename = shift || $Chart->{filename};
283 0 0         $Chart->{_image}->write(file=>$filename)
284             || warn "error: couldn't print chart ",$Chart->{_image}->{ERRSTR},"\n";
285 0           return;
286             }
287              
288              
289             ###########################################################################################
290              
291             1;
292              
293              
294             ###########################################################################################
295              
296             =head1 NAME
297              
298             Imager::Chart::Radial
299              
300             =head1 SYNOPSIS
301              
302             =item use Imager::Chart::Radial;
303              
304             =item my $chart = Radial->new(axis => \@axis, fonts => \%fonts);
305              
306             =item $chart->plot_axis();
307              
308             =item $chart->plot_values( \@records );>
309              
310             =item $chart->add_title("This is a chart, there are many like it but this is mine");>
311              
312             =item $chart->add_legend();>
313              
314             =item $chart->print('mychart.png');
315              
316             =head1 DESCRIPTION
317              
318             This module uses Imager to plot and output Radial or Radar charts.
319              
320             =head1 ABOUT
321              
322             I originally wrote a radial chart creator based on GD, but the GD library did not provide anti-aliasing and sufficient colours for a clean looking image until relatively recently. I wrote this version because I wanted to learn Imager and also provide some charting modules for Imager to make life easier when the GD library is not available.
323              
324             =head1 USING
325              
326             =head2 Creating a class
327              
328             To create a new Radial object use the new method on the class
329              
330             my $chart = Radial->new(axis => \@axis, fonts => \%fonts);
331              
332             This requires two data structures, one for your axis and one for the fonts you wish to use:
333              
334             my %fonts = (
335             text => Imager::Font->new(file => '/path/to/fonts/cour.ttf', size => 8),
336             header => Imager::Font->new(file => '/path/to/fonts/arial.ttf', size => 18),
337             label => Imager::Font->new(file => '/path/to/fonts/arial.ttf', size => 14),
338             );
339              
340             Fonts must be TrueType compatible fonts, for more information see Imager::Font.
341              
342             my @axis = (
343             { Label => "Reliability" },
344             { Label => "Ease of Use" },
345             { Label => "Information" },
346             { Label => "Layout" },
347             { Label => "Navigation" },
348             { Label => "Searching" },
349             );
350              
351             The axis are labelled as above and provide the skeleton of the graph
352              
353             =head2 Plotting the graph
354              
355             $chart->plot_axis();
356              
357             This plots the axis onto the chart
358              
359             $chart->plot_values( \@records );
360              
361             This plots the values themselves onto the chart using the records data structure as below :
362              
363             my @records = (
364             { Label => "Foo", Colour => "red", Values => {
365             "Reliability"=>5,"Ease of Use"=>3, "Response Speed"=>6,"Information"=>4,
366             "Layout"=>3,"Navigation"=>6,"Organisation"=>7,"Searching"=>8, },
367             },
368             { Label => "Bar", Colour => "blue", Values => {
369             "Reliability"=>9,"Ease of Use"=>8,"Response Speed"=>4,"Information"=>5,
370             "Layout"=>8,"Navigation"=>8,"Organisation"=>8,"Searching"=>7,
371             },
372             },
373             { Label => "Baz", Colour => "green", Values => {
374             "Reliability"=>7,"Ease of Use"=>2,"Response Speed"=>9,"Information"=>8,
375             "Layout"=>3,"Navigation"=>4,"Organisation"=>6,"Searching"=>3,
376             },
377             },
378             );
379              
380              
381             =head2 Labelling the graph
382              
383             You can add a title and a legend to the chart using add_title and add_legend
384              
385             $chart->add_title("This is a radial chart using Imager and my own values");
386              
387             The title should be short and uses the font specified as header in the fonts hash.
388              
389             $chart->add_legend();
390              
391             The legend is generated from the records, you must therefore plot the graph before adding the legend. The legend uses the label font.
392              
393             =head2 Outputing the graph
394              
395             To write out the graph just call the print method with the filename you wish to write to.
396              
397             $chart->print('newchart.png');
398              
399             =head1 SEE ALSO
400              
401             Imager
402              
403             Imager::Font
404              
405             GD
406              
407             GD::Graph
408              
409             =head1 AUTHOR
410              
411             Aaron J Trevena EFE
412              
413             =head1 COPYRIGHT
414              
415             Copyright (C) 2003, Aaron Trevena
416              
417             This module is free software; you can redistribute it or modify it
418             under the same terms as Perl itself.
419              
420              
421             =cut
422              
423             ###########################################################################################
424             ###########################################################################################
425