Updated the 19 build version to 20101031
[moodle.git] / lib / graphlib.php
blobec677dee5d366d30f0a61a8e3f788f6805f0c603
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 ''
146 var $y_tick_labels = null; // array of text values for y-axis tick labels
147 var $offset_relation = null; // array of offsets for different sets of data
151 // init all text - title, labels, and axis text.
152 function init() {
154 /// Moodle mods: overrides the font path and encodings
156 global $CFG;
158 /// A default.ttf is searched for in this order:
159 /// dataroot/lang/xx_local/fonts
160 /// dataroot/lang/xx/fonts
161 /// dirroot/lang/xx/fonts
162 /// dataroot/lang
163 /// lib/
165 $currlang = current_language();
166 if (file_exists("$CFG->dataroot/lang/".$currlang."_local/fonts/default.ttf")) {
167 $fontpath = "$CFG->dataroot/lang/".$currlang."_local/fonts/";
168 } else if (file_exists("$CFG->dataroot/lang/$currlang/fonts/default.ttf")) {
169 $fontpath = "$CFG->dataroot/lang/$currlang/fonts/";
170 } else if (file_exists("$CFG->dirroot/lang/$currlang/fonts/default.ttf")) {
171 $fontpath = "$CFG->dirroot/lang/$currlang/fonts/";
172 } else if (file_exists("$CFG->dataroot/lang/default.ttf")) {
173 $fontpath = "$CFG->dataroot/lang/";
174 } else {
175 $fontpath = "$CFG->libdir/";
178 $this->parameter['path_to_fonts'] = $fontpath;
180 /// End Moodle mods
184 $this->calculated['outer_border'] = $this->calculated['boundary_box'];
186 // outer padding
187 $this->calculated['boundary_box']['left'] += $this->parameter['outer_padding'];
188 $this->calculated['boundary_box']['top'] += $this->parameter['outer_padding'];
189 $this->calculated['boundary_box']['right'] -= $this->parameter['outer_padding'];
190 $this->calculated['boundary_box']['bottom'] -= $this->parameter['outer_padding'];
192 $this->init_x_axis();
193 $this->init_y_axis();
194 $this->init_legend();
195 $this->init_labels();
197 // take into account tick lengths
198 $this->calculated['bottom_inner_padding'] = $this->parameter['x_inner_padding'];
199 if (($this->parameter['x_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
200 $this->calculated['bottom_inner_padding'] -= $this->parameter['tick_length'];
201 $this->calculated['boundary_box']['bottom'] -= $this->calculated['bottom_inner_padding'];
203 $this->calculated['left_inner_padding'] = $this->parameter['y_inner_padding'];
204 if ($this->parameter['y_axis_text_left']) {
205 if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
206 $this->calculated['left_inner_padding'] -= $this->parameter['tick_length'];
208 $this->calculated['boundary_box']['left'] += $this->calculated['left_inner_padding'];
210 $this->calculated['right_inner_padding'] = $this->parameter['y_inner_padding'];
211 if ($this->parameter['y_axis_text_right']) {
212 if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
213 $this->calculated['right_inner_padding'] -= $this->parameter['tick_length'];
215 $this->calculated['boundary_box']['right'] -= $this->calculated['right_inner_padding'];
217 // boundaryBox now has coords for plotting area.
218 $this->calculated['inner_border'] = $this->calculated['boundary_box'];
220 $this->init_data();
221 $this->init_x_ticks();
222 $this->init_y_ticks();
225 function draw_text() {
226 $colour = $this->parameter['outer_background'];
227 if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'fill'); // graph background
229 // draw border around image
230 $colour = $this->parameter['outer_border'];
231 if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'box'); // graph border
233 $this->draw_title();
234 $this->draw_x_label();
235 $this->draw_y_label_left();
236 $this->draw_y_label_right();
237 $this->draw_x_axis();
238 $this->draw_y_axis();
239 if ($this->calculated['y_axis_left']['has_data']) $this->draw_zero_axis_left(); // either draw zero axis on left
240 else if ($this->calculated['y_axis_right']['has_data']) $this->draw_zero_axis_right(); // ... or right.
241 $this->draw_legend();
243 // draw border around plot area
244 $colour = $this->parameter['inner_background'];
245 if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, 'fill'); // graph background
247 // draw border around image
248 $colour = $this->parameter['inner_border'];
249 if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, $this->parameter['inner_border_type']); // graph border
252 function draw_stack() {
253 $this->init();
254 $this->draw_text();
256 $yOrder = $this->y_order; // save y_order data.
257 // iterate over each data set. order is very important if you want to see data correctly. remember shadows!!
258 foreach ($yOrder as $set) {
259 $this->y_order = array($set);
260 $this->init_data();
261 $this->draw_data();
263 $this->y_order = $yOrder; // revert y_order data.
265 $this->output();
268 function draw() {
269 $this->init();
270 $this->draw_text();
271 $this->draw_data();
272 $this->output();
275 // draw a data set
276 function draw_set($order, $set, $offset) {
277 if ($offset) @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
278 else $colour = $this->y_format[$set]['colour'];
279 @$this->init_variable($point, $this->y_format[$set]['point'], 'none');
280 @$this->init_variable($pointSize, $this->y_format[$set]['point_size'], $this->parameter['point_size']);
281 @$this->init_variable($line, $this->y_format[$set]['line'], 'none');
282 @$this->init_variable($brushType, $this->y_format[$set]['brush_type'], $this->parameter['brush_type']);
283 @$this->init_variable($brushSize, $this->y_format[$set]['brush_size'], $this->parameter['brush_size']);
284 @$this->init_variable($bar, $this->y_format[$set]['bar'], 'none');
285 @$this->init_variable($barSize, $this->y_format[$set]['bar_size'], $this->parameter['bar_size']);
286 @$this->init_variable($area, $this->y_format[$set]['area'], 'none');
288 $lastX = 0;
289 $lastY = 'none';
290 $fromX = 0;
291 $fromY = 'none';
293 //print "set $set<br />";
294 //expand_pre($this->calculated['y_plot']);
296 foreach ($this->x_data as $index => $x) {
297 //print "index $index<br />";
298 $thisY = $this->calculated['y_plot'][$set][$index];
299 $thisX = $this->calculated['x_plot'][$index];
301 //print "$thisX, $thisY <br />";
303 if (($bar!='none') && (string)$thisY != 'none') {
304 if ($relatedset = $this->offset_relation[$set]) { // Moodle
305 $yoffset = $this->calculated['y_plot'][$relatedset][$index]; // Moodle
306 } else { // Moodle
307 $yoffset = 0; // Moodle
308 } // Moodle
309 //$this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set); // Moodle
310 $this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set, $yoffset); // Moodle
313 if (($area!='none') && (((string)$lastY != 'none') && ((string)$thisY != 'none')))
314 $this->area($lastX, $lastY, $thisX, $thisY, $area, $colour, $offset);
316 if (($point!='none') && (string)$thisY != 'none') $this->plot($thisX, $thisY, $point, $pointSize, $colour, $offset);
318 if (($line!='none') && ((string)$thisY != 'none')) {
319 if ((string)$fromY != 'none')
320 $this->line($fromX, $fromY, $thisX, $thisY, $line, $brushType, $brushSize, $colour, $offset);
322 $fromY = $thisY; // start next line from here
323 $fromX = $thisX; // ...
324 } else {
325 $fromY = 'none';
326 $fromX = 'none';
329 $lastX = $thisX;
330 $lastY = $thisY;
334 function draw_data() {
335 // cycle thru y data to be plotted
336 // first check for drop shadows...
337 foreach ($this->y_order as $order => $set) {
338 @$this->init_variable($offset, $this->y_format[$set]['shadow_offset'], $this->parameter['shadow_offset']);
339 @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
340 if ($colour != 'none') $this->draw_set($order, $set, $offset);
344 // then draw data
345 foreach ($this->y_order as $order => $set) {
346 $this->draw_set($order, $set, 0);
350 function draw_legend() {
351 $position = $this->parameter['legend'];
352 if ($position == 'none') return; // abort if no border
354 $borderColour = $this->parameter['legend_border'];
355 $offset = $this->parameter['legend_offset'];
356 $padding = $this->parameter['legend_padding'];
357 $height = $this->calculated['legend']['boundary_box_all']['height'];
358 $width = $this->calculated['legend']['boundary_box_all']['width'];
359 $graphTop = $this->calculated['boundary_box']['top'];
360 $graphBottom = $this->calculated['boundary_box']['bottom'];
361 $graphLeft = $this->calculated['boundary_box']['left'];
362 $graphRight = $this->calculated['boundary_box']['right'];
363 $outsideRight = $this->calculated['outer_border']['right'];
364 $outsideBottom = $this->calculated['outer_border']['bottom'];
365 switch ($position) {
366 case 'top-left':
367 $top = $graphTop + $offset;
368 $bottom = $graphTop + $height + $offset;
369 $left = $graphLeft + $offset;
370 $right = $graphLeft + $width + $offset;
372 break;
373 case 'top-right':
374 $top = $graphTop + $offset;
375 $bottom = $graphTop + $height + $offset;
376 $left = $graphRight - $width - $offset;
377 $right = $graphRight - $offset;
379 break;
380 case 'bottom-left':
381 $top = $graphBottom - $height - $offset;
382 $bottom = $graphBottom - $offset;
383 $left = $graphLeft + $offset;
384 $right = $graphLeft + $width + $offset;
386 break;
387 case 'bottom-right':
388 $top = $graphBottom - $height - $offset;
389 $bottom = $graphBottom - $offset;
390 $left = $graphRight - $width - $offset;
391 $right = $graphRight - $offset;
392 break;
394 case 'outside-top' :
395 $top = $graphTop;
396 $bottom = $graphTop + $height;
397 $left = $outsideRight - $width - $offset;
398 $right = $outsideRight - $offset;
399 break;
401 case 'outside-bottom' :
402 $top = $graphBottom - $height;
403 $bottom = $graphBottom;
404 $left = $outsideRight - $width - $offset;
405 $right = $outsideRight - $offset;
406 break;
408 case 'outside-left' :
409 $top = $outsideBottom - $height - $offset;
410 $bottom = $outsideBottom - $offset;
411 $left = $graphLeft;
412 $right = $graphLeft + $width;
413 break;
415 case 'outside-right' :
416 $top = $outsideBottom - $height - $offset;
417 $bottom = $outsideBottom - $offset;
418 $left = $graphRight - $width;
419 $right = $graphRight;
420 break;
421 default: // default is top left. no particular reason.
422 $top = $this->calculated['boundary_box']['top'];
423 $bottom = $this->calculated['boundary_box']['top'] + $this->calculated['legend']['boundary_box_all']['height'];
424 $left = $this->calculated['boundary_box']['left'];
425 $right = $this->calculated['boundary_box']['right'] + $this->calculated['legend']['boundary_box_all']['width'];
428 // legend border
429 if($borderColour!='none') $this->draw_rectangle(array('top' => $top,
430 'left' => $left,
431 'bottom' => $bottom,
432 'right' => $right), $this->parameter['legend_border'], 'box');
434 // legend text
435 $legendText = array('points' => $this->parameter['legend_size'],
436 'angle' => 0,
437 'font' => $this->parameter['legend_font'],
438 'colour' => $this->parameter['legend_colour']);
440 $box = $this->calculated['legend']['boundary_box_max']['height']; // use max height for legend square size.
441 $x = $left + $padding;
442 $x_text = $x + $box * 2;
443 $y = $top + $padding;
445 foreach ($this->y_order as $set) {
446 $legendText['text'] = $this->calculated['legend']['text'][$set];
447 if ($legendText['text'] != 'none') {
448 // if text exists then draw box and text
449 $boxColour = $this->colour[$this->y_format[$set]['colour']];
451 // draw box
452 ImageFilledRectangle($this->image, $x, $y, $x + $box, $y + $box, $boxColour);
454 // draw text
455 $coords = array('x' => $x + $box * 2, 'y' => $y, 'reference' => 'top-left');
456 $legendText['boundary_box'] = $this->calculated['legend']['boundary_box'][$set];
457 $this->update_boundaryBox($legendText['boundary_box'], $coords);
458 $this->print_TTF($legendText);
459 $y += $padding + $box;
465 function draw_y_label_right() {
466 if (!$this->parameter['y_label_right']) return;
467 $x = $this->calculated['boundary_box']['right'] + $this->parameter['y_inner_padding'];
468 if ($this->parameter['y_axis_text_right']) $x += $this->calculated['y_axis_right']['boundary_box_max']['width']
469 + $this->calculated['right_inner_padding'];
470 $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
472 $label = $this->calculated['y_label_right'];
473 $coords = array('x' => $x, 'y' => $y, 'reference' => 'left-center');
474 $this->update_boundaryBox($label['boundary_box'], $coords);
475 $this->print_TTF($label);
479 function draw_y_label_left() {
480 if (!$this->parameter['y_label_left']) return;
481 $x = $this->calculated['boundary_box']['left'] - $this->parameter['y_inner_padding'];
482 if ($this->parameter['y_axis_text_left']) $x -= $this->calculated['y_axis_left']['boundary_box_max']['width']
483 + $this->calculated['left_inner_padding'];
484 $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
486 $label = $this->calculated['y_label_left'];
487 $coords = array('x' => $x, 'y' => $y, 'reference' => 'right-center');
488 $this->update_boundaryBox($label['boundary_box'], $coords);
489 $this->print_TTF($label);
492 function draw_title() {
493 if (!$this->parameter['title']) return;
494 //$y = $this->calculated['outside_border']['top'] + $this->parameter['outer_padding'];
495 $y = $this->calculated['boundary_box']['top'] - $this->parameter['outer_padding'];
496 $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
497 $label = $this->calculated['title'];
498 $coords = array('x' => $x, 'y' => $y, 'reference' => 'bottom-center');
499 $this->update_boundaryBox($label['boundary_box'], $coords);
500 $this->print_TTF($label);
503 function draw_x_label() {
504 if (!$this->parameter['x_label']) return;
505 $y = $this->calculated['boundary_box']['bottom'] + $this->parameter['x_inner_padding'];
506 if ($this->parameter['x_axis_text']) $y += $this->calculated['x_axis']['boundary_box_max']['height']
507 + $this->calculated['bottom_inner_padding'];
508 $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
509 $label = $this->calculated['x_label'];
510 $coords = array('x' => $x, 'y' => $y, 'reference' => 'top-center');
511 $this->update_boundaryBox($label['boundary_box'], $coords);
512 $this->print_TTF($label);
515 function draw_zero_axis_left() {
516 $colour = $this->parameter['zero_axis'];
517 if ($colour == 'none') return;
518 // draw zero axis on left hand side
519 $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top'] + ($this->calculated['y_axis_left']['max'] * $this->calculated['y_axis_left']['factor']));
520 ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
523 function draw_zero_axis_right() {
524 $colour = $this->parameter['zero_axis'];
525 if ($colour == 'none') return;
526 // draw zero axis on right hand side
527 $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top'] + ($this->calculated['y_axis_right']['max'] * $this->calculated['y_axis_right']['factor']));
528 ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
531 function draw_x_axis() {
532 $gridColour = $this->colour[$this->parameter['grid_colour']];
533 $tickColour = $this->colour[$this->parameter['x_ticks_colour']];
534 $axis_colour = $this->parameter['axis_colour'];
535 $xGrid = $this->parameter['x_grid'];
536 $gridTop = $this->calculated['boundary_box']['top'];
537 $gridBottom = $this->calculated['boundary_box']['bottom'];
539 if ($this->parameter['tick_length'] >= 0) {
540 $tickTop = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
541 $tickBottom = $this->calculated['boundary_box']['bottom'];
542 $textBottom = $tickBottom + $this->calculated['bottom_inner_padding'];
543 } else {
544 $tickTop = $this->calculated['boundary_box']['bottom'];
545 $tickBottom = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
546 $textBottom = $tickBottom + $this->calculated['bottom_inner_padding'];
549 $axis_font = $this->parameter['axis_font'];
550 $axis_size = $this->parameter['axis_size'];
551 $axis_angle = $this->parameter['x_axis_angle'];
553 if ($axis_angle == 0) $reference = 'top-center';
554 if ($axis_angle > 0) $reference = 'top-right';
555 if ($axis_angle < 0) $reference = 'top-left';
556 if ($axis_angle == 90) $reference = 'top-center';
558 //generic tag information. applies to all axis text.
559 $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
561 foreach ($this->calculated['x_axis']['tick_x'] as $set => $tickX) {
562 // draw x grid if colour specified
563 if ($xGrid != 'none') {
564 switch ($xGrid) {
565 case 'line':
566 ImageLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
567 break;
568 case 'dash':
569 ImageDashedLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
570 break;
574 if ($this->parameter['x_axis_text'] && !($set % $this->parameter['x_axis_text'])) { // test if tick should be displayed
575 // draw tick
576 if ($tickColour != 'none')
577 ImageLine($this->image, round($tickX), round($tickTop), round($tickX), round($tickBottom), $tickColour);
579 // draw axis text
580 $coords = array('x' => $tickX, 'y' => $textBottom, 'reference' => $reference);
581 $axisTag['text'] = $this->calculated['x_axis']['text'][$set];
582 $axisTag['boundary_box'] = $this->calculated['x_axis']['boundary_box'][$set];
583 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
584 $this->print_TTF($axisTag);
589 function draw_y_axis() {
590 $gridColour = $this->colour[$this->parameter['grid_colour']];
591 $tickColour = $this->colour[$this->parameter['y_ticks_colour']];
592 $axis_colour = $this->parameter['axis_colour'];
593 $yGrid = $this->parameter['y_grid'];
594 $gridLeft = $this->calculated['boundary_box']['left'];
595 $gridRight = $this->calculated['boundary_box']['right'];
597 // axis font information
598 $axis_font = $this->parameter['axis_font'];
599 $axis_size = $this->parameter['axis_size'];
600 $axis_angle = $this->parameter['y_axis_angle'];
601 $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
604 if ($this->calculated['y_axis_left']['has_data']) {
605 // LEFT HAND SIDE
606 // left and right coords for ticks
607 if ($this->parameter['tick_length'] >= 0) {
608 $tickLeft = $this->calculated['boundary_box']['left'];
609 $tickRight = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
610 } else {
611 $tickLeft = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
612 $tickRight = $this->calculated['boundary_box']['left'];
614 $textRight = $tickLeft - $this->calculated['left_inner_padding'];
616 if ($axis_angle == 0) $reference = 'right-center';
617 if ($axis_angle > 0) $reference = 'right-top';
618 if ($axis_angle < 0) $reference = 'right-bottom';
619 if ($axis_angle == 90) $reference = 'right-center';
621 foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
622 // draw y grid if colour specified
623 if ($yGrid != 'none') {
624 switch ($yGrid) {
625 case 'line':
626 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
627 break;
628 case 'dash':
629 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
630 break;
634 // y axis text
635 if ($this->parameter['y_axis_text_left'] && !($set % $this->parameter['y_axis_text_left'])) { // test if tick should be displayed
636 // draw tick
637 if ($tickColour != 'none')
638 ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
640 // draw axis text...
641 $coords = array('x' => $textRight, 'y' => $tickY, 'reference' => $reference);
642 $axisTag['text'] = $this->calculated['y_axis_left']['text'][$set];
643 $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
644 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
645 $this->print_TTF($axisTag);
650 if ($this->calculated['y_axis_right']['has_data']) {
651 // RIGHT HAND SIDE
652 // left and right coords for ticks
653 if ($this->parameter['tick_length'] >= 0) {
654 $tickLeft = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
655 $tickRight = $this->calculated['boundary_box']['right'];
656 } else {
657 $tickLeft = $this->calculated['boundary_box']['right'];
658 $tickRight = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
660 $textLeft = $tickRight+ $this->calculated['left_inner_padding'];
662 if ($axis_angle == 0) $reference = 'left-center';
663 if ($axis_angle > 0) $reference = 'left-bottom';
664 if ($axis_angle < 0) $reference = 'left-top';
665 if ($axis_angle == 90) $reference = 'left-center';
667 foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
668 if (!$this->calculated['y_axis_left']['has_data'] && $yGrid != 'none') { // draw grid if not drawn already (above)
669 switch ($yGrid) {
670 case 'line':
671 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
672 break;
673 case 'dash':
674 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
675 break;
679 if ($this->parameter['y_axis_text_right'] && !($set % $this->parameter['y_axis_text_right'])) { // test if tick should be displayed
680 // draw tick
681 if ($tickColour != 'none')
682 ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
684 // draw axis text...
685 $coords = array('x' => $textLeft, 'y' => $tickY, 'reference' => $reference);
686 $axisTag['text'] = $this->calculated['y_axis_right']['text'][$set];
687 $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
688 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
689 $this->print_TTF($axisTag);
695 function init_data() {
696 $this->calculated['y_plot'] = array(); // array to hold pixel plotting coords for y axis
697 $height = $this->calculated['boundary_box']['bottom'] - $this->calculated['boundary_box']['top'];
698 $width = $this->calculated['boundary_box']['right'] - $this->calculated['boundary_box']['left'];
700 // calculate pixel steps between axis ticks.
701 $this->calculated['y_axis']['step'] = $height / ($this->parameter['y_axis_gridlines'] - 1);
703 // calculate x ticks spacing taking into account x offset for ticks.
704 $extraTick = 2 * $this->parameter['x_offset']; // extra tick to account for padding
705 $numTicks = $this->calculated['x_axis']['num_ticks'] - 1; // number of x ticks
707 // Hack by rodger to avoid division by zero, see bug 1231
708 if ($numTicks==0) $numTicks=1;
710 $this->calculated['x_axis']['step'] = $width / ($numTicks + $extraTick);
711 $widthPlot = $width - ($this->calculated['x_axis']['step'] * $extraTick);
712 $this->calculated['x_axis']['step'] = $widthPlot / $numTicks;
714 //calculate factor for transforming x,y physical coords to logical coords for right hand y_axis.
715 $y_range = $this->calculated['y_axis_right']['max'] - $this->calculated['y_axis_right']['min'];
716 $y_range = ($y_range ? $y_range : 1);
717 $this->calculated['y_axis_right']['factor'] = $height / $y_range;
719 //calculate factor for transforming x,y physical coords to logical coords for left hand axis.
720 $yRange = $this->calculated['y_axis_left']['max'] - $this->calculated['y_axis_left']['min'];
721 $yRange = ($yRange ? $yRange : 1);
722 $this->calculated['y_axis_left']['factor'] = $height / $yRange;
723 if ($this->parameter['x_axis_gridlines'] != 'auto') {
724 $xRange = $this->calculated['x_axis']['max'] - $this->calculated['x_axis']['min'];
725 $xRange = ($xRange ? $xRange : 1);
726 $this->calculated['x_axis']['factor'] = $widthPlot / $xRange;
729 //expand_pre($this->calculated['boundary_box']);
730 // cycle thru all data sets...
731 $this->calculated['num_bars'] = 0;
732 foreach ($this->y_order as $order => $set) {
733 // determine how many bars there are
734 if (isset($this->y_format[$set]['bar']) && ($this->y_format[$set]['bar'] != 'none')) {
735 $this->calculated['bar_offset_index'][$set] = $this->calculated['num_bars']; // index to relate bar with data set.
736 $this->calculated['num_bars']++;
739 // calculate y coords for plotting data
740 foreach ($this->x_data as $index => $x) {
741 $this->calculated['y_plot'][$set][$index] = $this->y_data[$set][$index];
743 if ((string)$this->y_data[$set][$index] != 'none') {
745 if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
746 $this->calculated['y_plot'][$set][$index] =
747 round(($this->y_data[$set][$index] - $this->calculated['y_axis_right']['min'])
748 * $this->calculated['y_axis_right']['factor']);
749 } else {
750 //print "$set $index<br />";
751 $this->calculated['y_plot'][$set][$index] =
752 round(($this->y_data[$set][$index] - $this->calculated['y_axis_left']['min'])
753 * $this->calculated['y_axis_left']['factor']);
759 //print "factor ".$this->calculated['x_axis']['factor']."<br />";
760 //expand_pre($this->calculated['x_plot']);
762 // calculate bar parameters if bars are to be drawn.
763 if ($this->calculated['num_bars']) {
764 $xStep = $this->calculated['x_axis']['step'];
765 $totalWidth = $this->calculated['x_axis']['step'] - $this->parameter['bar_spacing'];
766 $barWidth = $totalWidth / $this->calculated['num_bars'];
768 $barX = ($barWidth - $totalWidth) / 2; // starting x offset
769 for ($i=0; $i < $this->calculated['num_bars']; $i++) {
770 $this->calculated['bar_offset_x'][$i] = $barX;
771 $barX += $barWidth; // add width of bar to x offset.
773 $this->calculated['bar_width'] = $barWidth;
779 function init_x_ticks() {
780 // get coords for x axis ticks and data plots
781 //$xGrid = $this->parameter['x_grid'];
782 $xStep = $this->calculated['x_axis']['step'];
783 $ticksOffset = $this->parameter['x_offset']; // where to start drawing ticks relative to y axis.
784 $gridLeft = $this->calculated['boundary_box']['left'] + ($xStep * $ticksOffset); // grid x start
785 $tickX = $gridLeft; // tick x coord
787 foreach ($this->calculated['x_axis']['text'] as $set => $value) {
788 //print "index: $set<br />";
789 // x tick value
790 $this->calculated['x_axis']['tick_x'][$set] = $tickX;
791 // if num ticks is auto then x plot value is same as x tick
792 if ($this->parameter['x_axis_gridlines'] == 'auto') $this->calculated['x_plot'][$set] = round($tickX);
793 //print $this->calculated['x_plot'][$set].'<br />';
794 $tickX += $xStep;
797 //print "xStep: $xStep <br />";
798 // if numeric x axis then calculate x coords for each data point. this is seperate from x ticks.
799 $gridX = $gridLeft;
800 if (empty($this->calculated['x_axis']['factor'])) {
801 $this->calculated['x_axis']['factor'] = 0;
803 if (empty($this->calculated['x_axis']['min'])) {
804 $this->calculated['x_axis']['min'] = 0;
806 $factor = $this->calculated['x_axis']['factor'];
807 $min = $this->calculated['x_axis']['min'];
809 if ($this->parameter['x_axis_gridlines'] != 'auto') {
810 foreach ($this->x_data as $index => $x) {
811 //print "index: $index, x: $x<br />";
812 $offset = $x - $this->calculated['x_axis']['min'];
814 //$gridX = ($offset * $this->calculated['x_axis']['factor']);
815 //print "offset: $offset <br />";
816 //$this->calculated['x_plot'][$set] = $gridLeft + ($offset * $this->calculated['x_axis']['factor']);
818 $this->calculated['x_plot'][$index] = $gridLeft + ($x - $min) * $factor;
820 //print $this->calculated['x_plot'][$set].'<br />';
823 //expand_pre($this->calculated['boundary_box']);
824 //print "factor ".$this->calculated['x_axis']['factor']."<br />";
825 //expand_pre($this->calculated['x_plot']);
828 function init_y_ticks() {
829 // get coords for y axis ticks
831 $yStep = $this->calculated['y_axis']['step'];
832 $gridBottom = $this->calculated['boundary_box']['bottom'];
833 $tickY = $gridBottom; // tick y coord
835 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) {
836 $this->calculated['y_axis']['tick_y'][$i] = $tickY;
837 $tickY -= $yStep;
842 function init_labels() {
843 if ($this->parameter['title']) {
844 $size = $this->get_boundaryBox(
845 array('points' => $this->parameter['title_size'],
846 'angle' => 0,
847 'font' => $this->parameter['title_font'],
848 'text' => $this->parameter['title']));
849 $this->calculated['title']['boundary_box'] = $size;
850 $this->calculated['title']['text'] = $this->parameter['title'];
851 $this->calculated['title']['font'] = $this->parameter['title_font'];
852 $this->calculated['title']['points'] = $this->parameter['title_size'];
853 $this->calculated['title']['colour'] = $this->parameter['title_colour'];
854 $this->calculated['title']['angle'] = 0;
856 $this->calculated['boundary_box']['top'] += $size['height'] + $this->parameter['outer_padding'];
857 //$this->calculated['boundary_box']['top'] += $size['height'];
859 } else $this->calculated['title']['boundary_box'] = $this->get_null_size();
861 if ($this->parameter['y_label_left']) {
862 $this->calculated['y_label_left']['text'] = $this->parameter['y_label_left'];
863 $this->calculated['y_label_left']['angle'] = $this->parameter['y_label_angle'];
864 $this->calculated['y_label_left']['font'] = $this->parameter['label_font'];
865 $this->calculated['y_label_left']['points'] = $this->parameter['label_size'];
866 $this->calculated['y_label_left']['colour'] = $this->parameter['label_colour'];
868 $size = $this->get_boundaryBox($this->calculated['y_label_left']);
869 $this->calculated['y_label_left']['boundary_box'] = $size;
870 //$this->calculated['boundary_box']['left'] += $size['width'] + $this->parameter['inner_padding'];
871 $this->calculated['boundary_box']['left'] += $size['width'];
873 } else $this->calculated['y_label_left']['boundary_box'] = $this->get_null_size();
875 if ($this->parameter['y_label_right']) {
876 $this->calculated['y_label_right']['text'] = $this->parameter['y_label_right'];
877 $this->calculated['y_label_right']['angle'] = $this->parameter['y_label_angle'];
878 $this->calculated['y_label_right']['font'] = $this->parameter['label_font'];
879 $this->calculated['y_label_right']['points'] = $this->parameter['label_size'];
880 $this->calculated['y_label_right']['colour'] = $this->parameter['label_colour'];
882 $size = $this->get_boundaryBox($this->calculated['y_label_right']);
883 $this->calculated['y_label_right']['boundary_box'] = $size;
884 //$this->calculated['boundary_box']['right'] -= $size['width'] + $this->parameter['inner_padding'];
885 $this->calculated['boundary_box']['right'] -= $size['width'];
887 } else $this->calculated['y_label_right']['boundary_box'] = $this->get_null_size();
889 if ($this->parameter['x_label']) {
890 $this->calculated['x_label']['text'] = $this->parameter['x_label'];
891 $this->calculated['x_label']['angle'] = $this->parameter['x_label_angle'];
892 $this->calculated['x_label']['font'] = $this->parameter['label_font'];
893 $this->calculated['x_label']['points'] = $this->parameter['label_size'];
894 $this->calculated['x_label']['colour'] = $this->parameter['label_colour'];
896 $size = $this->get_boundaryBox($this->calculated['x_label']);
897 $this->calculated['x_label']['boundary_box'] = $size;
898 //$this->calculated['boundary_box']['bottom'] -= $size['height'] + $this->parameter['inner_padding'];
899 $this->calculated['boundary_box']['bottom'] -= $size['height'];
901 } else $this->calculated['x_label']['boundary_box'] = $this->get_null_size();
906 function init_legend() {
907 $this->calculated['legend'] = array(); // array to hold calculated values for legend.
908 //$this->calculated['legend']['boundary_box_max'] = array('height' => 0, 'width' => 0);
909 $this->calculated['legend']['boundary_box_max'] = $this->get_null_size();
910 if ($this->parameter['legend'] == 'none') return;
912 $position = $this->parameter['legend'];
913 $numSets = 0; // number of data sets with legends.
914 $sumTextHeight = 0; // total of height of all legend text items.
915 $width = 0;
916 $height = 0;
918 foreach ($this->y_order as $set) {
919 $text = isset($this->y_format[$set]['legend']) ? $this->y_format[$set]['legend'] : 'none';
920 $size = $this->get_boundaryBox(
921 array('points' => $this->parameter['legend_size'],
922 'angle' => 0,
923 'font' => $this->parameter['legend_font'],
924 'text' => $text));
926 $this->calculated['legend']['boundary_box'][$set] = $size;
927 $this->calculated['legend']['text'][$set] = $text;
928 //$this->calculated['legend']['font'][$set] = $this->parameter['legend_font'];
929 //$this->calculated['legend']['points'][$set] = $this->parameter['legend_size'];
930 //$this->calculated['legend']['angle'][$set] = 0;
932 if ($text && $text!='none') {
933 $numSets++;
934 $sumTextHeight += $size['height'];
937 if ($size['width'] > $this->calculated['legend']['boundary_box_max']['width'])
938 $this->calculated['legend']['boundary_box_max'] = $size;
941 $offset = $this->parameter['legend_offset']; // offset in pixels of legend box from graph border.
942 $padding = $this->parameter['legend_padding']; // padding in pixels around legend text.
943 $textWidth = $this->calculated['legend']['boundary_box_max']['width']; // width of largest legend item.
944 $textHeight = $this->calculated['legend']['boundary_box_max']['height']; // use height as size to use for colour square in legend.
945 $width = $padding * 2 + $textWidth + $textHeight * 2; // left and right padding + maximum text width + space for square
946 $height = ($padding + $textHeight) * $numSets + $padding; // top and bottom padding + padding between text + text.
949 $this->calculated['legend']['boundary_box_all'] = array('width' => $width,
950 'height' => $height,
951 'offset' => $offset,
952 'reference' => $position);
954 switch ($position) { // move in right or bottom if legend is outside data plotting area.
955 case 'outside-top' :
956 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
957 break;
959 case 'outside-bottom' :
960 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
961 break;
963 case 'outside-left' :
964 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
965 break;
967 case 'outside-right' :
968 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
969 break;
973 function init_y_axis() {
974 $this->calculated['y_axis_left'] = array(); // array to hold calculated values for y_axis on left.
975 $this->calculated['y_axis_left']['boundary_box_max'] = $this->get_null_size();
976 $this->calculated['y_axis_right'] = array(); // array to hold calculated values for y_axis on right.
977 $this->calculated['y_axis_right']['boundary_box_max'] = $this->get_null_size();
979 $axis_font = $this->parameter['axis_font'];
980 $axis_size = $this->parameter['axis_size'];
981 $axis_colour = $this->parameter['axis_colour'];
982 $axis_angle = $this->parameter['y_axis_angle'];
983 $y_tick_labels = $this->y_tick_labels;
985 $this->calculated['y_axis_left']['has_data'] = FALSE;
986 $this->calculated['y_axis_right']['has_data'] = FALSE;
988 // find min and max y values.
989 $minLeft = $this->parameter['y_min_left'];
990 $maxLeft = $this->parameter['y_max_left'];
991 $minRight = $this->parameter['y_min_right'];
992 $maxRight = $this->parameter['y_max_right'];
993 $dataLeft = array();
994 $dataRight = array();
995 foreach ($this->y_order as $order => $set) {
996 if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
997 $this->calculated['y_axis_right']['has_data'] = TRUE;
998 $dataRight = array_merge($dataRight, $this->y_data[$set]);
999 } else {
1000 $this->calculated['y_axis_left']['has_data'] = TRUE;
1001 $dataLeft = array_merge($dataLeft, $this->y_data[$set]);
1004 $dataLeftRange = $this->find_range($dataLeft, $minLeft, $maxLeft, $this->parameter['y_resolution_left']);
1005 $dataRightRange = $this->find_range($dataRight, $minRight, $maxRight, $this->parameter['y_resolution_right']);
1006 $minLeft = $dataLeftRange['min'];
1007 $maxLeft = $dataLeftRange['max'];
1008 $minRight = $dataRightRange['min'];
1009 $maxRight = $dataRightRange['max'];
1011 $this->calculated['y_axis_left']['min'] = $minLeft;
1012 $this->calculated['y_axis_left']['max'] = $maxLeft;
1013 $this->calculated['y_axis_right']['min'] = $minRight;
1014 $this->calculated['y_axis_right']['max'] = $maxRight;
1016 $stepLeft = ($maxLeft - $minLeft) / ($this->parameter['y_axis_gridlines'] - 1);
1017 $startLeft = $minLeft;
1018 $step_right = ($maxRight - $minRight) / ($this->parameter['y_axis_gridlines'] - 1);
1019 $start_right = $minRight;
1021 if ($this->parameter['y_axis_text_left']) {
1022 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1023 // left y axis
1024 if ($y_tick_labels) {
1025 $value = $y_tick_labels[$i];
1026 } else {
1027 $value = number_format($startLeft, $this->parameter['y_decimal_left'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1029 $this->calculated['y_axis_left']['data'][$i] = $startLeft;
1030 $this->calculated['y_axis_left']['text'][$i] = $value; // text is formatted raw data
1032 $size = $this->get_boundaryBox(
1033 array('points' => $axis_size,
1034 'font' => $axis_font,
1035 'angle' => $axis_angle,
1036 'colour' => $axis_colour,
1037 'text' => $value));
1038 $this->calculated['y_axis_left']['boundary_box'][$i] = $size;
1040 if ($size['height'] > $this->calculated['y_axis_left']['boundary_box_max']['height'])
1041 $this->calculated['y_axis_left']['boundary_box_max']['height'] = $size['height'];
1042 if ($size['width'] > $this->calculated['y_axis_left']['boundary_box_max']['width'])
1043 $this->calculated['y_axis_left']['boundary_box_max']['width'] = $size['width'];
1045 $startLeft += $stepLeft;
1047 $this->calculated['boundary_box']['left'] += $this->calculated['y_axis_left']['boundary_box_max']['width']
1048 + $this->parameter['y_inner_padding'];
1051 if ($this->parameter['y_axis_text_right']) {
1052 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1053 // right y axis
1054 $value = number_format($start_right, $this->parameter['y_decimal_right'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1055 $this->calculated['y_axis_right']['data'][$i] = $start_right;
1056 $this->calculated['y_axis_right']['text'][$i] = $value; // text is formatted raw data
1057 $size = $this->get_boundaryBox(
1058 array('points' => $axis_size,
1059 'font' => $axis_font,
1060 'angle' => $axis_angle,
1061 'colour' => $axis_colour,
1062 'text' => $value));
1063 $this->calculated['y_axis_right']['boundary_box'][$i] = $size;
1065 if ($size['height'] > $this->calculated['y_axis_right']['boundary_box_max']['height'])
1066 $this->calculated['y_axis_right']['boundary_box_max'] = $size;
1067 if ($size['width'] > $this->calculated['y_axis_right']['boundary_box_max']['width'])
1068 $this->calculated['y_axis_right']['boundary_box_max']['width'] = $size['width'];
1070 $start_right += $step_right;
1072 $this->calculated['boundary_box']['right'] -= $this->calculated['y_axis_right']['boundary_box_max']['width']
1073 + $this->parameter['y_inner_padding'];
1077 function init_x_axis() {
1078 $this->calculated['x_axis'] = array(); // array to hold calculated values for x_axis.
1079 $this->calculated['x_axis']['boundary_box_max'] = array('height' => 0, 'width' => 0);
1081 $axis_font = $this->parameter['axis_font'];
1082 $axis_size = $this->parameter['axis_size'];
1083 $axis_colour = $this->parameter['axis_colour'];
1084 $axis_angle = $this->parameter['x_axis_angle'];
1086 // check whether to treat x axis as numeric
1087 if ($this->parameter['x_axis_gridlines'] == 'auto') { // auto means text based x_axis, not numeric...
1088 $this->calculated['x_axis']['num_ticks'] = sizeof($this->x_data);
1089 $data = $this->x_data;
1090 for ($i=0; $i < $this->calculated['x_axis']['num_ticks']; $i++) {
1091 $value = array_shift($data); // grab value from begin of array
1092 $this->calculated['x_axis']['data'][$i] = $value;
1093 $this->calculated['x_axis']['text'][$i] = $value; // raw data and text are both the same in this case
1094 $size = $this->get_boundaryBox(
1095 array('points' => $axis_size,
1096 'font' => $axis_font,
1097 'angle' => $axis_angle,
1098 'colour' => $axis_colour,
1099 'text' => $value));
1100 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1101 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1102 $this->calculated['x_axis']['boundary_box_max'] = $size;
1105 } else { // x axis is numeric so find max min values...
1106 $this->calculated['x_axis']['num_ticks'] = $this->parameter['x_axis_gridlines'];
1108 $min = $this->parameter['x_min'];
1109 $max = $this->parameter['x_max'];
1110 $data = array();
1111 $data = $this->find_range($this->x_data, $min, $max, $this->parameter['x_resolution']);
1112 $min = $data['min'];
1113 $max = $data['max'];
1114 $this->calculated['x_axis']['min'] = $min;
1115 $this->calculated['x_axis']['max'] = $max;
1117 $step = ($max - $min) / ($this->calculated['x_axis']['num_ticks'] - 1);
1118 $start = $min;
1120 for ($i = 0; $i < $this->calculated['x_axis']['num_ticks']; $i++) { // calculate x axis text sizes
1121 $value = number_format($start, $this->parameter['xDecimal'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1122 $this->calculated['x_axis']['data'][$i] = $start;
1123 $this->calculated['x_axis']['text'][$i] = $value; // text is formatted raw data
1125 $size = $this->get_boundaryBox(
1126 array('points' => $axis_size,
1127 'font' => $axis_font,
1128 'angle' => $axis_angle,
1129 'colour' => $axis_colour,
1130 'text' => $value));
1131 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1133 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1134 $this->calculated['x_axis']['boundary_box_max'] = $size;
1136 $start += $step;
1139 if ($this->parameter['x_axis_text'])
1140 $this->calculated['boundary_box']['bottom'] -= $this->calculated['x_axis']['boundary_box_max']['height']
1141 + $this->parameter['x_inner_padding'];
1144 // find max and min values for a data array given the resolution.
1145 function find_range($data, $min, $max, $resolution) {
1146 if (sizeof($data) == 0 ) return array('min' => 0, 'max' => 0);
1147 foreach ($data as $key => $value) {
1148 if ($value=='none') continue;
1149 if ($value > $max) $max = $value;
1150 if ($value < $min) $min = $value;
1153 if ($max == 0) {
1154 $factor = 1;
1155 } else {
1156 if ($max < 0) $factor = - pow(10, (floor(log10(abs($max))) + $resolution) );
1157 else $factor = pow(10, (floor(log10(abs($max))) - $resolution) );
1159 if ($factor > 0.1) { // To avoid some wierd rounding errors (Moodle)
1160 $factor = round($factor * 1000.0) / 1000.0; // To avoid some wierd rounding errors (Moodle)
1161 } // To avoid some wierd rounding errors (Moodle)
1163 $max = $factor * @ceil($max / $factor);
1164 $min = $factor * @floor($min / $factor);
1166 //print "max=$max, min=$min<br />";
1168 return array('min' => $min, 'max' => $max);
1171 function graph() {
1172 if (func_num_args() == 2) {
1173 $this->parameter['width'] = func_get_arg(0);
1174 $this->parameter['height'] = func_get_arg(1);
1176 //$this->boundaryBox = array(
1177 $this->calculated['boundary_box'] = array(
1178 'left' => 0,
1179 'top' => 0,
1180 'right' => $this->parameter['width'] - 1,
1181 'bottom' => $this->parameter['height'] - 1);
1183 $this->init_colours();
1185 //ImageColorTransparent($this->image, $this->colour['white']); // colour for transparency
1188 function print_TTF($message) {
1189 $points = $message['points'];
1190 $angle = $message['angle'];
1191 $text = $message['text'];
1192 $colour = $this->colour[$message['colour']];
1193 $font = $this->parameter['path_to_fonts'].$message['font'];
1195 $x = $message['boundary_box']['x'];
1196 $y = $message['boundary_box']['y'];
1197 $offsetX = $message['boundary_box']['offsetX'];
1198 $offsetY = $message['boundary_box']['offsetY'];
1199 $height = $message['boundary_box']['height'];
1200 $width = $message['boundary_box']['width'];
1201 $reference = $message['boundary_box']['reference'];
1203 switch ($reference) {
1204 case 'top-left':
1205 case 'left-top':
1206 $y += $height - $offsetY;
1207 //$y += $offsetY;
1208 $x += $offsetX;
1209 break;
1210 case 'left-center':
1211 $y += ($height / 2) - $offsetY;
1212 $x += $offsetX;
1213 break;
1214 case 'left-bottom':
1215 $y -= $offsetY;
1216 $x += $offsetX;
1217 break;
1218 case 'top-center':
1219 $y += $height - $offsetY;
1220 $x -= ($width / 2) - $offsetX;
1221 break;
1222 case 'top-right':
1223 case 'right-top':
1224 $y += $height - $offsetY;
1225 $x -= $width - $offsetX;
1226 break;
1227 case 'right-center':
1228 $y += ($height / 2) - $offsetY;
1229 $x -= $width - $offsetX;
1230 break;
1231 case 'right-bottom':
1232 $y -= $offsetY;
1233 $x -= $width - $offsetX;
1234 break;
1235 case 'bottom-center':
1236 $y -= $offsetY;
1237 $x -= ($width / 2) - $offsetX;
1238 break;
1239 default:
1240 $y = 0;
1241 $x = 0;
1242 break;
1244 // start of Moodle addition
1245 $textlib = textlib_get_instance();
1246 $text = $textlib->utf8_to_entities($text, true, true); //does not work with hex entities!
1247 // end of Moodle addition
1248 ImageTTFText($this->image, $points, $angle, $x, $y, $colour, $font, $text);
1251 // move boundaryBox to coordinates specified
1252 function update_boundaryBox(&$boundaryBox, $coords) {
1253 $width = $boundaryBox['width'];
1254 $height = $boundaryBox['height'];
1255 $x = $coords['x'];
1256 $y = $coords['y'];
1257 $reference = $coords['reference'];
1258 switch ($reference) {
1259 case 'top-left':
1260 case 'left-top':
1261 $top = $y;
1262 $bottom = $y + $height;
1263 $left = $x;
1264 $right = $x + $width;
1265 break;
1266 case 'left-center':
1267 $top = $y - ($height / 2);
1268 $bottom = $y + ($height / 2);
1269 $left = $x;
1270 $right = $x + $width;
1271 break;
1272 case 'left-bottom':
1273 $top = $y - $height;
1274 $bottom = $y;
1275 $left = $x;
1276 $right = $x + $width;
1277 break;
1278 case 'top-center':
1279 $top = $y;
1280 $bottom = $y + $height;
1281 $left = $x - ($width / 2);
1282 $right = $x + ($width / 2);
1283 break;
1284 case 'right-top':
1285 case 'top-right':
1286 $top = $y;
1287 $bottom = $y + $height;
1288 $left = $x - $width;
1289 $right = $x;
1290 break;
1291 case 'right-center':
1292 $top = $y - ($height / 2);
1293 $bottom = $y + ($height / 2);
1294 $left = $x - $width;
1295 $right = $x;
1296 break;
1297 case 'bottom=right':
1298 case 'right-bottom':
1299 $top = $y - $height;
1300 $bottom = $y;
1301 $left = $x - $width;
1302 $right = $x;
1303 break;
1304 default:
1305 $top = 0;
1306 $bottom = $height;
1307 $left = 0;
1308 $right = $width;
1309 break;
1312 $boundaryBox = array_merge($boundaryBox, array('top' => $top,
1313 'bottom' => $bottom,
1314 'left' => $left,
1315 'right' => $right,
1316 'x' => $x,
1317 'y' => $y,
1318 'reference' => $reference));
1321 function get_null_size() {
1322 return array('width' => 0,
1323 'height' => 0,
1324 'offsetX' => 0,
1325 'offsetY' => 0,
1326 //'fontHeight' => 0
1330 function get_boundaryBox($message) {
1331 $points = $message['points'];
1332 $angle = $message['angle'];
1333 $font = $this->parameter['path_to_fonts'].$message['font'];
1334 $text = $message['text'];
1336 //print ('get_boundaryBox');
1337 //expandPre($message);
1339 // get font size
1340 $bounds = ImageTTFBBox($points, $angle, $font, "W");
1341 if ($angle < 0) {
1342 $fontHeight = abs($bounds[7]-$bounds[1]);
1343 } else if ($angle > 0) {
1344 $fontHeight = abs($bounds[1]-$bounds[7]);
1345 } else {
1346 $fontHeight = abs($bounds[7]-$bounds[1]);
1349 // get boundary box and offsets for printing at an angle
1350 // start of Moodle addition
1351 $textlib = textlib_get_instance();
1352 $text = $textlib->utf8_to_entities($text, true, true); //gd does not work with hex entities!
1353 // end of Moodle addition
1354 $bounds = ImageTTFBBox($points, $angle, $font, $text);
1356 if ($angle < 0) {
1357 $width = abs($bounds[4]-$bounds[0]);
1358 $height = abs($bounds[3]-$bounds[7]);
1359 $offsetY = abs($bounds[3]-$bounds[1]);
1360 $offsetX = 0;
1362 } else if ($angle > 0) {
1363 $width = abs($bounds[2]-$bounds[6]);
1364 $height = abs($bounds[1]-$bounds[5]);
1365 $offsetY = 0;
1366 $offsetX = abs($bounds[0]-$bounds[6]);
1368 } else {
1369 $width = abs($bounds[4]-$bounds[6]);
1370 $height = abs($bounds[7]-$bounds[1]);
1371 $offsetY = $bounds[1];
1372 $offsetX = 0;
1375 //return values
1376 return array('width' => $width,
1377 'height' => $height,
1378 'offsetX' => $offsetX,
1379 'offsetY' => $offsetY,
1380 //'fontHeight' => $fontHeight
1384 function draw_rectangle($border, $colour, $type) {
1385 $colour = $this->colour[$colour];
1386 switch ($type) {
1387 case 'fill': // fill the rectangle
1388 ImageFilledRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1389 break;
1390 case 'box': // all sides
1391 ImageRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1392 break;
1393 case 'axis': // bottom x axis and left y axis
1394 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1395 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1396 break;
1397 case 'y': // left y axis only
1398 case 'y-left':
1399 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1400 break;
1401 case 'y-right': // right y axis only
1402 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1403 break;
1404 case 'x': // bottom x axis only
1405 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1406 break;
1407 case 'u': // u shaped. bottom x axis and both left and right y axis.
1408 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1409 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1410 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1411 break;
1416 function init_colours() {
1417 $this->image = ImageCreate($this->parameter['width'], $this->parameter['height']);
1418 // standard colours
1419 $this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF); // first colour is background colour.
1420 $this->colour['black'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x00);
1421 $this->colour['maroon'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x00);
1422 $this->colour['green'] = ImageColorAllocate ($this->image, 0x00, 0x80, 0x00);
1423 $this->colour['ltgreen'] = ImageColorAllocate ($this->image, 0x52, 0xF1, 0x7F);
1424 $this->colour['ltltgreen']= ImageColorAllocate ($this->image, 0x99, 0xFF, 0x99);
1425 $this->colour['olive'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x00);
1426 $this->colour['navy'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x80);
1427 $this->colour['purple'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x80);
1428 $this->colour['gray'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x80);
1429 $this->colour['red'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0x00);
1430 $this->colour['ltred'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x99);
1431 $this->colour['ltltred'] = ImageColorAllocate ($this->image, 0xFF, 0xCC, 0xCC);
1432 $this->colour['orange'] = ImageColorAllocate ($this->image, 0xFF, 0x66, 0x00);
1433 $this->colour['ltorange'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x66);
1434 $this->colour['ltltorange'] = ImageColorAllocate ($this->image, 0xFF, 0xcc, 0x99);
1435 $this->colour['lime'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0x00);
1436 $this->colour['yellow'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0x00);
1437 $this->colour['blue'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0xFF);
1438 $this->colour['ltblue'] = ImageColorAllocate ($this->image, 0x00, 0xCC, 0xFF);
1439 $this->colour['ltltblue'] = ImageColorAllocate ($this->image, 0x99, 0xFF, 0xFF);
1440 $this->colour['fuchsia'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0xFF);
1441 $this->colour['aqua'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0xFF);
1442 //$this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF);
1443 // shades of gray
1444 $this->colour['grayF0'] = ImageColorAllocate ($this->image, 0xF0, 0xF0, 0xF0);
1445 $this->colour['grayEE'] = ImageColorAllocate ($this->image, 0xEE, 0xEE, 0xEE);
1446 $this->colour['grayDD'] = ImageColorAllocate ($this->image, 0xDD, 0xDD, 0xDD);
1447 $this->colour['grayCC'] = ImageColorAllocate ($this->image, 0xCC, 0xCC, 0xCC);
1448 $this->colour['gray33'] = ImageColorAllocate ($this->image, 0x33, 0x33, 0x33);
1449 $this->colour['gray66'] = ImageColorAllocate ($this->image, 0x66, 0x66, 0x66);
1450 $this->colour['gray99'] = ImageColorAllocate ($this->image, 0x99, 0x99, 0x99);
1452 $this->colour['none'] = 'none';
1453 return true;
1456 function output() {
1457 if ($this->debug) { // for debugging purposes.
1458 //expandPre($this->graph);
1459 //expandPre($this->y_data);
1460 //expandPre($this->x_data);
1461 //expandPre($this->parameter);
1462 } else {
1464 $expiresSeconds = $this->parameter['seconds_to_live'];
1465 $expiresHours = $this->parameter['hours_to_live'];
1467 if ($expiresHours || $expiresSeconds) {
1468 $now = mktime (date("H"),date("i"),date("s"),date("m"),date("d"),date("Y"));
1469 $expires = mktime (date("H")+$expiresHours,date("i"),date("s")+$expiresSeconds,date("m"),date("d"),date("Y"));
1470 $expiresGMT = gmdate('D, d M Y H:i:s', $expires).' GMT';
1471 $lastModifiedGMT = gmdate('D, d M Y H:i:s', $now).' GMT';
1473 Header('Last-modified: '.$lastModifiedGMT);
1474 Header('Expires: '.$expiresGMT);
1477 if ($this->parameter['file_name'] == 'none') {
1478 switch ($this->parameter['output_format']) {
1479 case 'GIF':
1480 Header("Content-type: image/gif"); // GIF??. switch to PNG guys!!
1481 ImageGIF($this->image);
1482 break;
1483 case 'JPEG':
1484 Header("Content-type: image/jpeg"); // JPEG for line art??. included for completeness.
1485 ImageJPEG($this->image);
1486 break;
1487 default:
1488 Header("Content-type: image/png"); // preferred output format
1489 ImagePNG($this->image);
1490 break;
1492 } else {
1493 switch ($this->parameter['output_format']) {
1494 case 'GIF':
1495 ImageGIF($this->image, $this->parameter['file_name'].'.gif');
1496 break;
1497 case 'JPEG':
1498 ImageJPEG($this->image, $this->parameter['file_name'].'.jpg');
1499 break;
1500 default:
1501 ImagePNG($this->image, $this->parameter['file_name'].'.png');
1502 break;
1506 ImageDestroy($this->image);
1508 } // function output
1510 function init_variable(&$variable, $value, $default) {
1511 if (!empty($value)) $variable = $value;
1512 else if (isset($default)) $variable = $default;
1513 else unset($variable);
1516 // plot a point. options include square, circle, diamond, triangle, and dot. offset is used for drawing shadows.
1517 // for diamonds and triangles the size should be an even number to get nice look. if odd the points are crooked.
1518 function plot($x, $y, $type, $size, $colour, $offset) {
1519 //print("drawing point of type: $type, at offset: $offset");
1520 $u = $x + $offset;
1521 $v = $this->calculated['inner_border']['bottom'] - $y + $offset;
1522 $half = $size / 2;
1524 switch ($type) {
1525 case 'square':
1526 ImageFilledRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1527 break;
1528 case 'square-open':
1529 ImageRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1530 break;
1531 case 'circle':
1532 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1533 ImageFillToBorder($this->image, $u, $v, $this->colour[$colour], $this->colour[$colour]);
1534 break;
1535 case 'circle-open':
1536 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1537 break;
1538 case 'diamond':
1539 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1540 break;
1541 case 'diamond-open':
1542 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1543 break;
1544 case 'triangle':
1545 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1546 break;
1547 case 'triangle-open':
1548 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1549 break;
1550 case 'dot':
1551 ImageSetPixel($this->image, $u, $v, $this->colour[$colour]);
1552 break;
1556 function bar($x, $y, $type, $size, $colour, $offset, $index, $yoffset) {
1557 $index_offset = $this->calculated['bar_offset_index'][$index];
1558 if ( $yoffset ) {
1559 $bar_offsetx = 0;
1560 } else {
1561 $bar_offsetx = $this->calculated['bar_offset_x'][$index_offset];
1563 //$this->dbug("drawing bar at offset = $offset : index = $index: bar_offsetx = $bar_offsetx");
1565 $span = ($this->calculated['bar_width'] * $size) / 2;
1566 $x_left = $x + $bar_offsetx - $span;
1567 $x_right = $x + $bar_offsetx + $span;
1569 if ($this->parameter['zero_axis'] != 'none') {
1570 $zero = $this->calculated['zero_axis'];
1571 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1572 $u_left = $x_left + $offset;
1573 $u_right = $x_right + $offset - 1;
1574 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1576 if ($v > $zero) {
1577 $top = $zero +1;
1578 $bottom = $v;
1579 } else {
1580 $top = $v;
1581 $bottom = $zero - 1;
1584 switch ($type) {
1585 case 'open':
1586 //ImageRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1587 if ($v > $zero)
1588 ImageRectangle($this->image, round($u_left), $bottom, round($u_right), $bottom, $this->colour[$colour]);
1589 else
1590 ImageRectangle($this->image, round($u_left), $top, round($u_right), $top, $this->colour[$colour]);
1591 ImageRectangle($this->image, round($u_left), $top, round($u_left), $bottom, $this->colour[$colour]);
1592 ImageRectangle($this->image, round($u_right), $top, round($u_right), $bottom, $this->colour[$colour]);
1593 break;
1594 case 'fill':
1595 ImageFilledRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1596 break;
1599 } else {
1601 $bottom = $this->calculated['boundary_box']['bottom'];
1602 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1603 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1604 $u_left = $x_left + $offset;
1605 $u_right = $x_right + $offset - 1;
1606 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1608 // Moodle addition, plus the function parameter yoffset
1609 if ($yoffset) { // Moodle
1610 $yoffset = $yoffset - round(($bottom - $v) / 2.0); // Moodle
1611 $bottom -= $yoffset; // Moodle
1612 $v -= $yoffset; // Moodle
1613 } // Moodle
1615 switch ($type) {
1616 case 'open':
1617 ImageRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1618 break;
1619 case 'fill':
1620 ImageFilledRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1621 break;
1626 function area($x_start, $y_start, $x_end, $y_end, $type, $colour, $offset) {
1627 //dbug("drawing area type: $type, at offset: $offset");
1628 if ($this->parameter['zero_axis'] != 'none') {
1629 $bottom = $this->calculated['boundary_box']['bottom'];
1630 $zero = $this->calculated['zero_axis'];
1631 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1632 $u_start = $x_start + $offset;
1633 $u_end = $x_end + $offset;
1634 $v_start = $bottom - $y_start + $offset;
1635 $v_end = $bottom - $y_end + $offset;
1636 switch ($type) {
1637 case 'fill':
1638 // draw it this way 'cos the FilledPolygon routine seems a bit buggy.
1639 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1640 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1641 break;
1642 case 'open':
1643 //ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1644 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1645 ImageLine($this->image, $u_start, $v_start, $u_start, $zero, $this->colour[$colour]);
1646 ImageLine($this->image, $u_end, $v_end, $u_end, $zero, $this->colour[$colour]);
1647 break;
1649 } else {
1650 $bottom = $this->calculated['boundary_box']['bottom'];
1651 $u_start = $x_start + $offset;
1652 $u_end = $x_end + $offset;
1653 $v_start = $bottom - $y_start + $offset;
1654 $v_end = $bottom - $y_end + $offset;
1656 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1657 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1658 switch ($type) {
1659 case 'fill':
1660 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1661 break;
1662 case 'open':
1663 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1664 break;
1669 function line($x_start, $y_start, $x_end, $y_end, $type, $brush_type, $brush_size, $colour, $offset) {
1670 //dbug("drawing line of type: $type, at offset: $offset");
1671 $u_start = $x_start + $offset;
1672 $v_start = $this->calculated['boundary_box']['bottom'] - $y_start + $offset;
1673 $u_end = $x_end + $offset;
1674 $v_end = $this->calculated['boundary_box']['bottom'] - $y_end + $offset;
1676 switch ($type) {
1677 case 'brush':
1678 $this->draw_brush_line($u_start, $v_start, $u_end, $v_end, $brush_size, $brush_type, $colour);
1679 break;
1680 case 'line' :
1681 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1682 break;
1683 case 'dash':
1684 ImageDashedLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1685 break;
1689 // function to draw line. would prefer to use gdBrush but this is not supported yet.
1690 function draw_brush_line($x0, $y0, $x1, $y1, $size, $type, $colour) {
1691 //$this->dbug("line: $x0, $y0, $x1, $y1");
1692 $dy = $y1 - $y0;
1693 $dx = $x1 - $x0;
1694 $t = 0;
1695 $watchdog = 1024; // precaution to prevent infinite loops.
1697 $this->draw_brush($x0, $y0, $size, $type, $colour);
1698 if (abs($dx) > abs($dy)) { // slope < 1
1699 //$this->dbug("slope < 1");
1700 $m = $dy / $dx; // compute slope
1701 $t += $y0;
1702 $dx = ($dx < 0) ? -1 : 1;
1703 $m *= $dx;
1704 while (round($x0) != round($x1)) {
1705 if (!$watchdog--) break;
1706 $x0 += $dx; // step to next x value
1707 $t += $m; // add slope to y value
1708 $y = round($t);
1709 //$this->dbug("x0=$x0, x1=$x1, y=$y watchdog=$watchdog");
1710 $this->draw_brush($x0, $y, $size, $type, $colour);
1713 } else { // slope >= 1
1714 //$this->dbug("slope >= 1");
1715 $m = $dx / $dy; // compute slope
1716 $t += $x0;
1717 $dy = ($dy < 0) ? -1 : 1;
1718 $m *= $dy;
1719 while (round($y0) != round($y1)) {
1720 if (!$watchdog--) break;
1721 $y0 += $dy; // step to next y value
1722 $t += $m; // add slope to x value
1723 $x = round($t);
1724 //$this->dbug("x=$x, y0=$y0, y1=$y1 watchdog=$watchdog");
1725 $this->draw_brush($x, $y0, $size, $type, $colour);
1731 function draw_brush($x, $y, $size, $type, $colour) {
1732 $x = round($x);
1733 $y = round($y);
1734 $half = round($size / 2);
1735 switch ($type) {
1736 case 'circle':
1737 ImageArc($this->image, $x, $y, $size, $size, 0, 360, $this->colour[$colour]);
1738 ImageFillToBorder($this->image, $x, $y, $this->colour[$colour], $this->colour[$colour]);
1739 break;
1740 case 'square':
1741 ImageFilledRectangle($this->image, $x-$half, $y-$half, $x+$half, $y+$half, $this->colour[$colour]);
1742 break;
1743 case 'vertical':
1744 ImageFilledRectangle($this->image, $x, $y-$half, $x+1, $y+$half, $this->colour[$colour]);
1745 break;
1746 case 'horizontal':
1747 ImageFilledRectangle($this->image, $x-$half, $y, $x+$half, $y+1, $this->colour[$colour]);
1748 break;
1749 case 'slash':
1750 ImageFilledPolygon($this->image, array($x+$half, $y-$half,
1751 $x+$half+1, $y-$half,
1752 $x-$half+1, $y+$half,
1753 $x-$half, $y+$half
1754 ), 4, $this->colour[$colour]);
1755 break;
1756 case 'backslash':
1757 ImageFilledPolygon($this->image, array($x-$half, $y-$half,
1758 $x-$half+1, $y-$half,
1759 $x+$half+1, $y+$half,
1760 $x+$half, $y+$half
1761 ), 4, $this->colour[$colour]);
1762 break;
1763 default:
1764 @eval($type); // user can create own brush script.
1768 } // class graph