File Coverage

blib/lib/CGI/Graph/Plot/points.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package CGI::Graph::Plot::points;
2              
3 1     1   1689 use GD;
  0            
  0            
4             use CGI::Graph::Plot;
5             use GD::Graph::points;
6              
7             @ISA = ("CGI::Graph::Plot");
8              
9             my %default = ( x_tick_number => 8,
10             y_tick_number => 8,
11             y_label_skip => 2,
12             r_margin => 7,
13             label_offset => 65,
14             label_size => 12,
15             tick_size => 4,
16             precision => 14,
17             header_size => 15, # default
18             space_size => 18, # values for
19             data_size => 100 # text box
20             );
21              
22             #
23             # calls parent class to intialize values, then updates selection values if
24             # necessary. If called from the CGI that is generating the image, then the
25             # bounds should not be calculated and the selection values not updated (since
26             # these have already been done).
27             #
28              
29             sub new {
30             my ($pkg, $vars) = @_;
31             my $class = ref($pkg) || $pkg;
32             $vars->{graph_type} = 'points';
33             my $self = $class->SUPER::new($vars);
34              
35             # for a point graph, if the Y axis is not numerical,
36             # find a numerical data set
37             if ($self->{Y} !~ /^[ir]_/) {
38             my @header = $self->{table}->header;
39             foreach (1..$#header) {
40             $self->{Y} = $header[$_];
41             last if ($self->{Y} =~ /^[ir]_/);
42             }
43             die "No numerical Y data available!\n" if ($self->{Y} !~ /^[ir]_/);
44             }
45              
46             # for calls within Draw.cgi, return now
47             if ($self->{rand}) {
48             return bless $self, $class;
49             }
50              
51             $self->graphBounds();
52              
53             my @selected = split("",$self->{selected});
54             if ($self->{select}) {
55             $selected[$self->{select}-1]=($selected[$self->{select}-1])?0:1;
56             }
57              
58             elsif ($self->{select_list} eq 'Visible' || $self->{unselect_list} eq 'Visible') {
59             my ($Xref,$Yref) = $self->valuesInRange();
60             my @drawY = @$Yref;
61              
62             foreach (0..$#drawY) {
63             if (defined $drawY[$_]) {
64             $selected[$self->{table}->elm($_,'_row')-1] =
65             ($self->{select_list} eq 'Visible')?1:0;
66             }
67             }
68             }
69              
70             $self->{selected} = join("",@selected);
71             $self->write_selected();
72              
73             return bless $self, $class;
74             }
75              
76             #
77             # determines upper and lower bounds for the grid.
78             #
79              
80             sub gridBounds {
81             my $self = shift;
82              
83             my @X = $self->{table}->col($self->{X});
84             my @Y = $self->{table}->col($self->{Y});
85             @X = $self->count(@X);
86              
87             return (0,$X[-1],CGI::Graph::Plot::bounds(@Y)) unless ($self->{X} =~/^[ir]_/);
88             return (CGI::Graph::Plot::bounds(@X),CGI::Graph::Plot::bounds(@Y));
89             }
90              
91             #
92             # determines upper and lower bounds for the main graph image or map.
93             #
94              
95             sub graphBounds {
96             my $self = shift;
97            
98             my @X = $self->{table}->col($self->{X});
99             my @Y = $self->{table}->col($self->{Y});
100            
101             @X = $self->count(@X);
102              
103             my ($x_min,$x_max) = ($self->{X} =~/^[ir]_/) ?
104             CGI::Graph::Plot::bounds(@X) : (0,$X[-1]);
105              
106             my ($y_min,$y_max) = CGI::Graph::Plot::bounds(@Y);
107            
108             # for zoom level 1, update self using present min/max values
109             if ($self->{zoom} == 1) {
110             $self->resize();
111             ($self->{x_min},$self->{x_max},$self->{y_min},$self->{y_max}) =
112             ($x_min,$x_max,$y_min,$y_max);
113             return;
114             }
115            
116             # for zoom levels other than 1, adjust the min/max
117             else {
118             my ($xc,$yc,$span) = $self->resize();
119            
120             my $deltaX = $x_max-$x_min;
121             $x_max2 = sprintf("%.".$default{precision}."f",
122             $x_min+($xc+$span/2)*($deltaX)/$self->{divisions});
123             $x_min2 = sprintf("%.".$default{precision}."f",
124             $x_min+($xc-$span/2)*($deltaX)/$self->{divisions});
125            
126             my $deltaY = $y_max-$y_min;
127             $y_max2 = sprintf("%.".$default{precision}."f",
128             $y_min+($yc+$span/2)*($deltaY)/$self->{divisions});
129             $y_min2 = sprintf("%.".$default{precision}."f",
130             $y_min+($yc-$span/2)*($deltaY)/$self->{divisions});
131            
132             ($self->{x_min},$self->{x_max},$self->{y_min},$self->{y_max}) =
133             ($x_min2,$x_max2,$y_min2,$y_max2);
134             return;
135             }
136             }
137              
138             #
139             # initializes the graph for the graphMap and drawGraph functions.
140             #
141              
142             sub setGraph {
143             my ($self,$graph) = @_;
144              
145             $graph->set(
146             x_number_format => \&shorten,
147             y_number_format => \&shorten,
148             x_label => substr($self->{X},2),
149             x_label_position => .5,
150             y_label => substr($self->{Y},2),
151             x_min_value => $self->{x_min},
152             x_max_value => $self->{x_max},
153             y_min_value => $self->{y_min},
154             y_max_value => $self->{y_max},
155             r_margin => $default{r_margin},
156             x_tick_number => $default{x_tick_number},
157             y_tick_number => $default{y_tick_number},
158             y_label_skip => $default{y_label_skip}
159             );
160              
161             # for non-numerical X data
162             unless ($self->{X} =~ /^[ir]_/) {
163             $graph->set(
164             x_number_format => \&x_erase,
165             x_tick_number => 1,
166             x_labels_vertical => 1
167             );
168             }
169            
170             return $graph;
171             }
172              
173             #
174             # generates an image map which allows individual points to be selected. Also
175             # displays properties of the points in a textbox.
176             #
177              
178             sub graphMap {
179             my ($self,$name,$mapName,$info) = @_;
180            
181             # obtain X and Y values to be drawn
182             my($Xref,$Yref) = $self->valuesInRange();
183             my @drawX = @$Xref;
184             my @drawY = @$Yref;
185              
186             # set up graph and plot points
187             my $graph = GD::Graph::points->new($self->{width},$self->{height});
188             $graph = $self->setGraph($graph);
189             my @data = ([@drawX],[@drawY]);
190             my $gd = $graph->plot(\@data);
191              
192             my @info = $self->{table}->col($info);
193             my @row = $self->{table}->col('_row');
194             my @setlist = $graph->get_hotspot;
195             my @header = $self->{table}->header;
196             my $index= 0;
197              
198             my $mapInfo = $self->mapInfo();
199             my $final = "\n\n";
200              
201             for $setnum (1.. $#setlist) {
202              
203             foreach $set (@{$setlist[$setnum]}) {
204              
205             # set up string used for text box
206             my $text;
207             for ($i=0; $i<$self->{table}->nofCol-1; $i++) {
208             $text.= sprintf("%-$default{header_size}.".
209             "$default{space_size}s",substr($header[$i],2));
210             $text.= sprintf("%-.$default{data_size}s",
211             $self->{table}->elm($index,$i));
212             $text.="\\n";
213             }
214             #escape ' characters
215             $text=~s/\'/\\\'/;
216              
217             $final.= "\"$info[$index]\"
218             $final.= "shape=$$set[0] coords=$$set[1],";
219             $final.= "$$set[4],$$set[2],$$set[3] ";
220             # note that there is an error in documentation
221             # arguments are in wrong order
222             $final.= "href=\"$name?select=$row[$index]&$mapInfo";
223             $final.= " onMouseOver=\"myform.myarea.value='$text'; ";
224             $final.= "return true;\">\n";
225              
226             $index++;
227             }
228             }
229             $final.= "\n";
230              
231             return $final;
232             }
233              
234             #
235             # returns a graph image (as a gd object) with selected points highlighted
236             #
237              
238             sub drawGraph {
239             my ($self) = @_;
240              
241             my($Xref,$Yref,$Sref) = $self->valuesInRange();
242             my @drawX = @$Xref;
243             my @drawY = @$Yref;
244             my @selectDraw = @$Sref;
245              
246             my $graph = GD::Graph::points->new($self->{width}, $self->{height});
247             $graph = $self->setGraph($graph);
248             my @data = ([@drawX],[@drawY]);
249              
250             my @dataColor = GD::Graph::colour::hex2rgb($self->{dataColor});
251             GD::Graph::colour::add_colour(myColor => \@dataColor);
252             $graph->set(dclrs => ["myColor"]);
253              
254             my $gd = $graph->plot(\@data);
255             my @setlist = $graph->get_hotspot;
256              
257             my @selectColor = GD::Graph::colour::hex2rgb($self->{selectColor});
258             $selectColor = $gd->colorAllocate(@selectColor);
259             my $index=0;
260            
261             # draw rectangle around each selected point
262             for $setnum (1.. $#setlist) {
263             foreach $set (@{$setlist[$setnum]}) {
264              
265             if ($$set[0] eq 'rect' && $selectDraw[$index]) {
266             $gd->rectangle($$set[1],$$set[4],
267             $$set[2],$$set[3],$selectColor);
268             $gd->rectangle($$set[1]+1,$$set[4]+1,
269             $$set[2]-1,$$set[3]-1,$selectColor);
270             }
271              
272             $index++;
273             }
274             }
275              
276             $gd = &addLabels($self,$gd,$graph) unless ($self->{X} =~ /^[ir]_/);
277              
278             return $gd;
279             }
280              
281             #
282             # returns a gd object similar to drawGraph, but with axes hidden
283             #
284              
285             sub drawGrid {
286             my ($self,$dataColor,$selectColor,$lineColor,$windowColor) = @_;
287              
288             my($Xref,$Yref,$Sref) = $self->valuesInRange();
289             my @X = @$Xref;
290             my @Y = @$Yref;
291             my @selectDraw = @$Sref;
292              
293             # create a graph that is larger than necessary
294             my $graph = GD::Graph::points->new(2*$self->{grid_width},2*$self->{grid_height});
295            
296             my @data = ([@X],[@Y]);
297            
298             $graph->set(
299             fgclr => "white", # hide axes
300             x_tick_number => 1,
301             x_min_value => $self->{x_min},
302             x_max_value => $self->{x_max},
303             y_min_value => $self->{y_min},
304             y_max_value => $self->{y_max},
305             r_margin => $default{r_margin},
306             y_tick_number => $default{y_tick_number},
307             y_label_skip => $default{y_label_skip}
308             );
309              
310             my @dataColor = GD::Graph::colour::hex2rgb($self->{dataColor});
311             GD::Graph::colour::add_colour(myColor => \@dataColor);
312             $graph->set(dclrs => ["myColor"]);
313              
314             my $gd = $graph->plot(\@data);
315             my @setlist = $graph->get_hotspot;
316              
317             my @selectColor = GD::Graph::colour::hex2rgb($self->{selectColor});
318             $selectColor = $gd->colorAllocate(@selectColor);
319              
320             my $index=0;
321              
322             # draw rectangle around each selected point
323             for $setnum (1.. $#setlist) {
324             foreach $set (@{$setlist[$setnum]}) {
325              
326             if ($$set[0] eq 'rect' && $selectDraw[$index]) {
327             $gd->filledRectangle($$set[1],$$set[4],
328             $$set[2],$$set[3],$selectColor);
329             $gd->rectangle($$set[1]+1,$$set[4]+1,
330             $$set[2]-1,$$set[3]-1,$selectColor);
331             }
332              
333             $index++;
334             }
335             }
336              
337             # create new blank image of correct size
338             my $image = new GD::Image($self->{grid_width},$self->{grid_height});
339             my $white = $image->colorAllocate(255,255,255);
340            
341             # copy only the area of the graph that has data plotted
342             $image->copyResized($gd,0,0,$graph->{left},$graph->{top},$self->{grid_width},
343             $self->{grid_height},$graph->{right}-$graph->{left},
344             $graph->{bottom}-$graph->{top});
345              
346             $image = $self->gridLines($image);
347             return $image;
348             }
349              
350             #
351             # formats numerical axis labels to avoid excessively long labels
352             #
353              
354             sub shorten {
355             my $value = shift;
356             return sprintf ("%g", $value);
357             }
358              
359             #
360             # returns labels containing only spaces to make room for manually drawn labels
361             #
362              
363             sub x_erase {
364             return (sprintf "%".$default{label_size}."s");
365             }
366              
367             #
368             # adds tick marks and non-numerical labels
369             #
370              
371             sub addLabels {
372             my ($self,$gd,$graph) = @_;
373              
374             my @X = $self->{table}->col($self->{X});
375              
376             # allocate same blue as rest of graph axes and labels
377             my $blue = $gd->colorAllocate(0,0,125);
378              
379             # determine which X labels are needed
380             my $start = $self->{x_min};
381             $start = int($self->{x_min}+1) unless
382             ($self->{x_min} == int($self->{x_min}));
383             my $end = int($self->{x_max});
384              
385             my @XNR=(""); # non-redundant X values, first value for zero
386              
387             # shorten X labels
388             for (0.. $#X) {
389             if ($X[$_] ne $X[$_-1]) {
390             push (@XNR,sprintf ("%.$default{label_size}s",$X[$_]));
391             }
392             }
393              
394             # write each label under the corresponding point
395             foreach ($start..$end) {
396             # find coordinates of points on x-axis
397             my ($x,$y) = $graph->val_to_pixel($_,$self->{y_min},1);
398             # draw tick mark
399             $gd->line($x,$y-$default{tick_size},$x,$y,$blue);
400             $gd->stringUp(gdTinyFont,$x-$default{tick_size},
401             $y+$default{label_offset},$XNR[$_],$blue);
402             }
403              
404             return $gd;
405             }
406              
407             1;