File Coverage

blib/lib/GD/Graph/Cartesian.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 GD::Graph::Cartesian;
2 3     3   268955 use strict;
  3         6  
  3         113  
3 3     3   17 use warnings;
  3         9  
  3         90  
4 3     3   17 use base qw{Package::New};
  3         10  
  3         2556  
5 3     3   2025 use GD qw{gdSmallFont};
  0            
  0            
6             use List::MoreUtils qw{minmax};
7             use List::Util qw{first};
8              
9             our $VERSION = '0.11';
10              
11             =head1 NAME
12              
13             GD::Graph::Cartesian - Make Cartesian Graphs with GD Package
14              
15             =head1 SYNOPSIS
16              
17             use GD::Graph::Cartesian;
18             my $obj=GD::Graph::Cartesian->new(height=>400, width=>800);
19             $obj->addPoint(50=>25);
20             $obj->addLine($x0=>$y0, $x1=>$y1);
21             $obj->addRectangle($x0=>$y0, $x1=>$y1);
22             $obj->addString($x=>$y, 'Hello World!');
23             $obj->addLabel($pxx=>$pxy, 'Title'); #for labels on image not on chart
24             $obj->font(gdSmallFont); #sets the current font from GD exports
25             $obj->color('blue'); #sets the current color from Graphics::ColorNames
26             $obj->color([0,0,0]); #sets the current color [red,green,blue]
27             print $obj->draw;
28              
29             =head1 DESCRIPTION
30              
31             This is a wrapper around L to place points and lines on a X/Y scatter plot.
32              
33             =head1 CONSTRUCTOR
34              
35             =head2 new
36              
37             The new() constructor.
38              
39             my $obj = GD::Graph::Cartesian->new( #default values
40             width=>640, #width in pixels
41             height=>480, #height in pixels
42             ticksx=>10, #number of major ticks
43             ticksy=>10, #number of major ticks
44             borderx=>2, #pixel border left and right
45             bordery=>2, #pixel border top and bottom
46             rgbfile=>'/usr/X11R6/lib/X11/rgb.txt'
47             minx=>{auto}, #data minx
48             miny=>{auto}, #data miny
49             maxx=>{auto}, #data maxx
50             maxy=>{auto}, #data maxy
51             points=>[[$x,$y,$color],...], #addPoint method
52             lines=>[[$x0=>$y0,$x1=>$y1,$color],...] #addLine method
53             strings=>[[$x0=>$y0,'String',$color],...] #addString method
54             );
55              
56             =head1 METHODS
57              
58             =head2 addPoint
59              
60             Method to add a point to the graph.
61              
62             $obj->addPoint(50=>25);
63             $obj->addPoint(50=>25, [$r,$g,$b]);
64             $obj->addPoint(50=>25, [$r,$g,$b], $size); #size default iconsize 7
65             $obj->addPoint(50=>25, [$r,$g,$b], $size, $fill); #fill 0|1
66              
67             =cut
68              
69             sub addPoint {
70             my $self = shift;
71             my $x = shift;
72             my $y = shift;
73             my $c = shift || $self->color;
74             my $s = shift || $self->iconsize;
75             my $f = shift || 0;
76             my $p = $self->points;
77             push @$p, [$x=>$y, $c, $s, $f];
78             return scalar(@$p);
79             }
80              
81             =head2 addLine
82              
83             Method to add a line to the graph.
84              
85             $obj->addLine(50=>25, 75=>35);
86             $obj->addLine(50=>25, 75=>35, [$r,$g,$b]);
87              
88             =cut
89              
90             sub addLine {
91             my $self = shift;
92             my $x0 = shift;
93             my $y0 = shift;
94             my $x1 = shift;
95             my $y1 = shift;
96             my $c = shift || $self->color;
97             my $l = $self->lines;
98             push @$l, [$x0=>$y0, $x1=>$y1, $c];
99             return scalar(@$l);
100             }
101              
102             =head2 addString
103              
104             Method to add a string to the graph.
105              
106             $obj->addString(50=>25, 'String');
107             $obj->addString(50=>25, 'String', [$r,$g,$b]);
108             $obj->addString(50=>25, 'String', [$r,$g,$b], $font); #$font is a gdfont
109              
110             =cut
111              
112             sub addString {
113             my $self = shift;
114             my $x = shift;
115             my $y = shift;
116             my $s = shift;
117             my $c = shift || $self->color;
118             my $f = shift || $self->font;
119             my $a = $self->strings;
120             push @$a, [$x=>$y, $s, $c, $f];
121             return scalar(@$a);
122             }
123              
124             =head2 addLabel
125              
126             Method to add a label to the image (not the graph).
127              
128             $obj->addLabel(50=>25, 'Label'); #x/y pixels of the image NOT units of the chart
129             $obj->addLabel(50=>25, 'Label', [$r,$g,$b]);
130             $obj->addLabel(50=>25, 'Label', [$r,$g,$b], $font); #$font is a gdfont
131              
132             =cut
133              
134             sub addLabel {
135             my $self = shift;
136             my $x = shift;
137             my $y = shift;
138             my $s = shift;
139             my $c = shift || $self->color;
140             my $f = shift || $self->font;
141             my $a = $self->labels;
142             push @$a, [$x=>$y, $s, $c, $f];
143             return scalar(@$a);
144             }
145              
146             =head2 addRectangle
147              
148             $obj->addRectangle(50=>25, 75=>35);
149             $obj->addRectangle(50=>25, 75=>35, [$r,$g,$b]);
150              
151             =cut
152              
153             sub addRectangle {
154             my $self = shift;
155             my $x0 = shift;
156             my $y0 = shift;
157             my $x1 = shift;
158             my $y1 = shift;
159             my $c = shift || $self->color;
160             $self->addLine($x0=>$y0, $x0=>$y1, $c);
161             $self->addLine($x0=>$y1, $x1=>$y1, $c);
162             $self->addLine($x1=>$y1, $x1=>$y0, $c);
163             return $self->addLine($x1=>$y0, $x0=>$y0, $c);
164             }
165              
166             =head2 points
167              
168             Returns the points array reference.
169              
170             =cut
171              
172             sub points {
173             my $self=shift;
174             $self->{'points'}=[]
175             unless ref($self->{'points'}) eq "ARRAY";
176             return $self->{'points'};
177             }
178              
179             =head2 lines
180              
181             Returns the lines array reference.
182              
183             =cut
184              
185             sub lines {
186             my $self=shift;
187             $self->{'lines'}=[]
188             unless ref($self->{'lines'}) eq 'ARRAY';
189             return $self->{'lines'};
190             }
191              
192             =head2 strings
193              
194             Returns the strings array reference.
195              
196             =cut
197              
198             sub strings {
199             my $self=shift;
200             $self->{'strings'}=[]
201             unless ref($self->{'strings'}) eq 'ARRAY';
202             return $self->{'strings'};
203             }
204              
205             =head2 labels
206              
207             Returns the labels array reference.
208              
209             =cut
210              
211             sub labels {
212             my $self=shift;
213             $self->{'labels'}=[]
214             unless ref($self->{'labels'}) eq 'ARRAY';
215             return $self->{'labels'};
216             }
217              
218              
219             =head2 color
220              
221             Method to set or return the current drawing color
222              
223             my $colorobj=$obj->color('blue'); #if Graphics::ColorNames available
224             my $colorobj=$obj->color([77,82,68]); #rgb=>[decimal,decimal,decimal]
225             my $colorobj=$obj->color;
226              
227             =cut
228              
229             sub color {
230             my $self=shift;
231             $self->{"color"}=shift if @_;
232             $self->{"color"}=[0,0,0] unless defined $self->{"color"};
233             return $self->{"color"};
234             }
235              
236             sub _color_index {
237             my $self=shift;
238             my $color=shift || [0,0,0]; #default is black
239             if (ref($color) eq "ARRAY") {
240             #initialize cache
241             my ($r,$g,$b)=@$color;
242             $self->{'_color_index'}||={};
243             $self->{'_color_index'}->{$r}||={};
244             $self->{'_color_index'}->{$r}->{$g}||={};
245             return $self->{'_color_index'}->{$r}->{$g}->{$b}||=$self->gdimage->colorAllocate(@$color);
246             } else {
247             my @rgb=$self->gcnames->rgb($color);
248             if (scalar(@rgb) == 3) {
249             return $self->_color_index(\@rgb); #recursion
250             } else {
251             warn(qq{Warning: Color "$color" not found.});
252             return $self->_color_index([0,0,0]); #recursion
253             }
254             }
255             }
256              
257             =head2 font
258              
259             Method to set or return the current drawing font (only needed by the very few)
260              
261             use GD qw(gdGiantFont gdLargeFont gdMediumBoldFont gdSmallFont gdTinyFont);
262             $obj->font(gdSmallFont); #the default
263             $obj->font;
264              
265             =cut
266              
267             sub font {
268             my $self=shift;
269             $self->{'font'}=shift if @_;
270             $self->{'font'}=gdSmallFont unless defined $self->{'font'};
271             return $self->{'font'};
272             }
273              
274             =head2 iconsize
275              
276             =cut
277              
278             sub iconsize {
279             my $self=shift;
280             $self->{"iconsize"}=shift if @_;
281             $self->{"iconsize"}=7 unless $self->{"iconsize"};
282             return $self->{"iconsize"};
283             }
284              
285             =head2 draw
286              
287             Method returns a PNG binary blob.
288              
289             my $png_binary=$obj->draw;
290              
291             =cut
292              
293             sub draw {
294             my $self = shift;
295             my $p = $self->points;
296             foreach (@$p) {
297             my $x = $_->[0];
298             my $y = $_->[1];
299             my $c = $_->[2] || $self->color;
300             my $i = $_->[3] || $self->iconsize;
301             my $filled = $_->[4] || 0;
302             if ($filled) {
303             $self->gdimage->filledArc($self->_imgxy_xy($x,$y),$i,$i,0,360,$self->_color_index($c));
304             } else {
305             $self->gdimage->arc($self->_imgxy_xy($x,$y),$i,$i,0,360,$self->_color_index($c));
306             }
307             }
308             my $l=$self->lines;
309             foreach (@$l) {
310             my $x0 = $_->[0];
311             my $y0 = $_->[1];
312             my $x1 = $_->[2];
313             my $y1 = $_->[3];
314             my $c = $_->[4] || $self->color;
315             $self->gdimage->line($self->_imgxy_xy($x0, $y0), $self->_imgxy_xy($x1, $y1), $self->_color_index($c));
316             }
317             my $s=$self->strings;
318             foreach (@$s) {
319             my $x = $_->[0];
320             my $y = $_->[1];
321             my $s = $_->[2];
322             my $c = $_->[3] || $self->color;
323             my $f = $_->[4] || $self->font;
324             $self->gdimage->string($f, $self->_imgxy_xy($x, $y), $s, $self->_color_index($c));
325             }
326             my $label=$self->labels;
327             foreach (@$label) {
328             my $x = $_->[0];
329             my $y = $_->[1];
330             my $s = $_->[2];
331             my $c = $_->[3] || $self->color;
332             my $f = $_->[4] || $self->font;
333             $self->gdimage->string($f, $x, $y, $s, $self->_color_index($c));
334             }
335             return $self->gdimage->png;
336             }
337              
338             =head1 OBJECTS
339              
340             =head2 gdimage
341              
342             Returns a L object
343              
344             =cut
345              
346             sub gdimage {
347             my $self=shift;
348             unless ($self->{'gdimage'}) {
349             $self->{'gdimage'}=GD::Image->new($self->width, $self->height);
350              
351             # make the background transparent and interlaced
352             #$self->{'gdimage'}->transparent($self->_color_index([255,255,255]));
353             $self->{'gdimage'}->filledRectangle(0, 0, $self->width, $self->height, $self->_color_index([255,255,255]));
354             #$self->{'gdimage'}->interlaced('true');
355            
356             # Put a frame around the picture
357             $self->{'gdimage'}->rectangle(0, 0, $self->width-1, $self->height-1, $self->_color_index([0,0,0]));
358             }
359             return $self->{'gdimage'};
360             }
361              
362             =head2 gcnames
363              
364             Returns a L
365              
366             =cut
367              
368             sub gcnames {
369             my $self=shift;
370             unless (defined $self->{'gcnames'}) {
371             eval 'use Graphics::ColorNames';
372             if ($@) {
373             die("Error: Cannot load Graphics::ColorNames");
374             } else {
375             my $file=$self->rgbfile; #stringify for object support
376             $self->{'gcnames'}=Graphics::ColorNames->new("$file") or die("Error: Graphics::ColorNames constructor failed.");
377             }
378             }
379             return $self->{'gcnames'};
380             }
381              
382             =head1 PROPERTIES
383              
384             =head2 width
385              
386             =cut
387              
388             sub width {
389             my $self=shift;
390             $self->{'width'}=640
391             unless defined $self->{'width'};
392             return $self->{'width'};
393             }
394              
395             =head2 height
396              
397             =cut
398              
399             sub height {
400             my $self=shift;
401             $self->{'height'}=480
402             unless defined $self->{'height'};
403             return $self->{'height'};
404             }
405              
406             =head2 ticksx
407              
408             =cut
409              
410             sub ticksx {
411             my $self=shift;
412             $self->{'ticksx'}=10
413             unless defined $self->{'ticksx'};
414             return $self->{'ticksx'};
415             }
416              
417             =head2 ticksy
418              
419             =cut
420              
421             sub ticksy {
422             my $self=shift;
423             $self->{'ticksy'}=10
424             unless defined $self->{'ticksy'};
425             return $self->{'ticksy'};
426             }
427              
428             =head2 borderx
429              
430             =cut
431              
432             sub borderx {
433             my $self=shift;
434             $self->{'borderx'}=2
435             unless defined $self->{'borderx'};
436             return $self->{'borderx'};
437             }
438              
439             =head2 bordery
440              
441             =cut
442              
443             sub bordery {
444             my $self=shift;
445             $self->{'bordery'}=2
446             unless defined $self->{'bordery'};
447             return $self->{'bordery'};
448             }
449              
450             =head2 rgbfile
451              
452             =cut
453              
454             sub rgbfile {
455             my $self=shift;
456             $self->{'rgbfile'}=shift if @_;
457             unless (defined $self->{'rgbfile'}) {
458             $self->{'rgbfile'}="rgb.txt";
459             my $rgb=first {-r} (qw{/etc/X11/rgb.txt /usr/share/X11/rgb.txt /usr/X11R6/lib/X11/rgb.txt ../rgb.txt});
460             $self->{'rgbfile'}=$rgb if $rgb;
461             }
462             return $self->{'rgbfile'};
463             }
464              
465             =head2 minx
466              
467             =cut
468              
469             sub minx {
470             my $self=shift;
471             ($self->{'minx'}, $self->{'maxx'})=$self->_minmaxx
472             unless defined $self->{'minx'};
473             return $self->{'minx'};
474             }
475              
476             =head2 maxx
477              
478             =cut
479              
480             sub maxx {
481             my $self=shift;
482             ($self->{'minx'}, $self->{'maxx'})=$self->_minmaxx
483             unless defined $self->{'maxx'};
484             return $self->{'maxx'};
485             }
486              
487             =head2 miny
488              
489             =cut
490              
491             sub miny {
492             my $self=shift;
493             ($self->{'miny'}, $self->{'maxy'})=$self->_minmaxy
494             unless defined $self->{'miny'};
495             return $self->{'miny'};
496             }
497              
498             =head2 maxy
499              
500             =cut
501              
502             sub maxy {
503             my $self=shift;
504             ($self->{'miny'}, $self->{'maxy'})=$self->_minmaxy
505             unless defined $self->{'maxy'};
506             return $self->{'maxy'};
507             }
508              
509             =head1 INTERNAL METHODS
510              
511             =cut
512              
513             sub _minmaxx {
514             my $self = shift;
515             my $p = $self->points;
516             my $l = $self->lines;
517             my $s = $self->strings;
518             my @x = ();
519             push @x, map {$_->[0]} @$p;
520             push @x, map {$_->[0], $_->[2]} @$l;
521             push @x, map {$_->[0]} @$s;
522             return minmax(@x);
523             }
524              
525             sub _minmaxy {
526             my $self = shift;
527             my $p = $self->points;
528             my $l = $self->lines;
529             my $s = $self->strings;
530             my @x = ();
531             push @x, map {$_->[1]} @$p;
532             push @x, map {$_->[1], $_->[3]} @$l;
533             push @x, map {$_->[1]} @$s;
534             return minmax(@x);
535             }
536              
537             =head2 _scalex
538              
539             Method returns the parameter scaled to the pixels.
540              
541             =cut
542              
543             sub _scalex {
544             my $self = shift;
545             my $x = shift; #units
546             my $max = $self->maxx;
547             my $min = $self->minx;
548             my $s = 1;
549             if (defined($max) and defined($min) and $max-$min) {
550             $s=($max - $min) / ($self->width - 2 * $self->borderx); #units/pixel
551             }
552             return $x / $s; #pixels
553             }
554              
555             =head2 _scaley
556              
557             Method returns the parameter scaled to the pixels.
558              
559             =cut
560              
561             sub _scaley {
562             my $self = shift;
563             my $y = shift; #units
564             my $max = $self->maxy;
565             my $min = $self->miny;
566             my $s = 1;
567             if (defined($max) and defined($min) and $max-$min) {
568             $s=($max - $min) / ($self->height - 2 * $self->bordery); #units/pixel
569             }
570             return $y / $s; #pixels
571             }
572              
573             =head2 _imgxy_xy
574              
575             Method to convert xy to imgxy coordinates
576              
577             =cut
578              
579             sub _imgxy_xy {
580             my $self = shift;
581             my $x = shift;
582             my $y = shift;
583             return ($self->_imgx_x($x), $self->_imgy_y($y));
584             }
585              
586             sub _imgx_x {
587             my $self = shift;
588             my $x = shift;
589             return $self->borderx + $self->_scalex($x - $self->minx);
590             }
591              
592             sub _imgy_y {
593             my $self = shift;
594             my $y = shift;
595             return $self->height - ($self->bordery + $self->_scaley($y - $self->miny));
596             }
597              
598             =head1 TODO
599              
600             I'd like to add this capability into L as a use base qw{Chart::Base}
601              
602             =head1 BUGS
603              
604             Log on RT and email the author
605              
606             =head1 LIMITS
607              
608             There are many packages on CPAN that create graphs and plots from data. But, each one has it's own limitations. This is the research that I did so that hopefully you won't have to...
609              
610             =head2 Similar CPAN Packages
611              
612             =head3 L
613              
614             This is the second best package that I could find on CPAN that supports scatter plots of X/Y data. However, it does not supports a zero based Y-axis for positive data. Otherwise this is a great package.
615              
616             =head3 L
617              
618             This is a great package for its support of legends, layouts and labels but it only support equally spaced x axis data.
619              
620             =head3 L
621              
622             This is a great package for pie charts but for X/Y scatter plots it only supports equally spaced x axis data.
623              
624             =head1 AUTHOR
625              
626             Michael R. Davis qw/perl michaelrdavis com/
627              
628             =head1 LICENSE
629              
630             Copyright (c) 2009 Michael R. Davis (mrdvt92)
631              
632             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
633              
634             =head1 SEE ALSO
635              
636             L, L, L, L, L
637              
638             =cut
639              
640             1;