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