source: openweather/graph.php @ 85

Revision 85, 74.7 KB checked in by nigel, 5 years ago (diff)

Introduced X labels as defined by key values

Line 
1<?php
2/*
3Graph Class. PHP Class to draw line, point, bar, and area graphs, including numeric x-axis and double y-axis.
4Version: 1.6.4
5Copyright (C) 2000  Herman Veluwenkamp
6
7This library is free software; you can redistribute it and/or
8modify it under the terms of the GNU Lesser General Public
9License as published by the Free Software Foundation; either
10version 2.1 of the License, or (at your option) any later version.
11
12This library is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15Lesser General Public License for more details.
16
17You should have received a copy of the GNU Lesser General Public
18License along with this library; if not, write to the Free Software
19Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21Copy of GNU Lesser General Public License at: http://www.gnu.org/copyleft/lesser.txt
22Contact author at: hermanV@subdimension.com
23*/
24
25
26class graph {
27  var $image;
28  var $debug             =   FALSE;        // be careful!!
29  var $calculated        =   array();      // array of computed values for chart
30  var $parameter         =   array(        // input parameters
31    'width'              =>  320,          // default width of image
32    'height'             =>  240,          // default height of image
33    'file_name'          =>  'none',       // name of file for file to be saved as.
34                                           //  NOTE: no suffix required. this is determined from output_format below.
35    'output_format'      => 'PNG',         // image output format. 'GIF', 'PNG', 'JPEG'. default 'PNG'.
36
37    'seconds_to_live'    =>  0,            // expiry time in seconds (for HTTP header)
38    'hours_to_live'      =>  0,            // expiry time in hours (for HTTP header)
39    'path_to_fonts'      => 'fonts/',      // path to fonts folder. don't forget *trailing* slash!!
40                                           //   for WINDOZE this may need to be the full path, not relative.
41
42    'title'              => 'Graph Title', // text for graph title
43    'title_font'         => 'ARIALBD.TTF',   // title text font. don't forget to set 'path_to_fonts' above.
44    'title_size'         =>  14,           // title text point size
45    'title_colour'       => 'black',       // colour for title text
46
47    'x_label'            => '',            // if this is set then this text is printed on bottom axis of graph.
48    'y_label_left'       => '',            // if this is set then this text is printed on left axis of graph.
49    'y_label_right'      => '',            // if this is set then this text is printed on right axis of graph.
50
51    'label_size'         =>  10,           // label text point size
52    'label_font'         => 'ARIALBD.TTF', // label text font. don't forget to set 'path_to_fonts' above.
53    'label_colour'       => 'gray33',      // label text colour
54    'y_label_angle'      =>  90,           // rotation of y axis label
55
56    'x_label_angle'      =>  0,            // rotation of y axis label
57
58    'outer_padding'      =>  8,            // padding around outer text. i.e. title, y label, and x label.
59    'inner_padding'      =>  6,            // padding beteen axis text and graph.
60    'outer_border'       => 'none',        // colour of border aound image, or 'none'.
61    'inner_border'       => 'black',       // colour of border around actual graph, or 'none'.
62    'inner_border_type'  => 'box',         // 'box' for all four sides, 'axis' for x/y axis only,
63                                           // 'y' or 'y-left' for y axis only, 'y-right' for right y axis only,
64                                           // 'x' for x axis only, 'u' for both left and right y axis and x axis.
65    'outer_background'   => 'none',        // background colour of entire image.
66    'inner_background'   => 'none',        // background colour of plot area.
67
68    'y_min_left'         =>  0,            // this will be reset to minimum value if there is a value lower than this.
69    'y_max_left'         =>  0,            // this will be reset to maximum value if there is a value higher than this.
70    'y_min_right'        =>  0,            // this will be reset to minimum value if there is a value lower than this.
71    'y_max_right'        =>  0,            // this will be reset to maximum value if there is a value higher than this.
72    'x_min'              =>  0,            // only used if x axis is numeric.
73    'x_max'              =>  0,            // only used if x axis is numeric.
74
75    'y_resolution_left'  =>  2,            // scaling for rounding of y axis max value.
76                                           // if max y value is 8645 then
77                                           // if y_resolution is 0, then y_max becomes 9000.
78                                           // if y_resolution is 1, then y_max becomes 8700.
79                                           // if y_resolution is 2, then y_max becomes 8650.
80                                           // if y_resolution is 3, then y_max becomes 8645.
81                                           // get it?
82    'y_decimal_left'     =>  0,            // number of decimal places for y_axis text.
83    'y_resolution_right' =>  2,            // ... same for right hand side
84    'y_decimal_right'    =>  0,            // ... same for right hand side
85    'x_resolution'       =>  2,            // only used if x axis is numeric.
86    'x_decimal'          =>  0,            // only used if x axis is numeric.
87
88    'point_size'         =>  4,            // default point size. use even number for diamond or triangle to get nice look.
89    'brush_size'         =>  4,            // default brush size for brush line.
90    'brush_type'         => 'circle',      // type of brush to use to draw line. choose from the following
91                                           //   'circle', 'square', 'horizontal', 'vertical', 'slash', 'backslash'
92    'bar_size'           =>  0.8,          // size of bar to draw. <1 bars won't touch
93                                           //   1 is full width - i.e. bars will touch.
94                                           //   >1 means bars will overlap.
95    'bar_spacing'        =>  10,           // space in pixels between group of bars for each x value.
96    'shadow_offset'      =>  3,            // draw shadow at this offset, unless overidden by data parameter.
97    'shadow'             => 'grayCC',      // 'none' or colour of shadow.
98    'shadow_below_axis'  => FALSE,         // whether to draw shadows of bars and areas below the x/zero axis.
99
100
101    'x_axis_gridlines'   => 'auto',        // if set to a number then x axis is treated as numeric.
102    'y_axis_gridlines'   =>  6,            // number of gridlines on y axis.
103    'zero_axis'          => 'none',        // colour to draw zero-axis, or 'none'.
104
105
106    'axis_font'          => 'ARIAL.TTF', // axis text font. don't forget to set 'path_to_fonts' above.
107    'axis_size'          =>  9,            // axis text font size in points
108    'axis_colour'        => 'gray33',      // colour of axis text.
109    'y_axis_angle'       =>  0,            // rotation of axis text.
110    'x_axis_angle'       =>  90,           // rotation of axis text.
111
112    'y_axis_text_left'   =>  1,            // whether to print left hand y axis text. if 0 no text, if 1 all ticks have text,
113    'x_axis_text'        =>  1,            //   if 4 then print every 4th tick and text, etc...
114    'y_axis_text_right'  =>  0,            // behaviour same as above for right hand y axis.
115
116    'x_offset'           =>  0.5,          // x axis tick offset from y axis as fraction of tick spacing.
117    'y_ticks_colour'     => 'black',       // colour to draw y ticks, or 'none'.
118    'x_ticks_colour'     => 'black',       // colour to draw x ticks, or 'none'.
119    'y_grid'             => 'line',        // grid lines. set to 'line' or 'dash'...
120    'x_grid'             => 'line',        //   or if set to 'none' print nothing.
121    'grid_colour'        => 'grayEE',      // default grid colour.
122    'tick_length'        =>  4,            // length of ticks in pixels. can be negative. i.e. outside data drawing area.
123
124
125    'legend'             => 'none',        // default. no legend.
126                                          // otherwise: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
127                                          //   'outside-top', 'outside-bottom', 'outside-left', or 'outside-right'.
128    'legend_offset'      =>  10,           // offset in pixels from graph or outside border.
129    'legend_padding'     =>  5,            // padding around legend text.
130    'legend_font'        => 'ARIAL.TTF',   // legend text font. don't forget to set 'path_to_fonts' above.
131    'legend_size'        =>  9,            // legend text point size.
132    'legend_colour'      => 'black',       // legend text colour.
133    'legend_border'      => 'none',        // legend border colour, or 'none'.
134
135    'decimal_point'      => '.',           // symbol for decimal separation  '.' or ',' *european support.
136    'thousand_sep'       => ',',           // symbol for thousand separation ',' or ''
137
138  );
139
140
141function graph() {
142  if (func_num_args() == 2) {
143    $this->parameter['width']  = func_get_arg(0);
144    $this->parameter['height'] = func_get_arg(1);
145  }
146  //$this->boundaryBox  = array(
147  $this->calculated['boundary_box'] = array(
148    'left'      =>  0,
149    'top'       =>  0,
150    'right'     =>  $this->parameter['width'] - 1,
151    'bottom'    =>  $this->parameter['height'] - 1);
152
153  $this->init_colours();
154
155  //ImageColorTransparent($this->image, $this->colour['white']); // colour for transparency
156}
157
158// init all text - title, labels, and axis text.
159function init() {
160  $this->calculated['outer_border'] = $this->calculated['boundary_box'];
161
162  // outer padding
163  $this->calculated['boundary_box']['left']   += $this->parameter['outer_padding'];
164  $this->calculated['boundary_box']['top']    += $this->parameter['outer_padding'];
165  $this->calculated['boundary_box']['right']  -= $this->parameter['outer_padding'];
166  $this->calculated['boundary_box']['bottom'] -= $this->parameter['outer_padding'];
167
168  $this->init_x_axis();
169  $this->init_y_axis();
170  $this->init_legend();
171  $this->init_labels();
172
173  //  take into account tick lengths
174  $this->calculated['bottom_inner_padding'] = $this->parameter['inner_padding'];
175  if (($this->parameter['x_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
176    $this->calculated['bottom_inner_padding'] -= $this->parameter['tick_length'];
177  $this->calculated['boundary_box']['bottom'] -= $this->calculated['bottom_inner_padding'];
178
179  $this->calculated['left_inner_padding'] = $this->parameter['inner_padding'];
180  if ($this->parameter['y_axis_text_left']) {
181    if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
182      $this->calculated['left_inner_padding'] -= $this->parameter['tick_length'];
183  }
184  $this->calculated['boundary_box']['left'] += $this->calculated['left_inner_padding'];
185
186  $this->calculated['right_inner_padding'] = $this->parameter['inner_padding'];
187  if ($this->parameter['y_axis_text_right']) {
188    if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
189      $this->calculated['right_inner_padding'] -= $this->parameter['tick_length'];
190  }
191  $this->calculated['boundary_box']['right'] -= $this->calculated['right_inner_padding'];
192
193  // boundaryBox now has coords for plotting area.
194  $this->calculated['inner_border'] = $this->calculated['boundary_box'];
195
196  $this->init_data();
197  $this->init_x_ticks();
198  $this->init_y_ticks();
199}
200
201
202function draw_text() {
203  $colour = $this->parameter['outer_background'];
204  if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'fill'); // graph background
205
206  // draw border around image
207  $colour = $this->parameter['outer_border'];
208  if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'box'); // graph border
209
210  $this->draw_title();
211  $this->draw_x_label();
212  $this->draw_y_label_left();
213  $this->draw_y_label_right();
214  $this->draw_x_axis();
215  $this->draw_y_axis();
216  if      ($this->calculated['y_axis_left']['has_data'])  $this->draw_zero_axis_left();  // either draw zero axis on left
217  else if ($this->calculated['y_axis_right']['has_data']) $this->draw_zero_axis_right(); // ... or right.
218  $this->draw_legend();
219
220  // draw border around plot area
221  $colour = $this->parameter['inner_background'];
222  if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, 'fill'); // graph background
223
224  // draw border around image
225  $colour = $this->parameter['inner_border'];
226  if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, $this->parameter['inner_border_type']); // graph border
227}
228
229function draw_stack() {
230  $this->init();
231  $this->draw_text();
232
233  $yOrder = $this->y_order; // save y_order data.
234  // iterate over each data set. order is very important if you want to see data correctly. remember shadows!!
235  foreach ($yOrder as $set) {
236    $this->y_order = array($set);
237    $this->init_data();
238    $this->draw_data();
239  }
240  $this->y_order = $yOrder; // revert y_order data.
241
242  $this->output();
243}
244
245function draw() {
246  $this->init();
247  $this->draw_text();
248  $this->draw_data();
249  $this->output();
250}
251
252// draw a data set
253function draw_set($order, $set, $offset) {
254  // defaults
255 
256  //
257  if ($offset) {
258    if (isset($this->y_format[$set]['shadow'])) $colour = $this->y_format[$set]['shadow'];
259    else                                        $colour = $this->parameter['shadow'];
260  } else {
261    $colour   = $this->y_format[$set]['colour'];
262  }
263   
264  $point     = isset($this->y_format[$set]['point'])      ? $this->y_format[$set]['point']      : 'none';
265  $pointSize = isset($this->y_format[$set]['point_size']) ? $this->y_format[$set]['point_size'] : $this->parameter['point_size'];
266  $line      = isset($this->y_format[$set]['line'])       ? $this->y_format[$set]['line']       : 'none';
267  $brushType = isset($this->y_format[$set]['brush_type']) ? $this->y_format[$set]['brush_type'] : $this->parameter['brush_type'];
268  $brushSize = isset($this->y_format[$set]['brush_size']) ? $this->y_format[$set]['brush_size'] : $this->parameter['brush_size'];
269  $bar       = isset($this->y_format[$set]['bar'])        ? $this->y_format[$set]['bar']        : 'none';
270  $barSize   = isset($this->y_format[$set]['bar_size'])   ? $this->y_format[$set]['bar_size']   : $this->parameter['bar_size'];
271  $area      = isset($this->y_format[$set]['area'])       ? $this->y_format[$set]['area']       : 'none';
272
273  $lastX = 0;
274  $lastY = 'none';
275  $fromX = 0;
276  $fromY = 'none';
277
278  //print "set $set<BR>";
279  //expand_pre($this->calculated['y_plot']);
280  $index = -1;
281  foreach ($this->x_data as $x) {
282    $index++;
283   
284    //print "index $index<BR>";
285    $thisY = $this->calculated['y_plot'][$set][$index];
286    $thisX = $this->calculated['x_plot'][$index];
287
288    //print "$thisX, $thisY <BR>";
289    //_p("thisX=$thisX, thisY=$thisY, bar=$bar, barSize=$barSize, colour=$colour, offset=$offset, set=$set");
290   
291    /*    */
292
293
294    if (($bar!='none') && (string)$thisY != 'none') $this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set);
295
296    if (($area!='none') && (((string)$lastY != 'none') && ((string)$thisY != 'none')))
297      $this->area($lastX, $lastY, $thisX, $thisY, $area, $colour, $offset);
298
299    if (($point!='none') && (string)$thisY != 'none') $this->plot($thisX, $thisY, $point, $pointSize, $colour, $offset);
300
301    if (($line!='none') && ((string)$thisY != 'none')) {
302      if ((string)$fromY != 'none')
303        $this->line($fromX, $fromY, $thisX, $thisY, $line, $brushType, $brushSize, $colour, $offset);
304
305      $fromY = $thisY; // start next line from here
306      $fromX = $thisX; // ...
307    } else {
308      $fromY = 'none';
309      $fromX = 'none';
310    }
311
312    $lastX = $thisX;
313    $lastY = $thisY;
314   
315  }
316}
317
318function draw_data() {
319  // cycle thru y data to be plotted
320  // first check for drop shadows...
321  foreach ($this->y_order as $order => $set) {
322   
323    $offset = $this->parameter['shadow_offset'];
324    if (isset($this->y_format[$set]['shadow_offset'])) $offset =  $this->y_format[$set]['shadow_offset'];
325
326    $colour = $this->parameter['shadow'];
327    if (isset($this->y_format[$set]['shadow'])) $colour =  $this->y_format[$set]['shadow'];
328   
329    if (isset($colour)) if ($colour != 'none') $this->draw_set($order, $set, $offset);
330  }
331
332  // then draw data
333  foreach ($this->y_order as $order => $set) {
334    $this->draw_set($order, $set, 0);
335  }
336}
337
338function draw_legend() {
339  $position      = $this->parameter['legend'];
340  if ($position == 'none') return; // abort if no border
341
342  $borderColour  = $this->parameter['legend_border'];
343  $offset        = $this->parameter['legend_offset'];
344  $padding       = $this->parameter['legend_padding'];
345  $height        = $this->calculated['legend']['boundary_box_all']['height'];
346  $width         = $this->calculated['legend']['boundary_box_all']['width'];
347  $graphTop      = $this->calculated['boundary_box']['top'];
348  $graphBottom   = $this->calculated['boundary_box']['bottom'];
349  $graphLeft     = $this->calculated['boundary_box']['left'];
350  $graphRight    = $this->calculated['boundary_box']['right'];
351  $outsideRight  = $this->calculated['outer_border']['right'];
352  $outsideBottom = $this->calculated['outer_border']['bottom'];
353  switch ($position) {
354    case 'top-left':
355      $top    = $graphTop  + $offset;
356      $bottom = $graphTop  + $height + $offset;
357      $left   = $graphLeft + $offset;
358      $right  = $graphLeft + $width + $offset;
359
360      break;
361    case 'top-right':
362      $top    = $graphTop   + $offset;
363      $bottom = $graphTop   + $height + $offset;
364      $left   = $graphRight - $width - $offset;
365      $right  = $graphRight - $offset;
366
367      break;
368    case 'bottom-left':
369      $top    = $graphBottom - $height - $offset;
370      $bottom = $graphBottom - $offset;
371      $left   = $graphLeft   + $offset;
372      $right  = $graphLeft   + $width + $offset;
373
374      break;
375    case 'bottom-right':
376      $top    = $graphBottom - $height - $offset;
377      $bottom = $graphBottom - $offset;
378      $left   = $graphRight  - $width - $offset;
379      $right  = $graphRight  - $offset;
380      break;
381
382    case 'outside-top' :
383      $top    = $graphTop;
384      $bottom = $graphTop     + $height;
385      $left   = $outsideRight - $width - $offset;
386      $right  = $outsideRight - $offset;
387      break;
388
389    case 'outside-bottom' :
390      $top    = $graphBottom  - $height;
391      $bottom = $graphBottom;
392      $left   = $outsideRight - $width - $offset;
393      $right  = $outsideRight - $offset;
394     break;
395
396    case 'outside-left' :
397      $top    = $outsideBottom - $height - $offset;
398      $bottom = $outsideBottom - $offset;
399      $left   = $graphLeft;
400      $right  = $graphLeft     + $width;
401     break;
402
403    case 'outside-right' :
404      $top    = $outsideBottom - $height - $offset;
405      $bottom = $outsideBottom - $offset;
406      $left   = $graphRight    - $width;
407      $right  = $graphRight;
408      break;
409    default: // default is top left. no particular reason.
410      $top    = $this->calculated['boundary_box']['top'];
411      $bottom = $this->calculated['boundary_box']['top'] + $this->calculated['legend']['boundary_box_all']['height'];
412      $left   = $this->calculated['boundary_box']['left'];
413      $right  = $this->calculated['boundary_box']['right'] + $this->calculated['legend']['boundary_box_all']['width'];
414
415}
416  // legend border
417  if($borderColour!='none') $this->draw_rectangle(array('top' => $top,
418                                                        'left' => $left,
419                                                        'bottom' => $bottom,
420                                                        'right' => $right), $this->parameter['legend_border'], 'box');
421
422  // legend text
423  $legendText = array('points' => $this->parameter['legend_size'],
424                      'angle'  => 0,
425                      'font'   => $this->parameter['legend_font'],
426                      'colour' => $this->parameter['legend_colour']);
427
428  $box = $this->calculated['legend']['boundary_box_max']['height']; // use max height for legend square size.
429  $x = $left + $padding;
430  $x_text = $x + $box * 2;
431  $y = $top + $padding;
432
433  foreach ($this->y_order as $set) {
434    $legendText['text'] = $this->calculated['legend']['text'][$set];
435    if ($legendText['text'] != 'none') {
436      // if text exists then draw box and text
437      $boxColour = $this->colour[$this->y_format[$set]['colour']];
438
439      // draw box
440      ImageFilledRectangle($this->image, $x, $y, $x + $box, $y + $box, $boxColour);
441
442      // draw text
443      $coords = array('x' => $x + $box * 2, 'y' => $y, 'reference' => 'top-left');
444      $legendText['boundary_box'] = $this->calculated['legend']['boundary_box'][$set];
445      $this->update_boundaryBox($legendText['boundary_box'], $coords);
446      $this->print_TTF($legendText);
447      $y += $padding + $box;
448    }
449  }
450
451}
452
453function draw_y_label_right() {
454  if (!$this->parameter['y_label_right']) return;
455  $x = $this->calculated['boundary_box']['right'] + $this->parameter['inner_padding'];
456  if ($this->parameter['y_axis_text_right']) $x += $this->calculated['y_axis_right']['boundary_box_max']['width']
457                                           + $this->calculated['right_inner_padding'];
458  $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
459
460  $label = $this->calculated['y_label_right'];
461  $coords = array('x' => $x, 'y' => $y, 'reference' => 'left-center');
462  $this->update_boundaryBox($label['boundary_box'], $coords);
463  $this->print_TTF($label);
464}
465
466
467function draw_y_label_left() {
468  if (!$this->parameter['y_label_left']) return;
469  $x = $this->calculated['boundary_box']['left'] - $this->parameter['inner_padding'];
470  if ($this->parameter['y_axis_text_left']) $x -= $this->calculated['y_axis_left']['boundary_box_max']['width']
471                                           + $this->calculated['left_inner_padding'];
472  $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
473
474  $label = $this->calculated['y_label_left'];
475  $coords = array('x' => $x, 'y' => $y, 'reference' => 'right-center');
476  $this->update_boundaryBox($label['boundary_box'], $coords);
477  $this->print_TTF($label);
478}
479
480function draw_title() {
481  if (!$this->parameter['title']) return;
482  //$y = $this->calculated['outside_border']['top'] + $this->parameter['outer_padding'];
483  $y = $this->calculated['boundary_box']['top'] - $this->parameter['outer_padding'];
484  $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
485  $label = $this->calculated['title'];
486  $coords = array('x' => $x, 'y' => $y, 'reference' => 'bottom-center');
487  $this->update_boundaryBox($label['boundary_box'], $coords);
488  $this->print_TTF($label);
489}
490
491function draw_x_label() {
492  if (!$this->parameter['x_label']) return;
493  $y = $this->calculated['boundary_box']['bottom'] + $this->parameter['inner_padding'];
494  if ($this->parameter['x_axis_text']) $y += $this->calculated['x_axis']['boundary_box_max']['height']
495                                          + $this->calculated['bottom_inner_padding'];
496  $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
497  $label = $this->calculated['x_label'];
498  $coords = array('x' => $x, 'y' => $y, 'reference' => 'top-center');
499  $this->update_boundaryBox($label['boundary_box'], $coords);
500 $this->print_TTF($label);
501}
502
503function draw_zero_axis_left() {
504  $colour = $this->parameter['zero_axis'];
505  if ($colour == 'none') return;
506  // draw zero axis on left hand side
507  $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top']  + ($this->calculated['y_axis_left']['max'] * $this->calculated['y_axis_left']['factor']));
508  ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
509}
510
511function draw_zero_axis_right() {
512  $colour = $this->parameter['zero_axis'];
513  if ($colour == 'none') return;
514  // draw zero axis on right hand side
515  $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top']  + ($this->calculated['y_axis_right']['max'] * $this->calculated['y_axis_right']['factor']));
516  ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
517}
518
519function draw_x_axis() {
520  $gridColour  = $this->colour[$this->parameter['grid_colour']];
521  $tickColour  = $this->colour[$this->parameter['x_ticks_colour']];
522  $axis_colour  = $this->parameter['axis_colour'];
523  $xGrid       = $this->parameter['x_grid'];
524  $gridTop     = $this->calculated['boundary_box']['top'];
525  $gridBottom  = $this->calculated['boundary_box']['bottom'];
526
527  if ($this->parameter['tick_length'] >= 0) {
528    $tickTop     = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
529    $tickBottom  = $this->calculated['boundary_box']['bottom'];
530    $textBottom  = $tickBottom + $this->calculated['bottom_inner_padding'];
531  } else {
532    $tickTop     = $this->calculated['boundary_box']['bottom'];
533    $tickBottom  = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
534    $textBottom  = $tickBottom + $this->calculated['bottom_inner_padding'];
535  }
536
537  $axis_font    = $this->parameter['axis_font'];
538  $axis_size    = $this->parameter['axis_size'];
539  $axis_angle   = $this->parameter['x_axis_angle'];
540
541  if ($axis_angle == 0)  $reference = 'top-center';
542  if ($axis_angle > 0)   $reference = 'top-right';
543  if ($axis_angle < 0)   $reference = 'top-left';
544  if ($axis_angle == 90) $reference = 'top-center';
545
546  //generic tag information. applies to all axis text.
547  $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
548
549  foreach ($this->calculated['x_axis']['tick_x'] as $set => $tickX) {
550    // draw x grid if colour specified
551    if ($xGrid != 'none') {
552      switch ($xGrid) {
553        case 'line':
554          ImageLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
555          break;
556         case 'dash':
557          ImageDashedLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
558          break;
559      }
560    }
561
562    if ($this->parameter['x_axis_text'] && !($set % $this->parameter['x_axis_text'])) { // test if tick should be displayed
563      // draw tick
564      if ($tickColour != 'none')
565        ImageLine($this->image, round($tickX), round($tickTop), round($tickX), round($tickBottom), $tickColour);
566
567      // draw axis text
568      $coords = array('x' => $tickX, 'y' => $textBottom, 'reference' => $reference);
569      $axisTag['text'] = $this->calculated['x_axis']['text'][$set];
570      $axisTag['boundary_box'] = $this->calculated['x_axis']['boundary_box'][$set];
571      $this->update_boundaryBox($axisTag['boundary_box'], $coords);
572      $this->print_TTF($axisTag);
573    }
574  }
575}
576
577function draw_y_axis() {
578  $gridColour  = $this->colour[$this->parameter['grid_colour']];
579  $tickColour  = $this->colour[$this->parameter['y_ticks_colour']];
580  $axis_colour  = $this->parameter['axis_colour'];
581  $yGrid       = $this->parameter['y_grid'];
582  $gridLeft    = $this->calculated['boundary_box']['left'];
583  $gridRight   = $this->calculated['boundary_box']['right'];
584
585  // axis font information
586  $axis_font    = $this->parameter['axis_font'];
587  $axis_size    = $this->parameter['axis_size'];
588  $axis_angle   = $this->parameter['y_axis_angle'];
589  $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
590
591
592  if ($this->calculated['y_axis_left']['has_data']) {
593    // LEFT HAND SIDE
594    // left and right coords for ticks
595    if ($this->parameter['tick_length'] >= 0) {
596      $tickLeft     = $this->calculated['boundary_box']['left'];
597      $tickRight    = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
598    } else {
599      $tickLeft     = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
600      $tickRight    = $this->calculated['boundary_box']['left'];
601    }
602    $textRight      = $tickLeft - $this->calculated['left_inner_padding'];
603
604    if ($axis_angle == 0)  $reference = 'right-center';
605    if ($axis_angle > 0)   $reference = 'right-top';
606    if ($axis_angle < 0)   $reference = 'right-bottom';
607    if ($axis_angle == 90) $reference = 'right-center';
608
609    foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
610      // draw y grid if colour specified
611      if ($yGrid != 'none') {
612        switch ($yGrid) {
613          case 'line':
614            ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
615            break;
616           case 'dash':
617            ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
618            break;
619        }
620      }
621
622      // y axis text
623      if ($this->parameter['y_axis_text_left'] && !($set % $this->parameter['y_axis_text_left'])) { // test if tick should be displayed
624        // draw tick
625        if ($tickColour != 'none')
626          ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
627
628        // draw axis text...
629        $coords = array('x' => $textRight, 'y' => $tickY, 'reference' => $reference);
630        $axisTag['text'] = $this->calculated['y_axis_left']['text'][$set];
631        $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
632        $this->update_boundaryBox($axisTag['boundary_box'], $coords);
633        $this->print_TTF($axisTag);
634      }
635    }
636  }
637
638  if ($this->calculated['y_axis_right']['has_data']) {
639    // RIGHT HAND SIDE
640    // left and right coords for ticks
641    if ($this->parameter['tick_length'] >= 0) {
642      $tickLeft     = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
643      $tickRight    = $this->calculated['boundary_box']['right'];
644    } else {
645      $tickLeft     = $this->calculated['boundary_box']['right'];
646      $tickRight    = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
647    }
648    $textLeft       = $tickRight+ $this->calculated['left_inner_padding'];
649
650    if ($axis_angle == 0)  $reference = 'left-center';
651    if ($axis_angle > 0)   $reference = 'left-bottom';
652    if ($axis_angle < 0)   $reference = 'left-top';
653    if ($axis_angle == 90) $reference = 'left-center';
654
655    foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
656      if (!$this->calculated['y_axis_left']['has_data'] && $yGrid != 'none') { // draw grid if not drawn already (above)
657        switch ($yGrid) {
658          case 'line':
659            ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
660            break;
661           case 'dash':
662            ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
663            break;
664        }
665      }
666
667      if ($this->parameter['y_axis_text_right'] && !($set % $this->parameter['y_axis_text_right'])) { // test if tick should be displayed
668        // draw tick
669        if ($tickColour != 'none')
670          ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
671
672        // draw axis text...
673        $coords = array('x' => $textLeft, 'y' => $tickY, 'reference' => $reference);
674        $axisTag['text'] = $this->calculated['y_axis_right']['text'][$set];
675        $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
676        $this->update_boundaryBox($axisTag['boundary_box'], $coords);
677        $this->print_TTF($axisTag);
678      }
679    }
680  }
681}
682
683function init_data() {
684  $this->calculated['y_plot'] = array(); // array to hold pixel plotting coords for y axis
685  $height = $this->calculated['boundary_box']['bottom'] - $this->calculated['boundary_box']['top'];
686  $width  = $this->calculated['boundary_box']['right'] - $this->calculated['boundary_box']['left'];
687
688  // calculate pixel steps between axis ticks.
689  $this->calculated['y_axis']['step'] = $height / ($this->parameter['y_axis_gridlines'] - 1);
690
691  // calculate x ticks spacing taking into account x offset for ticks.
692  $extraTick  = 2 * $this->parameter['x_offset']; // extra tick to account for padding
693  $numTicks = $this->calculated['x_axis']['num_ticks'] - 1;    // number of x ticks
694
695  $this->calculated['x_axis']['step'] = $width / ($numTicks + $extraTick);
696  $widthPlot = $width - ($this->calculated['x_axis']['step'] * $extraTick);
697  $this->calculated['x_axis']['step'] = $widthPlot / $numTicks;
698
699  //calculate factor for transforming x,y physical coords to logical coords for right hand y_axis.
700  $y_range = $this->calculated['y_axis_right']['max'] - $this->calculated['y_axis_right']['min'];
701  $y_range = ($y_range ? $y_range : 1);
702  $this->calculated['y_axis_right']['factor'] = $height / $y_range;
703
704  //calculate factor for transforming x,y physical coords to logical coords for left hand axis.
705  $yRange = $this->calculated['y_axis_left']['max'] - $this->calculated['y_axis_left']['min'];
706  $yRange = ($yRange ? $yRange : 1);
707  $this->calculated['y_axis_left']['factor'] = $height / $yRange;
708  if ($this->parameter['x_axis_gridlines'] != 'auto') {
709    $xRange = $this->calculated['x_axis']['max'] - $this->calculated['x_axis']['min'];
710    $xRange = ($xRange ? $xRange : 1);
711    $this->calculated['x_axis']['factor'] = $widthPlot / $xRange;
712  }
713
714  // cycle thru all data sets...
715  $this->calculated['num_bars'] = 0;
716  foreach ($this->y_order as $order => $set) {
717    // determine how many bars there are
718    if (isset($this->y_format[$set]['bar']) && ($this->y_format[$set]['bar'] != 'none')) {
719      $this->calculated['bar_offset_index'][$set] = $this->calculated['num_bars']; // index to relate bar with data set.
720      $this->calculated['num_bars']++;
721    }
722
723    // calculate y coords for plotting data
724    $index = -1;
725    foreach ($this->x_data as $x) {
726      $index++;
727      $this->calculated['y_plot'][$set][$index] = $this->y_data[$set][$index];
728
729      if ((string)$this->y_data[$set][$index] != 'none') {
730
731        if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
732          $this->calculated['y_plot'][$set][$index] =
733            round(($this->y_data[$set][$index] - $this->calculated['y_axis_right']['min'])
734              * $this->calculated['y_axis_right']['factor']);
735        } else {
736          //print "$set $index<BR>";
737          $this->calculated['y_plot'][$set][$index] =
738            round(($this->y_data[$set][$index] - $this->calculated['y_axis_left']['min'])
739              * $this->calculated['y_axis_left']['factor']);
740        }
741
742      }
743    }
744  }
745
746  // calculate bar parameters if bars are to be drawn.
747  if ($this->calculated['num_bars']) {
748    $xStep       = $this->calculated['x_axis']['step'];
749    $totalWidth  = $this->calculated['x_axis']['step'] - $this->parameter['bar_spacing'];
750    $barWidth    = $totalWidth / $this->calculated['num_bars'];
751
752    $barX = ($barWidth - $totalWidth) / 2; // starting x offset
753    for ($i=0; $i < $this->calculated['num_bars']; $i++) {
754      $this->calculated['bar_offset_x'][$i] = $barX;
755      $barX += $barWidth; // add width of bar to x offset.
756    }
757    $this->calculated['bar_width'] = $barWidth;
758  }
759
760
761}
762
763function init_x_ticks() {
764  // get coords for x axis ticks and data plots
765  //$xGrid       = $this->parameter['x_grid'];
766  $xStep       = $this->calculated['x_axis']['step'];
767  $ticksOffset = $this->parameter['x_offset']; // where to start drawing ticks relative to y axis.
768  $gridLeft    = $this->calculated['boundary_box']['left'] + ($xStep * $ticksOffset); // grid x start
769  $tickX       = $gridLeft; // tick x coord
770
771  foreach ($this->calculated['x_axis']['text'] as $set => $value) {
772    //print "index: $set<BR>";
773    // x tick value
774    $this->calculated['x_axis']['tick_x'][$set] = $tickX;
775    // if num ticks is auto then x plot value is same as x  tick
776    if ($this->parameter['x_axis_gridlines'] == 'auto') $this->calculated['x_plot'][$set] = round($tickX);
777    //print $this->calculated['x_plot'][$set].'<BR>';
778    $tickX += $xStep;
779  }
780
781  //print "xStep: $xStep <BR>";
782  // if numeric x axis then calculate x coords for each data point. this is seperate from x ticks.
783  if ($this->parameter['x_axis_gridlines'] != 'auto') {
784    $gridX = $gridLeft;
785    $factor = $this->calculated['x_axis']['factor'];
786    $min = $this->calculated['x_axis']['min'];
787
788    foreach ($this->x_data as $index => $x) {
789      //print "index: $index, x: $x<BR>";
790      $offset = $x - $this->calculated['x_axis']['min'];
791
792      //$gridX = ($offset * $this->calculated['x_axis']['factor']);
793      //print "offset: $offset <BR>";
794      //$this->calculated['x_plot'][$set] = $gridLeft + ($offset * $this->calculated['x_axis']['factor']);
795
796      $this->calculated['x_plot'][$index] = $gridLeft + ($x - $min) * $factor;
797
798      //print $this->calculated['x_plot'][$set].'<BR>';
799    }
800  }
801  //expand_pre($this->calculated['boundary_box']);
802  //print "factor ".$this->calculated['x_axis']['factor']."<BR>";
803  //expand_pre($this->calculated['x_plot']);
804}
805
806function init_y_ticks() {
807  // get coords for y axis ticks
808
809  $yStep      = $this->calculated['y_axis']['step'];
810  $gridBottom = $this->calculated['boundary_box']['bottom'];
811  $tickY      = $gridBottom; // tick y coord
812
813  for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) {
814    $this->calculated['y_axis']['tick_y'][$i] = $tickY;
815    $tickY   -= $yStep;
816  }
817
818}
819
820function init_labels() {
821  if ($this->parameter['title']) {
822    $size = $this->get_boundaryBox(
823      array('points' => $this->parameter['title_size'],
824            'angle'  => 0,
825            'font'   => $this->parameter['title_font'],
826            'text'   => $this->parameter['title']));
827    $this->calculated['title']['boundary_box']  = $size;
828    $this->calculated['title']['text']         = $this->parameter['title'];
829    $this->calculated['title']['font']         = $this->parameter['title_font'];
830    $this->calculated['title']['points']       = $this->parameter['title_size'];
831    $this->calculated['title']['colour']       = $this->parameter['title_colour'];
832    $this->calculated['title']['angle']        = 0;
833
834    $this->calculated['boundary_box']['top'] += $size['height'] + $this->parameter['outer_padding'];
835    //$this->calculated['boundary_box']['top'] += $size['height'];
836
837  } else $this->calculated['title']['boundary_box'] = $this->get_null_size();
838
839  if ($this->parameter['y_label_left']) {
840    $this->calculated['y_label_left']['text']    = $this->parameter['y_label_left'];
841    $this->calculated['y_label_left']['angle']   = $this->parameter['y_label_angle'];
842    $this->calculated['y_label_left']['font']    = $this->parameter['label_font'];
843    $this->calculated['y_label_left']['points']  = $this->parameter['label_size'];
844    $this->calculated['y_label_left']['colour']  = $this->parameter['label_colour'];
845
846    $size = $this->get_boundaryBox($this->calculated['y_label_left']);
847    $this->calculated['y_label_left']['boundary_box']  = $size;
848    //$this->calculated['boundary_box']['left'] += $size['width'] + $this->parameter['inner_padding'];
849    $this->calculated['boundary_box']['left'] += $size['width'];
850
851  } else $this->calculated['y_label_left']['boundary_box'] = $this->get_null_size();
852
853  if ($this->parameter['y_label_right']) {
854    $this->calculated['y_label_right']['text']    = $this->parameter['y_label_right'];
855    $this->calculated['y_label_right']['angle']   = $this->parameter['y_label_angle'];
856    $this->calculated['y_label_right']['font']    = $this->parameter['label_font'];
857    $this->calculated['y_label_right']['points']  = $this->parameter['label_size'];
858    $this->calculated['y_label_right']['colour']  = $this->parameter['label_colour'];
859
860    $size = $this->get_boundaryBox($this->calculated['y_label_right']);
861    $this->calculated['y_label_right']['boundary_box']  = $size;
862    //$this->calculated['boundary_box']['right'] -= $size['width'] + $this->parameter['inner_padding'];
863    $this->calculated['boundary_box']['right'] -= $size['width'];
864
865  } else $this->calculated['y_label_right']['boundary_box'] = $this->get_null_size();
866
867  if ($this->parameter['x_label']) {
868    $this->calculated['x_label']['text']         = $this->parameter['x_label'];
869    $this->calculated['x_label']['angle']        = $this->parameter['x_label_angle'];
870    $this->calculated['x_label']['font']         = $this->parameter['label_font'];
871    $this->calculated['x_label']['points']       = $this->parameter['label_size'];
872    $this->calculated['x_label']['colour']       = $this->parameter['label_colour'];
873
874    $size = $this->get_boundaryBox($this->calculated['x_label']);
875    $this->calculated['x_label']['boundary_box']  = $size;
876    //$this->calculated['boundary_box']['bottom'] -= $size['height'] + $this->parameter['inner_padding'];
877    $this->calculated['boundary_box']['bottom'] -= $size['height'];
878
879  } else $this->calculated['x_label']['boundary_box'] = $this->get_null_size();
880
881}
882
883
884function init_legend() {
885  $this->calculated['legend'] = array(); // array to hold calculated values for legend.
886  //$this->calculated['legend']['boundary_box_max'] = array('height' => 0, 'width' => 0);
887  $this->calculated['legend']['boundary_box_max'] = $this->get_null_size();
888  if ($this->parameter['legend'] == 'none') return;
889
890  $position = $this->parameter['legend'];
891  $numSets = 0; // number of data sets with legends.
892  $sumTextHeight = 0; // total of height of all legend text items.
893  $width = 0;
894  $height = 0;
895
896  foreach ($this->y_order as $set) {
897   $text = isset($this->y_format[$set]['legend']) ? $this->y_format[$set]['legend'] : 'none';
898   $size = $this->get_boundaryBox(
899     array('points' => $this->parameter['legend_size'],
900           'angle'  => 0,
901           'font'   => $this->parameter['legend_font'],
902           'text'   => $text));
903
904   $this->calculated['legend']['boundary_box'][$set] = $size;
905   $this->calculated['legend']['text'][$set]        = $text;
906   //$this->calculated['legend']['font'][$set]        = $this->parameter['legend_font'];
907   //$this->calculated['legend']['points'][$set]      = $this->parameter['legend_size'];
908   //$this->calculated['legend']['angle'][$set]       = 0;
909
910   if ($text && $text!='none') {
911     $numSets++;
912     $sumTextHeight += $size['height'];
913   }
914
915   if ($size['width'] > $this->calculated['legend']['boundary_box_max']['width'])
916     $this->calculated['legend']['boundary_box_max'] = $size;
917  }
918
919  $offset  = $this->parameter['legend_offset'];  // offset in pixels of legend box from graph border.
920  $padding = $this->parameter['legend_padding']; // padding in pixels around legend text.
921  $textWidth = $this->calculated['legend']['boundary_box_max']['width']; // width of largest legend item.
922  $textHeight = $this->calculated['legend']['boundary_box_max']['height']; // use height as size to use for colour square in legend.
923  $width = $padding * 2 + $textWidth + $textHeight * 2;  // left and right padding + maximum text width + space for square
924  $height = $padding * ($numSets + 1) + $sumTextHeight; // top and bottom padding + padding between text + text.
925
926
927  $this->calculated['legend']['boundary_box_all'] = array('width'     => $width,
928                                                        'height'    => $height,
929                                                        'offset'    => $offset,
930                                                        'reference' => $position);
931
932  switch ($position) { // move in right or bottom if legend is outside data plotting area.
933    case 'outside-top' :
934      $this->calculated['boundary_box']['right']      -= $offset + $width; // move in right hand side
935      break;
936
937    case 'outside-bottom' :
938      $this->calculated['boundary_box']['right']      -= $offset + $width; // move in right hand side
939      break;
940
941    case 'outside-left' :
942      $this->calculated['boundary_box']['bottom']      -= $offset + $height; // move in right hand side
943      break;
944
945    case 'outside-right' :
946      $this->calculated['boundary_box']['bottom']      -= $offset + $height; // move in right hand side
947      break;
948  }
949}
950
951function init_y_axis() {
952  $this->calculated['y_axis_left'] = array(); // array to hold calculated values for y_axis on left.
953  $this->calculated['y_axis_left']['boundary_box_max'] = $this->get_null_size();
954  $this->calculated['y_axis_right'] = array(); // array to hold calculated values for y_axis on right.
955  $this->calculated['y_axis_right']['boundary_box_max'] = $this->get_null_size();
956
957  $axis_font       = $this->parameter['axis_font'];
958  $axis_size       = $this->parameter['axis_size'];
959  $axis_colour     = $this->parameter['axis_colour'];
960  $axis_angle      = $this->parameter['y_axis_angle'];
961
962  $this->calculated['y_axis_left']['has_data'] = FALSE;
963  $this->calculated['y_axis_right']['has_data'] = FALSE;
964
965  // find min and max y values.
966  $minLeft = $this->parameter['y_min_left'];
967  $maxLeft = $this->parameter['y_max_left'];
968  $minRight = $this->parameter['y_min_right'];
969  $maxRight = $this->parameter['y_max_right'];
970  $dataLeft = array();
971  $dataRight = array();
972  foreach ($this->y_order as $order => $set) {
973    if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
974      $this->calculated['y_axis_right']['has_data'] = TRUE;
975      $dataRight = array_merge($dataRight, $this->y_data[$set]);
976    } else {
977      $this->calculated['y_axis_left']['has_data'] = TRUE;
978      $dataLeft = array_merge($dataLeft, $this->y_data[$set]);
979    }
980  }
981  $dataLeftRange = $this->find_range($dataLeft, $minLeft, $maxLeft, $this->parameter['y_resolution_left']);
982  $dataRightRange = $this->find_range($dataRight, $minRight, $maxRight, $this->parameter['y_resolution_right']);
983  $minLeft = $dataLeftRange['min'];
984  $maxLeft = $dataLeftRange['max'];
985  $minRight = $dataRightRange['min'];
986  $maxRight = $dataRightRange['max'];
987
988  $this->calculated['y_axis_left']['min']  = $minLeft;
989  $this->calculated['y_axis_left']['max']  = $maxLeft;
990  $this->calculated['y_axis_right']['min'] = $minRight;
991  $this->calculated['y_axis_right']['max'] = $maxRight;
992
993  $stepLeft = ($maxLeft - $minLeft) / ($this->parameter['y_axis_gridlines'] - 1);
994  $startLeft = $minLeft;
995  $step_right = ($maxRight - $minRight) / ($this->parameter['y_axis_gridlines'] - 1);
996  $start_right = $minRight;
997
998  if ($this->parameter['y_axis_text_left']) {
999    for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1000      // left y axis
1001      $value = number_format($startLeft, $this->parameter['y_decimal_left'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1002      $this->calculated['y_axis_left']['data'][$i]  = $startLeft;
1003      $this->calculated['y_axis_left']['text'][$i]  = $value; // text is formatted raw data
1004
1005      $size = $this->get_boundaryBox(
1006        array('points' => $axis_size,
1007              'font'   => $axis_font,
1008              'angle'  => $axis_angle,
1009              'colour' => $axis_colour,
1010              'text'   => $value));
1011      $this->calculated['y_axis_left']['boundary_box'][$i] = $size;
1012
1013      if ($size['height'] > $this->calculated['y_axis_left']['boundary_box_max']['height'])
1014        $this->calculated['y_axis_left']['boundary_box_max']['height'] = $size['height'];
1015      if ($size['width'] > $this->calculated['y_axis_left']['boundary_box_max']['width'])
1016        $this->calculated['y_axis_left']['boundary_box_max']['width'] = $size['width'];
1017
1018      $startLeft += $stepLeft;
1019    }
1020    $this->calculated['boundary_box']['left'] += $this->calculated['y_axis_left']['boundary_box_max']['width']
1021                                                + $this->parameter['inner_padding'];
1022  }
1023
1024  if ($this->parameter['y_axis_text_right']) {
1025    for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1026      // right y axis
1027      $value = number_format($start_right, $this->parameter['y_decimal_right'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1028      $this->calculated['y_axis_right']['data'][$i]  = $start_right;
1029      $this->calculated['y_axis_right']['text'][$i]  = $value; // text is formatted raw data
1030      $size = $this->get_boundaryBox(
1031        array('points' => $axis_size,
1032              'font'   => $axis_font,
1033              'angle'  => $axis_angle,
1034              'colour' => $axis_colour,
1035              'text'   => $value));
1036      $this->calculated['y_axis_right']['boundary_box'][$i] = $size;
1037
1038      if ($size['height'] > $this->calculated['y_axis_right']['boundary_box_max']['height'])
1039        $this->calculated['y_axis_right']['boundary_box_max'] = $size;
1040      if ($size['width'] > $this->calculated['y_axis_right']['boundary_box_max']['width'])
1041        $this->calculated['y_axis_right']['boundary_box_max']['width'] = $size['width'];
1042
1043      $start_right += $step_right;
1044    }
1045    $this->calculated['boundary_box']['right'] -= $this->calculated['y_axis_right']['boundary_box_max']['width']
1046                                                + $this->parameter['inner_padding'];
1047  }
1048}
1049
1050function init_x_axis() {
1051  $this->calculated['x_axis'] = array(); // array to hold calculated values for x_axis.
1052  $this->calculated['x_axis']['boundary_box_max'] = array('height' => 0, 'width' => 0);
1053
1054  $axis_font       = $this->parameter['axis_font'];
1055  $axis_size       = $this->parameter['axis_size'];
1056  $axis_colour     = $this->parameter['axis_colour'];
1057  $axis_angle      = $this->parameter['x_axis_angle'];
1058
1059  // check whether to treat x axis as numeric
1060  if ($this->parameter['x_axis_gridlines'] == 'auto') { // auto means text based x_axis, not numeric...
1061    $this->calculated['x_axis']['num_ticks'] = sizeof($this->x_data);
1062      $data = $this->x_data;
1063      for ($i=0; $i < $this->calculated['x_axis']['num_ticks']; $i++) {
1064        $key = key($data);
1065        $value = array_shift($data); // grab value from begin of array
1066        if (is_numeric($key)) $key = $value;
1067        $this->calculated['x_axis']['data'][$i]  = $value;
1068        $this->calculated['x_axis']['text'][$i]  = $key; // raw data and text are both the same in this case
1069        $size = $this->get_boundaryBox(
1070          array('points' => $axis_size,
1071                'font'   => $axis_font,
1072                'angle'  => $axis_angle,
1073                'colour' => $axis_colour,
1074                'text'   => $key));
1075        $this->calculated['x_axis']['boundary_box'][$i] = $size;
1076        if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1077          $this->calculated['x_axis']['boundary_box_max'] = $size;
1078      }
1079
1080  } else { // x axis is numeric so find max min values...
1081    $this->calculated['x_axis']['num_ticks'] = $this->parameter['x_axis_gridlines'];
1082
1083    $min = $this->parameter['x_min'];
1084    $max = $this->parameter['x_max'];
1085    $data = array();
1086    $data = $this->find_range($this->x_data, $min, $max, $this->parameter['x_resolution']);
1087    $min = $data['min'];
1088    $max = $data['max'];
1089    $this->calculated['x_axis']['min'] = $min;
1090    $this->calculated['x_axis']['max'] = $max;
1091
1092    $step = ($max - $min) / ($this->calculated['x_axis']['num_ticks'] - 1);
1093    $start = $min;
1094
1095    for ($i = 0; $i < $this->calculated['x_axis']['num_ticks']; $i++) { // calculate x axis text sizes
1096      $value = number_format($start, $this->parameter['x_decimal'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1097      $this->calculated['x_axis']['data'][$i]  = $start;
1098      $this->calculated['x_axis']['text'][$i]  = $value; // text is formatted raw data
1099
1100      $size = $this->get_boundaryBox(
1101        array('points' => $axis_size,
1102              'font'   => $axis_font,
1103              'angle'  => $axis_angle,
1104              'colour' => $axis_colour,
1105              'text'   => $value));
1106      $this->calculated['x_axis']['boundary_box'][$i] = $size;
1107
1108      if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1109        $this->calculated['x_axis']['boundary_box_max'] = $size;
1110
1111      $start += $step;
1112    }
1113  }
1114  if ($this->parameter['x_axis_text'])
1115    $this->calculated['boundary_box']['bottom'] -= $this->calculated['x_axis']['boundary_box_max']['height']
1116                                                  + $this->parameter['inner_padding'];
1117}
1118
1119// find max and min values for a data array given the resolution.
1120function find_range($data, $min, $max, $resolution) {
1121  if (sizeof($data) == 0 ) return array('min' => 0, 'max' => 0);
1122  foreach ($data as $key => $value) {
1123    if ($value=='none') continue;
1124    if ($value > $max) $max = $value;
1125    if ($value < $min) $min = $value;
1126  }
1127
1128  if ($max == 0) {
1129    $factor = 1;
1130  } else {
1131    if ($max < 0) $factor = - pow(10, (floor(log10(abs($max))) + $resolution) );
1132    else $factor = pow(10, (floor(log10(abs($max))) - $resolution) );
1133  }
1134
1135  $max = $factor * @ceil($max / $factor);
1136  $min = $factor * @floor($min / $factor);
1137
1138  //print "max=$max, min=$min<BR>";
1139
1140  return array('min' => $min, 'max' => $max);
1141}
1142
1143
1144function print_TTF($message) {
1145  $points    = $message['points'];
1146  $angle     = $message['angle'];
1147  $text      = $message['text'];
1148  $colour    = $this->colour[$message['colour']];
1149  $font      = $this->parameter['path_to_fonts'].$message['font'];
1150
1151  $x         = $message['boundary_box']['x'];
1152  $y         = $message['boundary_box']['y'];
1153  $offsetX   = $message['boundary_box']['offsetX'];
1154  $offsetY   = $message['boundary_box']['offsetY'];
1155  $height    = $message['boundary_box']['height'];
1156  $width     = $message['boundary_box']['width'];
1157  $reference = $message['boundary_box']['reference'];
1158
1159  switch ($reference) {
1160    case 'top-left':
1161    case 'left-top':
1162      $y += $height - $offsetY;
1163      //$y += $offsetY;
1164      $x += $offsetX;
1165      break;
1166    case 'left-center':
1167      $y += ($height / 2) - $offsetY;
1168      $x += $offsetX;
1169      break;
1170    case 'left-bottom':
1171      $y -= $offsetY;
1172      $x += $offsetX;
1173     break;
1174    case 'top-center':
1175      $y += $height - $offsetY;
1176      $x -= ($width / 2) - $offsetX;
1177     break;
1178    case 'top-right':
1179    case 'right-top':
1180      $y += $height - $offsetY;
1181      $x -= $width  - $offsetX;
1182      break;
1183    case 'right-center':
1184      $y += ($height / 2) - $offsetY;
1185      $x -= $width  - $offsetX;
1186      break;
1187    case 'right-bottom':
1188      $y -= $offsetY;
1189      $x -= $width  - $offsetX;
1190      break;
1191    case 'bottom-center':
1192      $y -= $offsetY;
1193      $x -= ($width / 2) - $offsetX;
1194     break;
1195    default:
1196      $y = 0;
1197      $x = 0;
1198      break;
1199  }
1200  ImageTTFText($this->image, $points, $angle, $x, $y, $colour, $font, $text);
1201}
1202
1203// move boundaryBox to coordinates specified
1204function update_boundaryBox(&$boundaryBox, $coords) {
1205  $width      = $boundaryBox['width'];
1206  $height     = $boundaryBox['height'];
1207  $x          = $coords['x'];
1208  $y          = $coords['y'];
1209  $reference  = $coords['reference'];
1210  switch ($reference) {
1211    case 'top-left':
1212    case 'left-top':
1213      $top    = $y;
1214      $bottom = $y + $height;
1215      $left   = $x;
1216      $right  = $x + $width;
1217      break;
1218    case 'left-center':
1219      $top    = $y - ($height / 2);
1220      $bottom = $y + ($height / 2);
1221      $left   = $x;
1222      $right  = $x + $width;
1223      break;
1224    case 'left-bottom':
1225      $top    = $y - $height;
1226      $bottom = $y;
1227      $left   = $x;
1228      $right  = $x + $width;
1229      break;
1230    case 'top-center':
1231      $top    = $y;
1232      $bottom = $y + $height;
1233      $left   = $x - ($width / 2);
1234      $right  = $x + ($width / 2);
1235      break;
1236    case 'right-top':
1237    case 'top-right':
1238      $top    = $y;
1239      $bottom = $y + $height;
1240      $left   = $x - $width;
1241      $right  = $x;
1242      break;
1243    case 'right-center':
1244      $top    = $y - ($height / 2);
1245      $bottom = $y + ($height / 2);
1246      $left   = $x - $width;
1247      $right  = $x;
1248      break;
1249    case 'bottom=right':
1250    case 'right-bottom':
1251      $top    = $y - $height;
1252      $bottom = $y;
1253      $left   = $x - $width;
1254      $right  = $x;
1255      break;
1256    default:
1257      $top    = 0;
1258      $bottom = $height;
1259      $left   = 0;
1260      $right  = $width;
1261      break;
1262  }
1263
1264  $boundaryBox = array_merge($boundaryBox, array('top'       => $top,
1265                                                 'bottom'    => $bottom,
1266                                                 'left'      => $left,
1267                                                 'right'     => $right,
1268                                                 'x'         => $x,
1269                                                 'y'         => $y,
1270                                                 'reference' => $reference));
1271}
1272
1273function get_null_size() {
1274        return array('width'      => 0,
1275                     'height'     => 0,
1276                     'offsetX'    => 0,
1277                     'offsetY'    => 0,
1278                     //'fontHeight' => 0
1279                     );
1280}
1281
1282function get_boundaryBox($message) {
1283  $points  = $message['points'];
1284  $angle   = $message['angle'];
1285  $font    = $this->parameter['path_to_fonts'].$message['font'];
1286  $text    = $message['text'];
1287
1288  //print ('get_boundaryBox');
1289  //expandPre($message);
1290
1291  // get font size
1292        $bounds = ImageTTFBBox($points, $angle, $font, "W");
1293        if ($angle < 0) {
1294                $fontHeight = abs($bounds[7]-$bounds[1]);
1295        } else if ($angle > 0) {
1296                $fontHeight = abs($bounds[1]-$bounds[7]);
1297        } else {
1298                $fontHeight = abs($bounds[7]-$bounds[1]);
1299        }
1300
1301        // get boundary box and offsets for printing at an angle
1302        $bounds = ImageTTFBBox($points, $angle, $font, $text);
1303
1304        if ($angle < 0) {
1305                $width = abs($bounds[4]-$bounds[0]);
1306                $height = abs($bounds[3]-$bounds[7]);
1307                $offsetY = abs($bounds[3]-$bounds[1]);
1308                $offsetX = 0;
1309
1310        } else if ($angle > 0) {
1311                $width = abs($bounds[2]-$bounds[6]);
1312                $height = abs($bounds[1]-$bounds[5]);
1313                $offsetY = 0;
1314                $offsetX = abs($bounds[0]-$bounds[6]);
1315
1316        } else {
1317                $width = abs($bounds[4]-$bounds[6]);
1318                $height = abs($bounds[7]-$bounds[1]);
1319                $offsetY = 0;
1320                $offsetX = 0;
1321        }
1322
1323        //return values
1324        return array('width'      => $width,
1325                     'height'     => $height,
1326                     'offsetX'    => $offsetX,
1327                     'offsetY'    => $offsetY,
1328                     //'fontHeight' => $fontHeight
1329                     );
1330}
1331
1332function draw_rectangle($border, $colour, $type) {
1333  $colour = $this->colour[$colour];
1334  switch ($type) {
1335    case 'fill':    // fill the rectangle
1336      ImageFilledRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1337      break;
1338    case 'box':     // all sides
1339      ImageRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1340      break;
1341    case 'axis':    // bottom x axis and left y axis
1342      ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1343      ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1344      break;
1345    case 'y':       // left y axis only
1346    case 'y-left':
1347      ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1348      break;
1349    case 'y-right': // right y axis only
1350      ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1351      break;
1352    case 'x':       // bottom x axis only
1353      ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1354      break;
1355    case 'u':       // u shaped. bottom x axis and both left and right y axis.
1356      ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1357      ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1358      ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1359      break;
1360
1361  }
1362}
1363
1364function init_colours() {
1365  $this->image              = ImageCreate($this->parameter['width'], $this->parameter['height']);
1366  // standard colours
1367  $this->colour['white']    = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF); // first colour is background colour.
1368  $this->colour['black']    = ImageColorAllocate ($this->image, 0x00, 0x00, 0x00);
1369  $this->colour['maroon']   = ImageColorAllocate ($this->image, 0x80, 0x00, 0x00);
1370  $this->colour['green']    = ImageColorAllocate ($this->image, 0x00, 0x80, 0x00);
1371  $this->colour['olive']    = ImageColorAllocate ($this->image, 0x80, 0x80, 0x00);
1372  $this->colour['navy']     = ImageColorAllocate ($this->image, 0x00, 0x00, 0x80);
1373  $this->colour['purple']   = ImageColorAllocate ($this->image, 0x80, 0x00, 0x80);
1374  $this->colour['gray']     = ImageColorAllocate ($this->image, 0x80, 0x80, 0x80);
1375  $this->colour['red']      = ImageColorAllocate ($this->image, 0xFF, 0x00, 0x00);
1376  $this->colour['lime']     = ImageColorAllocate ($this->image, 0x00, 0xFF, 0x00);
1377  $this->colour['yellow']   = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0x00);
1378  $this->colour['blue']     = ImageColorAllocate ($this->image, 0x00, 0x00, 0xFF);
1379  $this->colour['fuchsia']  = ImageColorAllocate ($this->image, 0xFF, 0x00, 0xFF);
1380  $this->colour['aqua']     = ImageColorAllocate ($this->image, 0x00, 0xFF, 0xFF);
1381  //$this->colour['white']    = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF);
1382  // shades of gray
1383  $this->colour['grayF0']   = ImageColorAllocate ($this->image, 0xF0, 0xF0, 0xF0);
1384  $this->colour['grayEE']   = ImageColorAllocate ($this->image, 0xEE, 0xEE, 0xEE);
1385  $this->colour['grayDD']   = ImageColorAllocate ($this->image, 0xDD, 0xDD, 0xDD);
1386  $this->colour['grayCC']   = ImageColorAllocate ($this->image, 0xCC, 0xCC, 0xCC);
1387  $this->colour['grayBB']   = ImageColorAllocate ($this->image, 0xBB, 0xBB, 0xBB);
1388  $this->colour['grayAA']   = ImageColorAllocate ($this->image, 0xAA, 0xAA, 0xAA);
1389  $this->colour['gray33']   = ImageColorAllocate ($this->image, 0x33, 0x33, 0x33);
1390  $this->colour['gray66']   = ImageColorAllocate ($this->image, 0x66, 0x66, 0x66);
1391  $this->colour['gray99']   = ImageColorAllocate ($this->image, 0x99, 0x99, 0x99);
1392
1393  $this->colour['none']   = 'none';
1394  return true;
1395}
1396
1397function output() {
1398  if ($this->debug) { // for debugging purposes.
1399    //expandPre($this->graph);
1400    //expandPre($this->y_data);
1401    //expandPre($this->x_data);
1402    //expandPre($this->parameter);
1403  } else {
1404
1405    $expiresSeconds = $this->parameter['seconds_to_live'];
1406    $expiresHours = $this->parameter['hours_to_live'];
1407
1408    if ($expiresHours || $expiresSeconds) {
1409      $now = mktime (date("H"),date("i"),date("s"),date("m"),date("d"),date("Y"));
1410      $expires = mktime (date("H")+$expiresHours,date("i"),date("s")+$expiresSeconds,date("m"),date("d"),date("Y"));
1411      $expiresGMT = gmdate('D, d M Y H:i:s', $expires).' GMT';
1412      $lastModifiedGMT  = gmdate('D, d M Y H:i:s', $now).' GMT';
1413
1414      Header('Last-modified: '.$lastModifiedGMT);
1415      Header('Expires: '.$expiresGMT);
1416    }
1417
1418    if ($this->parameter['file_name'] == 'none' || empty($this->parameter['file_name'] )) {
1419      switch ($this->parameter['output_format']) {
1420        case 'GIF':
1421          Header("Content-type: image/gif");  // GIF??. switch to PNG guys!!
1422          ImageGIF($this->image);
1423          break;
1424        case 'JPEG':
1425          Header("Content-type: image/jpeg"); // JPEG for line art??. included for completeness.
1426          ImageJPEG($this->image);
1427          break;
1428       default:
1429          Header("Content-type: image/png");  // preferred output format
1430          ImagePNG($this->image);
1431          break;
1432      }
1433    } else {
1434       switch ($this->parameter['output_format']) {
1435        case 'GIF':
1436          ImageGIF($this->image, $this->parameter['file_name'].'.gif');
1437          break;
1438        case 'JPEG':
1439          ImageJPEG($this->image, $this->parameter['file_name'].'.jpg');
1440          break;
1441       default:
1442          ImagePNG($this->image, $this->parameter['file_name'].'.png');
1443          break;
1444      }
1445    }
1446
1447    ImageDestroy($this->image);
1448  }
1449} // function output
1450
1451/*
1452function init_variable(&$variable, $value, $default) {
1453        if (!empty($value)) $variable = $value;
1454        else if (isset($default)) $variable = $default;
1455        else unset($variable);
1456}
1457*/
1458
1459// plot a point. options include square, circle, diamond, triangle, and dot. offset is used for drawing shadows.
1460// for diamonds and triangles the size should be an even number to get nice look. if odd the points are crooked.
1461function plot($x, $y, $type, $size, $colour, $offset) {
1462  //print("drawing point of type: $type, at offset: $offset");
1463  $u = $x + $offset;
1464  $v = $this->calculated['inner_border']['bottom'] - $y + $offset;
1465  $half = $size / 2;
1466
1467  switch ($type) {
1468    case 'square':
1469      ImageFilledRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1470      break;
1471    case 'square-open':
1472      ImageRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1473      break;
1474    case 'circle':
1475      ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1476      ImageFillToBorder($this->image, $u, $v, $this->colour[$colour], $this->colour[$colour]);
1477      break;
1478    case 'circle-open':
1479      ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1480      break;
1481    case 'diamond':
1482      ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1483      break;
1484    case 'diamond-open':
1485      ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1486      break;
1487    case 'triangle':
1488      ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1489      break;
1490    case 'triangle-open':
1491      ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1492      break;
1493    case 'dot':
1494      ImageSetPixel($this->image, $u, $v, $this->colour[$colour]);
1495      break;
1496  }
1497}
1498
1499function bar($x, $y, $type, $size, $colour, $offset, $index) {
1500 
1501  //_p($index);
1502 
1503  $index_offset = $this->calculated['bar_offset_index'][$index];
1504  $bar_offset = $this->calculated['bar_offset_x'][$index_offset];
1505  //$this->dbug("drawing bar at offset = $offset : index = $index: bar_offset = $bar_offset");
1506
1507  $span = ($this->calculated['bar_width'] * $size) / 2;
1508  $x_left  = $x + $bar_offset - $span;
1509  $x_right = $x + $bar_offset + $span;
1510
1511  if ($this->parameter['zero_axis'] != 'none') {
1512    $zero = $this->calculated['zero_axis'];
1513    if ($this->parameter['shadow_below_axis'] ) $zero  += $offset;
1514    $u_left  = $x_left + $offset;
1515    $u_right = $x_right + $offset - 1;
1516    $v       = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1517
1518    if ($v > $zero) {
1519      $top = $zero +1;
1520      $bottom = $v;
1521    } else {
1522      $top = $v;
1523      $bottom = $zero - 1;
1524    }
1525
1526    switch ($type) {
1527      case 'open':
1528        //ImageRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1529        if ($v > $zero)
1530          ImageRectangle($this->image, round($u_left), $bottom, round($u_right), $bottom, $this->colour[$colour]);
1531        else
1532          ImageRectangle($this->image, round($u_left), $top, round($u_right), $top, $this->colour[$colour]);
1533        ImageRectangle($this->image, round($u_left), $top, round($u_left), $bottom, $this->colour[$colour]);
1534        ImageRectangle($this->image, round($u_right), $top, round($u_right), $bottom, $this->colour[$colour]);
1535        break;
1536      case 'fill':
1537        ImageFilledRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1538        break;
1539    }
1540
1541  } else {
1542
1543    $bottom = $this->calculated['boundary_box']['bottom'];
1544    if ($this->parameter['shadow_below_axis'] ) $bottom  += $offset;
1545    if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1546    $u_left  = $x_left + $offset;
1547    $u_right = $x_right + $offset - 1;
1548    $v       = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1549
1550    switch ($type) {
1551      case 'open':
1552        ImageRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1553        break;
1554      case 'fill':
1555        ImageFilledRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1556        break;
1557    }
1558  }
1559}
1560
1561function area($x_start, $y_start, $x_end, $y_end, $type, $colour, $offset) {
1562  //dbug("drawing area type: $type, at offset: $offset");
1563  if ($this->parameter['zero_axis'] != 'none') {
1564    $bottom = $this->calculated['boundary_box']['bottom'];
1565    $zero   = $this->calculated['zero_axis'];
1566    if ($this->parameter['shadow_below_axis'] ) $zero  += $offset;
1567    $u_start = $x_start + $offset;
1568    $u_end   = $x_end + $offset;
1569    $v_start = $bottom - $y_start + $offset;
1570    $v_end   = $bottom - $y_end + $offset;
1571    switch ($type) {
1572      case 'fill':
1573        // draw it this way 'cos the FilledPolygon routine seems a bit buggy.
1574        ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1575        ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1576       break;
1577      case 'open':
1578        //ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1579        ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1580        ImageLine($this->image, $u_start, $v_start, $u_start, $zero, $this->colour[$colour]);
1581        ImageLine($this->image, $u_end, $v_end, $u_end, $zero, $this->colour[$colour]);
1582       break;
1583    }
1584  } else {
1585    $bottom = $this->calculated['boundary_box']['bottom'];
1586    $u_start = $x_start + $offset;
1587    $u_end   = $x_end + $offset;
1588    $v_start = $bottom - $y_start + $offset;
1589    $v_end   = $bottom - $y_end + $offset;
1590
1591    if ($this->parameter['shadow_below_axis'] ) $bottom  += $offset;
1592    if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1593    switch ($type) {
1594      case 'fill':
1595        ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1596       break;
1597      case 'open':
1598        ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1599       break;
1600    }
1601  }
1602}
1603
1604function line($x_start, $y_start, $x_end, $y_end, $type, $brush_type, $brush_size, $colour, $offset) {
1605  //dbug("drawing line of type: $type, at offset: $offset");
1606  $u_start = $x_start + $offset;
1607  $v_start = $this->calculated['boundary_box']['bottom'] - $y_start + $offset;
1608  $u_end   = $x_end + $offset;
1609  $v_end   = $this->calculated['boundary_box']['bottom'] - $y_end + $offset;
1610
1611  switch ($type) {
1612    case 'brush':
1613      $this->draw_brush_line($u_start, $v_start, $u_end, $v_end, $brush_size, $brush_type, $colour);
1614     break;
1615    case 'line' :
1616      ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1617      break;
1618    case 'dash':
1619      ImageDashedLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1620      break;
1621  }
1622}
1623
1624// function to draw line. would prefer to use gdBrush but this is not supported yet.
1625function draw_brush_line($x0, $y0, $x1, $y1, $size, $type, $colour) {
1626  //$this->dbug("line: $x0, $y0, $x1, $y1");
1627  $dy = $y1 - $y0;
1628  $dx = $x1 - $x0;
1629  $t = 0;
1630  $watchdog = 1024; // precaution to prevent infinite loops.
1631
1632  $this->draw_brush($x0, $y0, $size, $type, $colour);
1633  if (abs($dx) > abs($dy)) { // slope < 1
1634    //$this->dbug("slope < 1");
1635    $m = $dy / $dx; // compute slope
1636    $t += $y0;
1637    $dx = ($dx < 0) ? -1 : 1;
1638    $m *= $dx;
1639    while (round($x0) != round($x1)) {
1640      if (!$watchdog--) break;
1641      $x0 += $dx; // step to next x value
1642      $t += $m;   // add slope to y value
1643      $y = round($t);
1644      //$this->dbug("x0=$x0, x1=$x1, y=$y watchdog=$watchdog");
1645      $this->draw_brush($x0, $y, $size, $type, $colour);
1646
1647    }
1648  } else { // slope >= 1
1649    //$this->dbug("slope >= 1");
1650    $m = $dx / $dy; // compute slope
1651    $t += $x0;
1652    $dy = ($dy < 0) ? -1 : 1;
1653    $m *= $dy;
1654    while (round($y0) != round($y1)) {
1655      if (!$watchdog--) break;
1656      $y0 += $dy; // step to next y value
1657      $t += $m;   // add slope to x value
1658      $x = round($t);
1659      //$this->dbug("x=$x, y0=$y0, y1=$y1 watchdog=$watchdog");
1660      $this->draw_brush($x, $y0, $size, $type, $colour);
1661
1662    }
1663  }
1664}
1665
1666function draw_brush($x, $y, $size, $type, $colour) {
1667  $x = round($x);
1668  $y = round($y);
1669  $half = round($size / 2);
1670  switch ($type) {
1671    case 'circle':
1672      ImageArc($this->image, $x, $y, $size, $size, 0, 360, $this->colour[$colour]);
1673      ImageFillToBorder($this->image, $x, $y, $this->colour[$colour], $this->colour[$colour]);
1674      break;
1675    case 'square':
1676      ImageFilledRectangle($this->image, $x-$half, $y-$half, $x+$half, $y+$half, $this->colour[$colour]);
1677      break;
1678    case 'vertical':
1679      ImageFilledRectangle($this->image, $x, $y-$half, $x+1, $y+$half, $this->colour[$colour]);
1680      break;
1681    case 'horizontal':
1682      ImageFilledRectangle($this->image, $x-$half, $y, $x+$half, $y+1, $this->colour[$colour]);
1683      break;
1684    case 'slash':
1685      ImageFilledPolygon($this->image, array($x+$half, $y-$half,
1686                                             $x+$half+1, $y-$half,
1687                                             $x-$half+1, $y+$half,
1688                                             $x-$half, $y+$half
1689                                             ), 4, $this->colour[$colour]);
1690      break;
1691    case 'backslash':
1692      ImageFilledPolygon($this->image, array($x-$half, $y-$half,
1693                                             $x-$half+1, $y-$half,
1694                                             $x+$half+1, $y+$half,
1695                                             $x+$half, $y+$half
1696                                             ), 4, $this->colour[$colour]);
1697      break;
1698    default:
1699      @eval($type); // user can create own brush script.
1700  }
1701}
1702
1703} // class graph
1704
1705
1706?>
Note: See TracBrowser for help on using the repository browser.