Merge branch 'MDL-68861-master' of git://github.com/vmdef/moodle
[moodle.git] / lib / graphlib.php
blob64db34af7929eb04cefa703d1cea5ec92a7dabde
1 <?php
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
25 * @package core
26 * @subpackage lib
29 defined('MOODLE_INTERNAL') || die();
31 /* This file contains modifications by Martin Dougiamas
32 * as part of Moodle (http://moodle.com). Modified lines
33 * are marked with "Moodle".
36 /**
37 * @package moodlecore
39 class graph {
40 var $image;
41 var $debug = FALSE; // be careful!!
42 var $calculated = array(); // array of computed values for chart
43 var $parameter = array( // input parameters
44 'width' => 320, // default width of image
45 'height' => 240, // default height of image
46 'file_name' => 'none', // name of file for file to be saved as.
47 // NOTE: no suffix required. this is determined from output_format below.
48 'output_format' => 'PNG', // image output format. 'GIF', 'PNG', 'JPEG'. default 'PNG'.
50 'seconds_to_live' => 0, // expiry time in seconds (for HTTP header)
51 'hours_to_live' => 0, // expiry time in hours (for HTTP header)
52 'path_to_fonts' => 'fonts/', // path to fonts folder. don't forget *trailing* slash!!
53 // for WINDOZE this may need to be the full path, not relative.
55 'title' => 'Graph Title', // text for graph title
56 'title_font' => 'default.ttf', // title text font. don't forget to set 'path_to_fonts' above.
57 'title_size' => 16, // title text point size
58 'title_colour' => 'black', // colour for title text
60 'x_label' => '', // if this is set then this text is printed on bottom axis of graph.
61 'y_label_left' => '', // if this is set then this text is printed on left axis of graph.
62 'y_label_right' => '', // if this is set then this text is printed on right axis of graph.
64 'label_size' => 8, // label text point size
65 'label_font' => 'default.ttf', // label text font. don't forget to set 'path_to_fonts' above.
66 'label_colour' => 'gray33', // label text colour
67 'y_label_angle' => 90, // rotation of y axis label
69 'x_label_angle' => 90, // rotation of y axis label
71 'outer_padding' => 5, // padding around outer text. i.e. title, y label, and x label.
72 'inner_padding' => 0, // padding beteen axis text and graph.
73 'x_inner_padding' => 5, // padding beteen axis text and graph.
74 'y_inner_padding' => 6, // padding beteen axis text and graph.
75 'outer_border' => 'none', // colour of border aound image, or 'none'.
76 'inner_border' => 'black', // colour of border around actual graph, or 'none'.
77 'inner_border_type' => 'box', // 'box' for all four sides, 'axis' for x/y axis only,
78 // 'y' or 'y-left' for y axis only, 'y-right' for right y axis only,
79 // 'x' for x axis only, 'u' for both left and right y axis and x axis.
80 'outer_background' => 'none', // background colour of entire image.
81 'inner_background' => 'none', // background colour of plot area.
83 'y_min_left' => 0, // this will be reset to minimum value if there is a value lower than this.
84 'y_max_left' => 0, // this will be reset to maximum value if there is a value higher than this.
85 'y_min_right' => 0, // this will be reset to minimum value if there is a value lower than this.
86 'y_max_right' => 0, // this will be reset to maximum value if there is a value higher than this.
87 'x_min' => 0, // only used if x axis is numeric.
88 'x_max' => 0, // only used if x axis is numeric.
90 'y_resolution_left' => 1, // scaling for rounding of y axis max value.
91 // if max y value is 8645 then
92 // if y_resolution is 0, then y_max becomes 9000.
93 // if y_resolution is 1, then y_max becomes 8700.
94 // if y_resolution is 2, then y_max becomes 8650.
95 // if y_resolution is 3, then y_max becomes 8645.
96 // get it?
97 'y_decimal_left' => 0, // number of decimal places for y_axis text.
98 'y_resolution_right' => 2, // ... same for right hand side
99 'y_decimal_right' => 0, // ... same for right hand side
100 'x_resolution' => 2, // only used if x axis is numeric.
101 'x_decimal' => 0, // only used if x axis is numeric.
103 'point_size' => 4, // default point size. use even number for diamond or triangle to get nice look.
104 'brush_size' => 4, // default brush size for brush line.
105 'brush_type' => 'circle', // type of brush to use to draw line. choose from the following
106 // 'circle', 'square', 'horizontal', 'vertical', 'slash', 'backslash'
107 'bar_size' => 0.8, // size of bar to draw. <1 bars won't touch
108 // 1 is full width - i.e. bars will touch.
109 // >1 means bars will overlap.
110 'bar_spacing' => 10, // space in pixels between group of bars for each x value.
111 'shadow_offset' => 3, // draw shadow at this offset, unless overidden by data parameter.
112 'shadow' => 'grayCC', // 'none' or colour of shadow.
113 'shadow_below_axis' => true, // whether to draw shadows of bars and areas below the x/zero axis.
116 'x_axis_gridlines' => 'auto', // if set to a number then x axis is treated as numeric.
117 'y_axis_gridlines' => 6, // number of gridlines on y axis.
118 'zero_axis' => 'none', // colour to draw zero-axis, or 'none'.
121 'axis_font' => 'default.ttf', // axis text font. don't forget to set 'path_to_fonts' above.
122 'axis_size' => 8, // axis text font size in points
123 'axis_colour' => 'gray33', // colour of axis text.
124 'y_axis_angle' => 0, // rotation of axis text.
125 'x_axis_angle' => 0, // rotation of axis text.
127 'y_axis_text_left' => 1, // whether to print left hand y axis text. if 0 no text, if 1 all ticks have text,
128 'x_axis_text' => 1, // if 4 then print every 4th tick and text, etc...
129 'y_axis_text_right' => 0, // behaviour same as above for right hand y axis.
131 'x_offset' => 0.5, // x axis tick offset from y axis as fraction of tick spacing.
132 'y_ticks_colour' => 'black', // colour to draw y ticks, or 'none'
133 'x_ticks_colour' => 'black', // colour to draw x ticks, or 'none'
134 'y_grid' => 'line', // grid lines. set to 'line' or 'dash'...
135 'x_grid' => 'line', // or if set to 'none' print nothing.
136 'grid_colour' => 'grayEE', // default grid colour.
137 'tick_length' => 4, // length of ticks in pixels. can be negative. i.e. outside data drawing area.
139 'legend' => 'none', // default. no legend.
140 // otherwise: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
141 // 'outside-top', 'outside-bottom', 'outside-left', or 'outside-right'.
142 'legend_offset' => 10, // offset in pixels from graph or outside border.
143 'legend_padding' => 5, // padding around legend text.
144 'legend_font' => 'default.ttf', // legend text font. don't forget to set 'path_to_fonts' above.
145 'legend_size' => 8, // legend text point size.
146 'legend_colour' => 'black', // legend text colour.
147 'legend_border' => 'none', // legend border colour, or 'none'.
149 'decimal_point' => '.', // symbol for decimal separation '.' or ',' *european support.
150 'thousand_sep' => ',', // symbol for thousand separation ',' or ''
153 var $y_tick_labels = null; // array of text values for y-axis tick labels
154 var $offset_relation = null; // array of offsets for different sets of data
157 // init all text - title, labels, and axis text.
158 function init() {
160 /// Moodle mods: overrides the font path and encodings
162 global $CFG;
164 /// A default.ttf is searched for in this order:
165 /// dataroot/lang/xx_local/fonts
166 /// dataroot/lang/xx/fonts
167 /// dirroot/lang/xx/fonts
168 /// dataroot/lang
169 /// lib/
171 $currlang = current_language();
172 if (file_exists("$CFG->dataroot/lang/".$currlang."_local/fonts/default.ttf")) {
173 $fontpath = "$CFG->dataroot/lang/".$currlang."_local/fonts/";
174 } else if (file_exists("$CFG->dataroot/lang/$currlang/fonts/default.ttf")) {
175 $fontpath = "$CFG->dataroot/lang/$currlang/fonts/";
176 } else if (file_exists("$CFG->dirroot/lang/$currlang/fonts/default.ttf")) {
177 $fontpath = "$CFG->dirroot/lang/$currlang/fonts/";
178 } else if (file_exists("$CFG->dataroot/lang/default.ttf")) {
179 $fontpath = "$CFG->dataroot/lang/";
180 } else {
181 $fontpath = "$CFG->libdir/";
184 $this->parameter['path_to_fonts'] = $fontpath;
186 /// End Moodle mods
190 $this->calculated['outer_border'] = $this->calculated['boundary_box'];
192 // outer padding
193 $this->calculated['boundary_box']['left'] += $this->parameter['outer_padding'];
194 $this->calculated['boundary_box']['top'] += $this->parameter['outer_padding'];
195 $this->calculated['boundary_box']['right'] -= $this->parameter['outer_padding'];
196 $this->calculated['boundary_box']['bottom'] -= $this->parameter['outer_padding'];
198 $this->init_x_axis();
199 $this->init_y_axis();
200 $this->init_legend();
201 $this->init_labels();
203 // take into account tick lengths
204 $this->calculated['bottom_inner_padding'] = $this->parameter['x_inner_padding'];
205 if (($this->parameter['x_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
206 $this->calculated['bottom_inner_padding'] -= $this->parameter['tick_length'];
207 $this->calculated['boundary_box']['bottom'] -= $this->calculated['bottom_inner_padding'];
209 $this->calculated['left_inner_padding'] = $this->parameter['y_inner_padding'];
210 if ($this->parameter['y_axis_text_left']) {
211 if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
212 $this->calculated['left_inner_padding'] -= $this->parameter['tick_length'];
214 $this->calculated['boundary_box']['left'] += $this->calculated['left_inner_padding'];
216 $this->calculated['right_inner_padding'] = $this->parameter['y_inner_padding'];
217 if ($this->parameter['y_axis_text_right']) {
218 if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
219 $this->calculated['right_inner_padding'] -= $this->parameter['tick_length'];
221 $this->calculated['boundary_box']['right'] -= $this->calculated['right_inner_padding'];
223 // boundaryBox now has coords for plotting area.
224 $this->calculated['inner_border'] = $this->calculated['boundary_box'];
226 $this->init_data();
227 $this->init_x_ticks();
228 $this->init_y_ticks();
231 function draw_text() {
232 $colour = $this->parameter['outer_background'];
233 if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'fill'); // graph background
235 // draw border around image
236 $colour = $this->parameter['outer_border'];
237 if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'box'); // graph border
239 $this->draw_title();
240 $this->draw_x_label();
241 $this->draw_y_label_left();
242 $this->draw_y_label_right();
243 $this->draw_x_axis();
244 $this->draw_y_axis();
245 if ($this->calculated['y_axis_left']['has_data']) $this->draw_zero_axis_left(); // either draw zero axis on left
246 else if ($this->calculated['y_axis_right']['has_data']) $this->draw_zero_axis_right(); // ... or right.
247 $this->draw_legend();
249 // draw border around plot area
250 $colour = $this->parameter['inner_background'];
251 if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, 'fill'); // graph background
253 // draw border around image
254 $colour = $this->parameter['inner_border'];
255 if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, $this->parameter['inner_border_type']); // graph border
258 function draw_stack() {
259 $this->init();
260 $this->draw_text();
262 $yOrder = $this->y_order; // save y_order data.
263 // iterate over each data set. order is very important if you want to see data correctly. remember shadows!!
264 foreach ($yOrder as $set) {
265 $this->y_order = array($set);
266 $this->init_data();
267 $this->draw_data();
269 $this->y_order = $yOrder; // revert y_order data.
271 $this->output();
274 function draw() {
275 $this->init();
276 $this->draw_text();
277 $this->draw_data();
278 $this->output();
281 // draw a data set
282 function draw_set($order, $set, $offset) {
283 if ($offset) @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
284 else $colour = $this->y_format[$set]['colour'];
285 @$this->init_variable($point, $this->y_format[$set]['point'], 'none');
286 @$this->init_variable($pointSize, $this->y_format[$set]['point_size'], $this->parameter['point_size']);
287 @$this->init_variable($line, $this->y_format[$set]['line'], 'none');
288 @$this->init_variable($brushType, $this->y_format[$set]['brush_type'], $this->parameter['brush_type']);
289 @$this->init_variable($brushSize, $this->y_format[$set]['brush_size'], $this->parameter['brush_size']);
290 @$this->init_variable($bar, $this->y_format[$set]['bar'], 'none');
291 @$this->init_variable($barSize, $this->y_format[$set]['bar_size'], $this->parameter['bar_size']);
292 @$this->init_variable($area, $this->y_format[$set]['area'], 'none');
294 $lastX = 0;
295 $lastY = 'none';
296 $fromX = 0;
297 $fromY = 'none';
299 //print "set $set<br />";
300 //expand_pre($this->calculated['y_plot']);
302 foreach ($this->x_data as $index => $x) {
303 //print "index $index<br />";
304 $thisY = $this->calculated['y_plot'][$set][$index];
305 $thisX = $this->calculated['x_plot'][$index];
307 //print "$thisX, $thisY <br />";
309 if (($bar!='none') && (string)$thisY != 'none') {
310 if ($relatedset = $this->offset_relation[$set]) { // Moodle
311 $yoffset = $this->calculated['y_plot'][$relatedset][$index]; // Moodle
312 } else { // Moodle
313 $yoffset = 0; // Moodle
314 } // Moodle
315 //$this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set); // Moodle
316 $this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set, $yoffset); // Moodle
319 if (($area!='none') && (((string)$lastY != 'none') && ((string)$thisY != 'none')))
320 $this->area($lastX, $lastY, $thisX, $thisY, $area, $colour, $offset);
322 if (($point!='none') && (string)$thisY != 'none') $this->plot($thisX, $thisY, $point, $pointSize, $colour, $offset);
324 if (($line!='none') && ((string)$thisY != 'none')) {
325 if ((string)$fromY != 'none')
326 $this->line($fromX, $fromY, $thisX, $thisY, $line, $brushType, $brushSize, $colour, $offset);
328 $fromY = $thisY; // start next line from here
329 $fromX = $thisX; // ...
330 } else {
331 $fromY = 'none';
332 $fromX = 'none';
335 $lastX = $thisX;
336 $lastY = $thisY;
340 function draw_data() {
341 // cycle thru y data to be plotted
342 // first check for drop shadows...
343 foreach ($this->y_order as $order => $set) {
344 @$this->init_variable($offset, $this->y_format[$set]['shadow_offset'], $this->parameter['shadow_offset']);
345 @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
346 if ($colour != 'none') $this->draw_set($order, $set, $offset);
350 // then draw data
351 foreach ($this->y_order as $order => $set) {
352 $this->draw_set($order, $set, 0);
356 function draw_legend() {
357 $position = $this->parameter['legend'];
358 if ($position == 'none') return; // abort if no border
360 $borderColour = $this->parameter['legend_border'];
361 $offset = $this->parameter['legend_offset'];
362 $padding = $this->parameter['legend_padding'];
363 $height = $this->calculated['legend']['boundary_box_all']['height'];
364 $width = $this->calculated['legend']['boundary_box_all']['width'];
365 $graphTop = $this->calculated['boundary_box']['top'];
366 $graphBottom = $this->calculated['boundary_box']['bottom'];
367 $graphLeft = $this->calculated['boundary_box']['left'];
368 $graphRight = $this->calculated['boundary_box']['right'];
369 $outsideRight = $this->calculated['outer_border']['right'];
370 $outsideBottom = $this->calculated['outer_border']['bottom'];
371 switch ($position) {
372 case 'top-left':
373 $top = $graphTop + $offset;
374 $bottom = $graphTop + $height + $offset;
375 $left = $graphLeft + $offset;
376 $right = $graphLeft + $width + $offset;
378 break;
379 case 'top-right':
380 $top = $graphTop + $offset;
381 $bottom = $graphTop + $height + $offset;
382 $left = $graphRight - $width - $offset;
383 $right = $graphRight - $offset;
385 break;
386 case 'bottom-left':
387 $top = $graphBottom - $height - $offset;
388 $bottom = $graphBottom - $offset;
389 $left = $graphLeft + $offset;
390 $right = $graphLeft + $width + $offset;
392 break;
393 case 'bottom-right':
394 $top = $graphBottom - $height - $offset;
395 $bottom = $graphBottom - $offset;
396 $left = $graphRight - $width - $offset;
397 $right = $graphRight - $offset;
398 break;
400 case 'outside-top' :
401 $top = $graphTop;
402 $bottom = $graphTop + $height;
403 $left = $outsideRight - $width - $offset;
404 $right = $outsideRight - $offset;
405 break;
407 case 'outside-bottom' :
408 $top = $graphBottom - $height;
409 $bottom = $graphBottom;
410 $left = $outsideRight - $width - $offset;
411 $right = $outsideRight - $offset;
412 break;
414 case 'outside-left' :
415 $top = $outsideBottom - $height - $offset;
416 $bottom = $outsideBottom - $offset;
417 $left = $graphLeft;
418 $right = $graphLeft + $width;
419 break;
421 case 'outside-right' :
422 $top = $outsideBottom - $height - $offset;
423 $bottom = $outsideBottom - $offset;
424 $left = $graphRight - $width;
425 $right = $graphRight;
426 break;
427 default: // default is top left. no particular reason.
428 $top = $this->calculated['boundary_box']['top'];
429 $bottom = $this->calculated['boundary_box']['top'] + $this->calculated['legend']['boundary_box_all']['height'];
430 $left = $this->calculated['boundary_box']['left'];
431 $right = $this->calculated['boundary_box']['right'] + $this->calculated['legend']['boundary_box_all']['width'];
434 // legend border
435 if($borderColour!='none') $this->draw_rectangle(array('top' => $top,
436 'left' => $left,
437 'bottom' => $bottom,
438 'right' => $right), $this->parameter['legend_border'], 'box');
440 // legend text
441 $legendText = array('points' => $this->parameter['legend_size'],
442 'angle' => 0,
443 'font' => $this->parameter['legend_font'],
444 'colour' => $this->parameter['legend_colour']);
446 $box = $this->calculated['legend']['boundary_box_max']['height']; // use max height for legend square size.
447 $x = $left + $padding;
448 $x_text = $x + $box * 2;
449 $y = $top + $padding;
451 foreach ($this->y_order as $set) {
452 $legendText['text'] = $this->calculated['legend']['text'][$set];
453 if ($legendText['text'] != 'none') {
454 // if text exists then draw box and text
455 $boxColour = $this->colour[$this->y_format[$set]['colour']];
457 // draw box
458 ImageFilledRectangle($this->image, $x, $y, $x + $box, $y + $box, $boxColour);
460 // draw text
461 $coords = array('x' => $x + $box * 2, 'y' => $y, 'reference' => 'top-left');
462 $legendText['boundary_box'] = $this->calculated['legend']['boundary_box'][$set];
463 $this->update_boundaryBox($legendText['boundary_box'], $coords);
464 $this->print_TTF($legendText);
465 $y += $padding + $box;
471 function draw_y_label_right() {
472 if (!$this->parameter['y_label_right']) return;
473 $x = $this->calculated['boundary_box']['right'] + $this->parameter['y_inner_padding'];
474 if ($this->parameter['y_axis_text_right']) $x += $this->calculated['y_axis_right']['boundary_box_max']['width']
475 + $this->calculated['right_inner_padding'];
476 $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
478 $label = $this->calculated['y_label_right'];
479 $coords = array('x' => $x, 'y' => $y, 'reference' => 'left-center');
480 $this->update_boundaryBox($label['boundary_box'], $coords);
481 $this->print_TTF($label);
485 function draw_y_label_left() {
486 if (!$this->parameter['y_label_left']) return;
487 $x = $this->calculated['boundary_box']['left'] - $this->parameter['y_inner_padding'];
488 if ($this->parameter['y_axis_text_left']) $x -= $this->calculated['y_axis_left']['boundary_box_max']['width']
489 + $this->calculated['left_inner_padding'];
490 $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
492 $label = $this->calculated['y_label_left'];
493 $coords = array('x' => $x, 'y' => $y, 'reference' => 'right-center');
494 $this->update_boundaryBox($label['boundary_box'], $coords);
495 $this->print_TTF($label);
498 function draw_title() {
499 if (!$this->parameter['title']) return;
500 //$y = $this->calculated['outside_border']['top'] + $this->parameter['outer_padding'];
501 $y = $this->calculated['boundary_box']['top'] - $this->parameter['outer_padding'];
502 $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
503 $label = $this->calculated['title'];
504 $coords = array('x' => $x, 'y' => $y, 'reference' => 'bottom-center');
505 $this->update_boundaryBox($label['boundary_box'], $coords);
506 $this->print_TTF($label);
509 function draw_x_label() {
510 if (!$this->parameter['x_label']) return;
511 $y = $this->calculated['boundary_box']['bottom'] + $this->parameter['x_inner_padding'];
512 if ($this->parameter['x_axis_text']) $y += $this->calculated['x_axis']['boundary_box_max']['height']
513 + $this->calculated['bottom_inner_padding'];
514 $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
515 $label = $this->calculated['x_label'];
516 $coords = array('x' => $x, 'y' => $y, 'reference' => 'top-center');
517 $this->update_boundaryBox($label['boundary_box'], $coords);
518 $this->print_TTF($label);
521 function draw_zero_axis_left() {
522 $colour = $this->parameter['zero_axis'];
523 if ($colour == 'none') return;
524 // draw zero axis on left hand side
525 $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top'] + ($this->calculated['y_axis_left']['max'] * $this->calculated['y_axis_left']['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_zero_axis_right() {
530 $colour = $this->parameter['zero_axis'];
531 if ($colour == 'none') return;
532 // draw zero axis on right hand side
533 $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top'] + ($this->calculated['y_axis_right']['max'] * $this->calculated['y_axis_right']['factor']));
534 ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
537 function draw_x_axis() {
538 $gridColour = $this->colour[$this->parameter['grid_colour']];
539 $tickColour = $this->colour[$this->parameter['x_ticks_colour']];
540 $axis_colour = $this->parameter['axis_colour'];
541 $xGrid = $this->parameter['x_grid'];
542 $gridTop = $this->calculated['boundary_box']['top'];
543 $gridBottom = $this->calculated['boundary_box']['bottom'];
545 if ($this->parameter['tick_length'] >= 0) {
546 $tickTop = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
547 $tickBottom = $this->calculated['boundary_box']['bottom'];
548 $textBottom = $tickBottom + $this->calculated['bottom_inner_padding'];
549 } else {
550 $tickTop = $this->calculated['boundary_box']['bottom'];
551 $tickBottom = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
552 $textBottom = $tickBottom + $this->calculated['bottom_inner_padding'];
555 $axis_font = $this->parameter['axis_font'];
556 $axis_size = $this->parameter['axis_size'];
557 $axis_angle = $this->parameter['x_axis_angle'];
559 if ($axis_angle == 0) $reference = 'top-center';
560 if ($axis_angle > 0) $reference = 'top-right';
561 if ($axis_angle < 0) $reference = 'top-left';
562 if ($axis_angle == 90) $reference = 'top-center';
564 //generic tag information. applies to all axis text.
565 $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
567 foreach ($this->calculated['x_axis']['tick_x'] as $set => $tickX) {
568 // draw x grid if colour specified
569 if ($xGrid != 'none') {
570 switch ($xGrid) {
571 case 'line':
572 ImageLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
573 break;
574 case 'dash':
575 ImageDashedLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
576 break;
580 if ($this->parameter['x_axis_text'] && !($set % $this->parameter['x_axis_text'])) { // test if tick should be displayed
581 // draw tick
582 if ($tickColour != 'none')
583 ImageLine($this->image, round($tickX), round($tickTop), round($tickX), round($tickBottom), $tickColour);
585 // draw axis text
586 $coords = array('x' => $tickX, 'y' => $textBottom, 'reference' => $reference);
587 $axisTag['text'] = $this->calculated['x_axis']['text'][$set];
588 $axisTag['boundary_box'] = $this->calculated['x_axis']['boundary_box'][$set];
589 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
590 $this->print_TTF($axisTag);
595 function draw_y_axis() {
596 $gridColour = $this->colour[$this->parameter['grid_colour']];
597 $tickColour = $this->colour[$this->parameter['y_ticks_colour']];
598 $axis_colour = $this->parameter['axis_colour'];
599 $yGrid = $this->parameter['y_grid'];
600 $gridLeft = $this->calculated['boundary_box']['left'];
601 $gridRight = $this->calculated['boundary_box']['right'];
603 // axis font information
604 $axis_font = $this->parameter['axis_font'];
605 $axis_size = $this->parameter['axis_size'];
606 $axis_angle = $this->parameter['y_axis_angle'];
607 $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
610 if ($this->calculated['y_axis_left']['has_data']) {
611 // LEFT HAND SIDE
612 // left and right coords for ticks
613 if ($this->parameter['tick_length'] >= 0) {
614 $tickLeft = $this->calculated['boundary_box']['left'];
615 $tickRight = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
616 } else {
617 $tickLeft = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
618 $tickRight = $this->calculated['boundary_box']['left'];
620 $textRight = $tickLeft - $this->calculated['left_inner_padding'];
622 if ($axis_angle == 0) $reference = 'right-center';
623 if ($axis_angle > 0) $reference = 'right-top';
624 if ($axis_angle < 0) $reference = 'right-bottom';
625 if ($axis_angle == 90) $reference = 'right-center';
627 foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
628 // draw y grid if colour specified
629 if ($yGrid != 'none') {
630 switch ($yGrid) {
631 case 'line':
632 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
633 break;
634 case 'dash':
635 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
636 break;
640 // y axis text
641 if ($this->parameter['y_axis_text_left'] && !($set % $this->parameter['y_axis_text_left'])) { // test if tick should be displayed
642 // draw tick
643 if ($tickColour != 'none')
644 ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
646 // draw axis text...
647 $coords = array('x' => $textRight, 'y' => $tickY, 'reference' => $reference);
648 $axisTag['text'] = $this->calculated['y_axis_left']['text'][$set];
649 $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
650 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
651 $this->print_TTF($axisTag);
656 if ($this->calculated['y_axis_right']['has_data']) {
657 // RIGHT HAND SIDE
658 // left and right coords for ticks
659 if ($this->parameter['tick_length'] >= 0) {
660 $tickLeft = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
661 $tickRight = $this->calculated['boundary_box']['right'];
662 } else {
663 $tickLeft = $this->calculated['boundary_box']['right'];
664 $tickRight = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
666 $textLeft = $tickRight+ $this->calculated['left_inner_padding'];
668 if ($axis_angle == 0) $reference = 'left-center';
669 if ($axis_angle > 0) $reference = 'left-bottom';
670 if ($axis_angle < 0) $reference = 'left-top';
671 if ($axis_angle == 90) $reference = 'left-center';
673 foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
674 if (!$this->calculated['y_axis_left']['has_data'] && $yGrid != 'none') { // draw grid if not drawn already (above)
675 switch ($yGrid) {
676 case 'line':
677 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
678 break;
679 case 'dash':
680 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
681 break;
685 if ($this->parameter['y_axis_text_right'] && !($set % $this->parameter['y_axis_text_right'])) { // test if tick should be displayed
686 // draw tick
687 if ($tickColour != 'none')
688 ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
690 // draw axis text...
691 $coords = array('x' => $textLeft, 'y' => $tickY, 'reference' => $reference);
692 $axisTag['text'] = $this->calculated['y_axis_right']['text'][$set];
693 $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
694 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
695 $this->print_TTF($axisTag);
701 function init_data() {
702 $this->calculated['y_plot'] = array(); // array to hold pixel plotting coords for y axis
703 $height = $this->calculated['boundary_box']['bottom'] - $this->calculated['boundary_box']['top'];
704 $width = $this->calculated['boundary_box']['right'] - $this->calculated['boundary_box']['left'];
706 // calculate pixel steps between axis ticks.
707 $this->calculated['y_axis']['step'] = $height / ($this->parameter['y_axis_gridlines'] - 1);
709 // calculate x ticks spacing taking into account x offset for ticks.
710 $extraTick = 2 * $this->parameter['x_offset']; // extra tick to account for padding
711 $numTicks = $this->calculated['x_axis']['num_ticks'] - 1; // number of x ticks
713 // Hack by rodger to avoid division by zero, see bug 1231
714 if ($numTicks==0) $numTicks=1;
716 $this->calculated['x_axis']['step'] = $width / ($numTicks + $extraTick);
717 $widthPlot = $width - ($this->calculated['x_axis']['step'] * $extraTick);
718 $this->calculated['x_axis']['step'] = $widthPlot / $numTicks;
720 //calculate factor for transforming x,y physical coords to logical coords for right hand y_axis.
721 $y_range = $this->calculated['y_axis_right']['max'] - $this->calculated['y_axis_right']['min'];
722 $y_range = ($y_range ? $y_range : 1);
723 $this->calculated['y_axis_right']['factor'] = $height / $y_range;
725 //calculate factor for transforming x,y physical coords to logical coords for left hand axis.
726 $yRange = $this->calculated['y_axis_left']['max'] - $this->calculated['y_axis_left']['min'];
727 $yRange = ($yRange ? $yRange : 1);
728 $this->calculated['y_axis_left']['factor'] = $height / $yRange;
729 if ($this->parameter['x_axis_gridlines'] != 'auto') {
730 $xRange = $this->calculated['x_axis']['max'] - $this->calculated['x_axis']['min'];
731 $xRange = ($xRange ? $xRange : 1);
732 $this->calculated['x_axis']['factor'] = $widthPlot / $xRange;
735 //expand_pre($this->calculated['boundary_box']);
736 // cycle thru all data sets...
737 $this->calculated['num_bars'] = 0;
738 foreach ($this->y_order as $order => $set) {
739 // determine how many bars there are
740 if (isset($this->y_format[$set]['bar']) && ($this->y_format[$set]['bar'] != 'none')) {
741 $this->calculated['bar_offset_index'][$set] = $this->calculated['num_bars']; // index to relate bar with data set.
742 $this->calculated['num_bars']++;
745 // calculate y coords for plotting data
746 foreach ($this->x_data as $index => $x) {
747 $this->calculated['y_plot'][$set][$index] = $this->y_data[$set][$index];
749 if ((string)$this->y_data[$set][$index] != 'none') {
751 if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
752 $this->calculated['y_plot'][$set][$index] =
753 round(($this->y_data[$set][$index] - $this->calculated['y_axis_right']['min'])
754 * $this->calculated['y_axis_right']['factor']);
755 } else {
756 //print "$set $index<br />";
757 $this->calculated['y_plot'][$set][$index] =
758 round(($this->y_data[$set][$index] - $this->calculated['y_axis_left']['min'])
759 * $this->calculated['y_axis_left']['factor']);
765 //print "factor ".$this->calculated['x_axis']['factor']."<br />";
766 //expand_pre($this->calculated['x_plot']);
768 // calculate bar parameters if bars are to be drawn.
769 if ($this->calculated['num_bars']) {
770 $xStep = $this->calculated['x_axis']['step'];
771 $totalWidth = $this->calculated['x_axis']['step'] - $this->parameter['bar_spacing'];
772 $barWidth = $totalWidth / $this->calculated['num_bars'];
774 $barX = ($barWidth - $totalWidth) / 2; // starting x offset
775 for ($i=0; $i < $this->calculated['num_bars']; $i++) {
776 $this->calculated['bar_offset_x'][$i] = $barX;
777 $barX += $barWidth; // add width of bar to x offset.
779 $this->calculated['bar_width'] = $barWidth;
785 function init_x_ticks() {
786 // get coords for x axis ticks and data plots
787 //$xGrid = $this->parameter['x_grid'];
788 $xStep = $this->calculated['x_axis']['step'];
789 $ticksOffset = $this->parameter['x_offset']; // where to start drawing ticks relative to y axis.
790 $gridLeft = $this->calculated['boundary_box']['left'] + ($xStep * $ticksOffset); // grid x start
791 $tickX = $gridLeft; // tick x coord
793 foreach ($this->calculated['x_axis']['text'] as $set => $value) {
794 //print "index: $set<br />";
795 // x tick value
796 $this->calculated['x_axis']['tick_x'][$set] = $tickX;
797 // if num ticks is auto then x plot value is same as x tick
798 if ($this->parameter['x_axis_gridlines'] == 'auto') $this->calculated['x_plot'][$set] = round($tickX);
799 //print $this->calculated['x_plot'][$set].'<br />';
800 $tickX += $xStep;
803 //print "xStep: $xStep <br />";
804 // if numeric x axis then calculate x coords for each data point. this is seperate from x ticks.
805 $gridX = $gridLeft;
806 if (empty($this->calculated['x_axis']['factor'])) {
807 $this->calculated['x_axis']['factor'] = 0;
809 if (empty($this->calculated['x_axis']['min'])) {
810 $this->calculated['x_axis']['min'] = 0;
812 $factor = $this->calculated['x_axis']['factor'];
813 $min = $this->calculated['x_axis']['min'];
815 if ($this->parameter['x_axis_gridlines'] != 'auto') {
816 foreach ($this->x_data as $index => $x) {
817 //print "index: $index, x: $x<br />";
818 $offset = $x - $this->calculated['x_axis']['min'];
820 //$gridX = ($offset * $this->calculated['x_axis']['factor']);
821 //print "offset: $offset <br />";
822 //$this->calculated['x_plot'][$set] = $gridLeft + ($offset * $this->calculated['x_axis']['factor']);
824 $this->calculated['x_plot'][$index] = $gridLeft + ($x - $min) * $factor;
826 //print $this->calculated['x_plot'][$set].'<br />';
829 //expand_pre($this->calculated['boundary_box']);
830 //print "factor ".$this->calculated['x_axis']['factor']."<br />";
831 //expand_pre($this->calculated['x_plot']);
834 function init_y_ticks() {
835 // get coords for y axis ticks
837 $yStep = $this->calculated['y_axis']['step'];
838 $gridBottom = $this->calculated['boundary_box']['bottom'];
839 $tickY = $gridBottom; // tick y coord
841 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) {
842 $this->calculated['y_axis']['tick_y'][$i] = $tickY;
843 $tickY -= $yStep;
848 function init_labels() {
849 if ($this->parameter['title']) {
850 $size = $this->get_boundaryBox(
851 array('points' => $this->parameter['title_size'],
852 'angle' => 0,
853 'font' => $this->parameter['title_font'],
854 'text' => $this->parameter['title']));
855 $this->calculated['title']['boundary_box'] = $size;
856 $this->calculated['title']['text'] = $this->parameter['title'];
857 $this->calculated['title']['font'] = $this->parameter['title_font'];
858 $this->calculated['title']['points'] = $this->parameter['title_size'];
859 $this->calculated['title']['colour'] = $this->parameter['title_colour'];
860 $this->calculated['title']['angle'] = 0;
862 $this->calculated['boundary_box']['top'] += $size['height'] + $this->parameter['outer_padding'];
863 //$this->calculated['boundary_box']['top'] += $size['height'];
865 } else $this->calculated['title']['boundary_box'] = $this->get_null_size();
867 if ($this->parameter['y_label_left']) {
868 $this->calculated['y_label_left']['text'] = $this->parameter['y_label_left'];
869 $this->calculated['y_label_left']['angle'] = $this->parameter['y_label_angle'];
870 $this->calculated['y_label_left']['font'] = $this->parameter['label_font'];
871 $this->calculated['y_label_left']['points'] = $this->parameter['label_size'];
872 $this->calculated['y_label_left']['colour'] = $this->parameter['label_colour'];
874 $size = $this->get_boundaryBox($this->calculated['y_label_left']);
875 $this->calculated['y_label_left']['boundary_box'] = $size;
876 //$this->calculated['boundary_box']['left'] += $size['width'] + $this->parameter['inner_padding'];
877 $this->calculated['boundary_box']['left'] += $size['width'];
879 } else $this->calculated['y_label_left']['boundary_box'] = $this->get_null_size();
881 if ($this->parameter['y_label_right']) {
882 $this->calculated['y_label_right']['text'] = $this->parameter['y_label_right'];
883 $this->calculated['y_label_right']['angle'] = $this->parameter['y_label_angle'];
884 $this->calculated['y_label_right']['font'] = $this->parameter['label_font'];
885 $this->calculated['y_label_right']['points'] = $this->parameter['label_size'];
886 $this->calculated['y_label_right']['colour'] = $this->parameter['label_colour'];
888 $size = $this->get_boundaryBox($this->calculated['y_label_right']);
889 $this->calculated['y_label_right']['boundary_box'] = $size;
890 //$this->calculated['boundary_box']['right'] -= $size['width'] + $this->parameter['inner_padding'];
891 $this->calculated['boundary_box']['right'] -= $size['width'];
893 } else $this->calculated['y_label_right']['boundary_box'] = $this->get_null_size();
895 if ($this->parameter['x_label']) {
896 $this->calculated['x_label']['text'] = $this->parameter['x_label'];
897 $this->calculated['x_label']['angle'] = $this->parameter['x_label_angle'];
898 $this->calculated['x_label']['font'] = $this->parameter['label_font'];
899 $this->calculated['x_label']['points'] = $this->parameter['label_size'];
900 $this->calculated['x_label']['colour'] = $this->parameter['label_colour'];
902 $size = $this->get_boundaryBox($this->calculated['x_label']);
903 $this->calculated['x_label']['boundary_box'] = $size;
904 //$this->calculated['boundary_box']['bottom'] -= $size['height'] + $this->parameter['inner_padding'];
905 $this->calculated['boundary_box']['bottom'] -= $size['height'];
907 } else $this->calculated['x_label']['boundary_box'] = $this->get_null_size();
912 function init_legend() {
913 $this->calculated['legend'] = array(); // array to hold calculated values for legend.
914 //$this->calculated['legend']['boundary_box_max'] = array('height' => 0, 'width' => 0);
915 $this->calculated['legend']['boundary_box_max'] = $this->get_null_size();
916 if ($this->parameter['legend'] == 'none') return;
918 $position = $this->parameter['legend'];
919 $numSets = 0; // number of data sets with legends.
920 $sumTextHeight = 0; // total of height of all legend text items.
921 $width = 0;
922 $height = 0;
924 foreach ($this->y_order as $set) {
925 $text = isset($this->y_format[$set]['legend']) ? $this->y_format[$set]['legend'] : 'none';
926 $size = $this->get_boundaryBox(
927 array('points' => $this->parameter['legend_size'],
928 'angle' => 0,
929 'font' => $this->parameter['legend_font'],
930 'text' => $text));
932 $this->calculated['legend']['boundary_box'][$set] = $size;
933 $this->calculated['legend']['text'][$set] = $text;
934 //$this->calculated['legend']['font'][$set] = $this->parameter['legend_font'];
935 //$this->calculated['legend']['points'][$set] = $this->parameter['legend_size'];
936 //$this->calculated['legend']['angle'][$set] = 0;
938 if ($text && $text!='none') {
939 $numSets++;
940 $sumTextHeight += $size['height'];
943 if ($size['width'] > $this->calculated['legend']['boundary_box_max']['width'])
944 $this->calculated['legend']['boundary_box_max'] = $size;
947 $offset = $this->parameter['legend_offset']; // offset in pixels of legend box from graph border.
948 $padding = $this->parameter['legend_padding']; // padding in pixels around legend text.
949 $textWidth = $this->calculated['legend']['boundary_box_max']['width']; // width of largest legend item.
950 $textHeight = $this->calculated['legend']['boundary_box_max']['height']; // use height as size to use for colour square in legend.
951 $width = $padding * 2 + $textWidth + $textHeight * 2; // left and right padding + maximum text width + space for square
952 $height = ($padding + $textHeight) * $numSets + $padding; // top and bottom padding + padding between text + text.
954 $this->calculated['legend']['boundary_box_all'] = array('width' => $width,
955 'height' => $height,
956 'offset' => $offset,
957 'reference' => $position);
959 switch ($position) { // move in right or bottom if legend is outside data plotting area.
960 case 'outside-top' :
961 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
962 break;
964 case 'outside-bottom' :
965 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
966 break;
968 case 'outside-left' :
969 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
970 break;
972 case 'outside-right' :
973 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
974 break;
978 function init_y_axis() {
979 $this->calculated['y_axis_left'] = array(); // array to hold calculated values for y_axis on left.
980 $this->calculated['y_axis_left']['boundary_box_max'] = $this->get_null_size();
981 $this->calculated['y_axis_right'] = array(); // array to hold calculated values for y_axis on right.
982 $this->calculated['y_axis_right']['boundary_box_max'] = $this->get_null_size();
984 $axis_font = $this->parameter['axis_font'];
985 $axis_size = $this->parameter['axis_size'];
986 $axis_colour = $this->parameter['axis_colour'];
987 $axis_angle = $this->parameter['y_axis_angle'];
988 $y_tick_labels = $this->y_tick_labels;
990 $this->calculated['y_axis_left']['has_data'] = FALSE;
991 $this->calculated['y_axis_right']['has_data'] = FALSE;
993 // find min and max y values.
994 $minLeft = $this->parameter['y_min_left'];
995 $maxLeft = $this->parameter['y_max_left'];
996 $minRight = $this->parameter['y_min_right'];
997 $maxRight = $this->parameter['y_max_right'];
998 $dataLeft = array();
999 $dataRight = array();
1000 foreach ($this->y_order as $order => $set) {
1001 if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
1002 $this->calculated['y_axis_right']['has_data'] = TRUE;
1003 $dataRight = array_merge($dataRight, $this->y_data[$set]);
1004 } else {
1005 $this->calculated['y_axis_left']['has_data'] = TRUE;
1006 $dataLeft = array_merge($dataLeft, $this->y_data[$set]);
1009 $dataLeftRange = $this->find_range($dataLeft, $minLeft, $maxLeft, $this->parameter['y_resolution_left']);
1010 $dataRightRange = $this->find_range($dataRight, $minRight, $maxRight, $this->parameter['y_resolution_right']);
1011 $minLeft = $dataLeftRange['min'];
1012 $maxLeft = $dataLeftRange['max'];
1013 $minRight = $dataRightRange['min'];
1014 $maxRight = $dataRightRange['max'];
1016 $this->calculated['y_axis_left']['min'] = $minLeft;
1017 $this->calculated['y_axis_left']['max'] = $maxLeft;
1018 $this->calculated['y_axis_right']['min'] = $minRight;
1019 $this->calculated['y_axis_right']['max'] = $maxRight;
1021 $stepLeft = ($maxLeft - $minLeft) / ($this->parameter['y_axis_gridlines'] - 1);
1022 $startLeft = $minLeft;
1023 $step_right = ($maxRight - $minRight) / ($this->parameter['y_axis_gridlines'] - 1);
1024 $start_right = $minRight;
1026 if ($this->parameter['y_axis_text_left']) {
1027 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1028 // left y axis
1029 if ($y_tick_labels) {
1030 $value = $y_tick_labels[$i];
1031 } else {
1032 $value = number_format($startLeft, $this->parameter['y_decimal_left'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1034 $this->calculated['y_axis_left']['data'][$i] = $startLeft;
1035 $this->calculated['y_axis_left']['text'][$i] = $value; // text is formatted raw data
1037 $size = $this->get_boundaryBox(
1038 array('points' => $axis_size,
1039 'font' => $axis_font,
1040 'angle' => $axis_angle,
1041 'colour' => $axis_colour,
1042 'text' => $value));
1043 $this->calculated['y_axis_left']['boundary_box'][$i] = $size;
1045 if ($size['height'] > $this->calculated['y_axis_left']['boundary_box_max']['height'])
1046 $this->calculated['y_axis_left']['boundary_box_max']['height'] = $size['height'];
1047 if ($size['width'] > $this->calculated['y_axis_left']['boundary_box_max']['width'])
1048 $this->calculated['y_axis_left']['boundary_box_max']['width'] = $size['width'];
1050 $startLeft += $stepLeft;
1052 $this->calculated['boundary_box']['left'] += $this->calculated['y_axis_left']['boundary_box_max']['width']
1053 + $this->parameter['y_inner_padding'];
1056 if ($this->parameter['y_axis_text_right']) {
1057 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1058 // right y axis
1059 $value = number_format($start_right, $this->parameter['y_decimal_right'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1060 $this->calculated['y_axis_right']['data'][$i] = $start_right;
1061 $this->calculated['y_axis_right']['text'][$i] = $value; // text is formatted raw data
1062 $size = $this->get_boundaryBox(
1063 array('points' => $axis_size,
1064 'font' => $axis_font,
1065 'angle' => $axis_angle,
1066 'colour' => $axis_colour,
1067 'text' => $value));
1068 $this->calculated['y_axis_right']['boundary_box'][$i] = $size;
1070 if ($size['height'] > $this->calculated['y_axis_right']['boundary_box_max']['height'])
1071 $this->calculated['y_axis_right']['boundary_box_max'] = $size;
1072 if ($size['width'] > $this->calculated['y_axis_right']['boundary_box_max']['width'])
1073 $this->calculated['y_axis_right']['boundary_box_max']['width'] = $size['width'];
1075 $start_right += $step_right;
1077 $this->calculated['boundary_box']['right'] -= $this->calculated['y_axis_right']['boundary_box_max']['width']
1078 + $this->parameter['y_inner_padding'];
1082 function init_x_axis() {
1083 $this->calculated['x_axis'] = array(); // array to hold calculated values for x_axis.
1084 $this->calculated['x_axis']['boundary_box_max'] = array('height' => 0, 'width' => 0);
1086 $axis_font = $this->parameter['axis_font'];
1087 $axis_size = $this->parameter['axis_size'];
1088 $axis_colour = $this->parameter['axis_colour'];
1089 $axis_angle = $this->parameter['x_axis_angle'];
1091 // check whether to treat x axis as numeric
1092 if ($this->parameter['x_axis_gridlines'] == 'auto') { // auto means text based x_axis, not numeric...
1093 $this->calculated['x_axis']['num_ticks'] = sizeof($this->x_data);
1094 $data = $this->x_data;
1095 for ($i=0; $i < $this->calculated['x_axis']['num_ticks']; $i++) {
1096 $value = array_shift($data); // grab value from begin of array
1097 $this->calculated['x_axis']['data'][$i] = $value;
1098 $this->calculated['x_axis']['text'][$i] = $value; // raw data and text are both the same in this case
1099 $size = $this->get_boundaryBox(
1100 array('points' => $axis_size,
1101 'font' => $axis_font,
1102 'angle' => $axis_angle,
1103 'colour' => $axis_colour,
1104 'text' => $value));
1105 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1106 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1107 $this->calculated['x_axis']['boundary_box_max'] = $size;
1110 } else { // x axis is numeric so find max min values...
1111 $this->calculated['x_axis']['num_ticks'] = $this->parameter['x_axis_gridlines'];
1113 $min = $this->parameter['x_min'];
1114 $max = $this->parameter['x_max'];
1115 $data = array();
1116 $data = $this->find_range($this->x_data, $min, $max, $this->parameter['x_resolution']);
1117 $min = $data['min'];
1118 $max = $data['max'];
1119 $this->calculated['x_axis']['min'] = $min;
1120 $this->calculated['x_axis']['max'] = $max;
1122 $step = ($max - $min) / ($this->calculated['x_axis']['num_ticks'] - 1);
1123 $start = $min;
1125 for ($i = 0; $i < $this->calculated['x_axis']['num_ticks']; $i++) { // calculate x axis text sizes
1126 $value = number_format($start, $this->parameter['xDecimal'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1127 $this->calculated['x_axis']['data'][$i] = $start;
1128 $this->calculated['x_axis']['text'][$i] = $value; // text is formatted raw data
1130 $size = $this->get_boundaryBox(
1131 array('points' => $axis_size,
1132 'font' => $axis_font,
1133 'angle' => $axis_angle,
1134 'colour' => $axis_colour,
1135 'text' => $value));
1136 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1138 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1139 $this->calculated['x_axis']['boundary_box_max'] = $size;
1141 $start += $step;
1144 if ($this->parameter['x_axis_text'])
1145 $this->calculated['boundary_box']['bottom'] -= $this->calculated['x_axis']['boundary_box_max']['height']
1146 + $this->parameter['x_inner_padding'];
1149 // find max and min values for a data array given the resolution.
1150 function find_range($data, $min, $max, $resolution) {
1151 if (sizeof($data) == 0 ) return array('min' => 0, 'max' => 0);
1152 foreach ($data as $key => $value) {
1153 if ($value=='none') continue;
1154 if ($value > $max) $max = $value;
1155 if ($value < $min) $min = $value;
1158 if ($max == 0) {
1159 $factor = 1;
1160 } else {
1161 if ($max < 0) $factor = - pow(10, (floor(log10(abs($max))) + $resolution) );
1162 else $factor = pow(10, (floor(log10(abs($max))) - $resolution) );
1164 if ($factor > 0.1) { // To avoid some wierd rounding errors (Moodle)
1165 $factor = round($factor * 1000.0) / 1000.0; // To avoid some wierd rounding errors (Moodle)
1166 } // To avoid some wierd rounding errors (Moodle)
1168 $max = $factor * @ceil($max / $factor);
1169 $min = $factor * @floor($min / $factor);
1171 //print "max=$max, min=$min<br />";
1173 return array('min' => $min, 'max' => $max);
1176 public function __construct() {
1177 if (func_num_args() == 2) {
1178 $this->parameter['width'] = func_get_arg(0);
1179 $this->parameter['height'] = func_get_arg(1);
1181 //$this->boundaryBox = array(
1182 $this->calculated['boundary_box'] = array(
1183 'left' => 0,
1184 'top' => 0,
1185 'right' => $this->parameter['width'] - 1,
1186 'bottom' => $this->parameter['height'] - 1);
1188 $this->init_colours();
1190 //ImageColorTransparent($this->image, $this->colour['white']); // colour for transparency
1194 * Old syntax of class constructor. Deprecated in PHP7.
1196 * @deprecated since Moodle 3.1
1198 public function graph() {
1199 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
1200 self::__construct();
1204 * Prepare label's text for GD output.
1206 * @param string $label string to be prepared.
1207 * @return string Reversed input string, if we are in RTL mode and has no numbers.
1208 * Otherwise, returns the string as is.
1210 private function prepare_label_text($label) {
1211 if (right_to_left() and !preg_match('/[0-9]/i', $label)) {
1212 return core_text::strrev($label);
1213 } else {
1214 return $label;
1218 function print_TTF($message) {
1219 $points = $message['points'];
1220 $angle = $message['angle'];
1221 // We have to manually reverse the label, since php GD cannot handle RTL characters properly in UTF8 strings.
1222 $text = $this->prepare_label_text($message['text']);
1223 $colour = $this->colour[$message['colour']];
1224 $font = $this->parameter['path_to_fonts'].$message['font'];
1226 $x = $message['boundary_box']['x'];
1227 $y = $message['boundary_box']['y'];
1228 $offsetX = $message['boundary_box']['offsetX'];
1229 $offsetY = $message['boundary_box']['offsetY'];
1230 $height = $message['boundary_box']['height'];
1231 $width = $message['boundary_box']['width'];
1232 $reference = $message['boundary_box']['reference'];
1234 switch ($reference) {
1235 case 'top-left':
1236 case 'left-top':
1237 $y += $height - $offsetY;
1238 //$y += $offsetY;
1239 $x += $offsetX;
1240 break;
1241 case 'left-center':
1242 $y += ($height / 2) - $offsetY;
1243 $x += $offsetX;
1244 break;
1245 case 'left-bottom':
1246 $y -= $offsetY;
1247 $x += $offsetX;
1248 break;
1249 case 'top-center':
1250 $y += $height - $offsetY;
1251 $x -= ($width / 2) - $offsetX;
1252 break;
1253 case 'top-right':
1254 case 'right-top':
1255 $y += $height - $offsetY;
1256 $x -= $width - $offsetX;
1257 break;
1258 case 'right-center':
1259 $y += ($height / 2) - $offsetY;
1260 $x -= $width - $offsetX;
1261 break;
1262 case 'right-bottom':
1263 $y -= $offsetY;
1264 $x -= $width - $offsetX;
1265 break;
1266 case 'bottom-center':
1267 $y -= $offsetY;
1268 $x -= ($width / 2) - $offsetX;
1269 break;
1270 default:
1271 $y = 0;
1272 $x = 0;
1273 break;
1275 // start of Moodle addition
1276 $text = core_text::utf8_to_entities($text, true, true); //does not work with hex entities!
1277 // end of Moodle addition
1278 ImageTTFText($this->image, $points, $angle, $x, $y, $colour, $font, $text);
1281 // move boundaryBox to coordinates specified
1282 function update_boundaryBox(&$boundaryBox, $coords) {
1283 $width = $boundaryBox['width'];
1284 $height = $boundaryBox['height'];
1285 $x = $coords['x'];
1286 $y = $coords['y'];
1287 $reference = $coords['reference'];
1288 switch ($reference) {
1289 case 'top-left':
1290 case 'left-top':
1291 $top = $y;
1292 $bottom = $y + $height;
1293 $left = $x;
1294 $right = $x + $width;
1295 break;
1296 case 'left-center':
1297 $top = $y - ($height / 2);
1298 $bottom = $y + ($height / 2);
1299 $left = $x;
1300 $right = $x + $width;
1301 break;
1302 case 'left-bottom':
1303 $top = $y - $height;
1304 $bottom = $y;
1305 $left = $x;
1306 $right = $x + $width;
1307 break;
1308 case 'top-center':
1309 $top = $y;
1310 $bottom = $y + $height;
1311 $left = $x - ($width / 2);
1312 $right = $x + ($width / 2);
1313 break;
1314 case 'right-top':
1315 case 'top-right':
1316 $top = $y;
1317 $bottom = $y + $height;
1318 $left = $x - $width;
1319 $right = $x;
1320 break;
1321 case 'right-center':
1322 $top = $y - ($height / 2);
1323 $bottom = $y + ($height / 2);
1324 $left = $x - $width;
1325 $right = $x;
1326 break;
1327 case 'bottom=right':
1328 case 'right-bottom':
1329 $top = $y - $height;
1330 $bottom = $y;
1331 $left = $x - $width;
1332 $right = $x;
1333 break;
1334 default:
1335 $top = 0;
1336 $bottom = $height;
1337 $left = 0;
1338 $right = $width;
1339 break;
1342 $boundaryBox = array_merge($boundaryBox, array('top' => $top,
1343 'bottom' => $bottom,
1344 'left' => $left,
1345 'right' => $right,
1346 'x' => $x,
1347 'y' => $y,
1348 'reference' => $reference));
1351 function get_null_size() {
1352 return array('width' => 0,
1353 'height' => 0,
1354 'offsetX' => 0,
1355 'offsetY' => 0,
1356 //'fontHeight' => 0
1360 function get_boundaryBox($message) {
1361 $points = $message['points'];
1362 $angle = $message['angle'];
1363 $font = $this->parameter['path_to_fonts'].$message['font'];
1364 $text = $message['text'];
1366 //print ('get_boundaryBox');
1367 //expandPre($message);
1369 // get font size
1370 $bounds = ImageTTFBBox($points, $angle, $font, "W");
1371 if ($angle < 0) {
1372 $fontHeight = abs($bounds[7]-$bounds[1]);
1373 } else if ($angle > 0) {
1374 $fontHeight = abs($bounds[1]-$bounds[7]);
1375 } else {
1376 $fontHeight = abs($bounds[7]-$bounds[1]);
1379 // get boundary box and offsets for printing at an angle
1380 // start of Moodle addition
1381 $text = core_text::utf8_to_entities($text, true, true); //gd does not work with hex entities!
1382 // end of Moodle addition
1383 $bounds = ImageTTFBBox($points, $angle, $font, $text);
1385 if ($angle < 0) {
1386 $width = abs($bounds[4]-$bounds[0]);
1387 $height = abs($bounds[3]-$bounds[7]);
1388 $offsetY = abs($bounds[3]-$bounds[1]);
1389 $offsetX = 0;
1391 } else if ($angle > 0) {
1392 $width = abs($bounds[2]-$bounds[6]);
1393 $height = abs($bounds[1]-$bounds[5]);
1394 $offsetY = 0;
1395 $offsetX = abs($bounds[0]-$bounds[6]);
1397 } else {
1398 $width = abs($bounds[4]-$bounds[6]);
1399 $height = abs($bounds[7]-$bounds[1]);
1400 $offsetY = $bounds[1];
1401 $offsetX = 0;
1404 //return values
1405 return array('width' => $width,
1406 'height' => $height,
1407 'offsetX' => $offsetX,
1408 'offsetY' => $offsetY,
1409 //'fontHeight' => $fontHeight
1413 function draw_rectangle($border, $colour, $type) {
1414 $colour = $this->colour[$colour];
1415 switch ($type) {
1416 case 'fill': // fill the rectangle
1417 ImageFilledRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1418 break;
1419 case 'box': // all sides
1420 ImageRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1421 break;
1422 case 'axis': // bottom x axis and left y axis
1423 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1424 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1425 break;
1426 case 'y': // left y axis only
1427 case 'y-left':
1428 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1429 break;
1430 case 'y-right': // right y axis only
1431 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1432 break;
1433 case 'x': // bottom x axis only
1434 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1435 break;
1436 case 'u': // u shaped. bottom x axis and both left and right y axis.
1437 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1438 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1439 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1440 break;
1445 function init_colours() {
1446 $this->image = ImageCreate($this->parameter['width'], $this->parameter['height']);
1447 // standard colours
1448 $this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF); // first colour is background colour.
1449 $this->colour['black'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x00);
1450 $this->colour['maroon'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x00);
1451 $this->colour['green'] = ImageColorAllocate ($this->image, 0x00, 0x80, 0x00);
1452 $this->colour['ltgreen'] = ImageColorAllocate ($this->image, 0x52, 0xF1, 0x7F);
1453 $this->colour['ltltgreen']= ImageColorAllocate ($this->image, 0x99, 0xFF, 0x99);
1454 $this->colour['olive'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x00);
1455 $this->colour['navy'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x80);
1456 $this->colour['purple'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x80);
1457 $this->colour['gray'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x80);
1458 $this->colour['red'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0x00);
1459 $this->colour['ltred'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x99);
1460 $this->colour['ltltred'] = ImageColorAllocate ($this->image, 0xFF, 0xCC, 0xCC);
1461 $this->colour['orange'] = ImageColorAllocate ($this->image, 0xFF, 0x66, 0x00);
1462 $this->colour['ltorange'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x66);
1463 $this->colour['ltltorange'] = ImageColorAllocate ($this->image, 0xFF, 0xcc, 0x99);
1464 $this->colour['lime'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0x00);
1465 $this->colour['yellow'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0x00);
1466 $this->colour['blue'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0xFF);
1467 $this->colour['ltblue'] = ImageColorAllocate ($this->image, 0x00, 0xCC, 0xFF);
1468 $this->colour['ltltblue'] = ImageColorAllocate ($this->image, 0x99, 0xFF, 0xFF);
1469 $this->colour['fuchsia'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0xFF);
1470 $this->colour['aqua'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0xFF);
1471 //$this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF);
1472 // shades of gray
1473 $this->colour['grayF0'] = ImageColorAllocate ($this->image, 0xF0, 0xF0, 0xF0);
1474 $this->colour['grayEE'] = ImageColorAllocate ($this->image, 0xEE, 0xEE, 0xEE);
1475 $this->colour['grayDD'] = ImageColorAllocate ($this->image, 0xDD, 0xDD, 0xDD);
1476 $this->colour['grayCC'] = ImageColorAllocate ($this->image, 0xCC, 0xCC, 0xCC);
1477 $this->colour['gray33'] = ImageColorAllocate ($this->image, 0x33, 0x33, 0x33);
1478 $this->colour['gray66'] = ImageColorAllocate ($this->image, 0x66, 0x66, 0x66);
1479 $this->colour['gray99'] = ImageColorAllocate ($this->image, 0x99, 0x99, 0x99);
1481 $this->colour['none'] = 'none';
1482 return true;
1485 function output() {
1486 if ($this->debug) { // for debugging purposes.
1487 //expandPre($this->graph);
1488 //expandPre($this->y_data);
1489 //expandPre($this->x_data);
1490 //expandPre($this->parameter);
1491 } else {
1493 $expiresSeconds = $this->parameter['seconds_to_live'];
1494 $expiresHours = $this->parameter['hours_to_live'];
1496 if ($expiresHours || $expiresSeconds) {
1497 $now = mktime (date("H"),date("i"),date("s"),date("m"),date("d"),date("Y"));
1498 $expires = mktime (date("H")+$expiresHours,date("i"),date("s")+$expiresSeconds,date("m"),date("d"),date("Y"));
1499 $expiresGMT = gmdate('D, d M Y H:i:s', $expires).' GMT';
1500 $lastModifiedGMT = gmdate('D, d M Y H:i:s', $now).' GMT';
1502 Header('Last-modified: '.$lastModifiedGMT);
1503 Header('Expires: '.$expiresGMT);
1506 if ($this->parameter['file_name'] == 'none') {
1507 switch ($this->parameter['output_format']) {
1508 case 'GIF':
1509 Header("Content-type: image/gif"); // GIF??. switch to PNG guys!!
1510 ImageGIF($this->image);
1511 break;
1512 case 'JPEG':
1513 Header("Content-type: image/jpeg"); // JPEG for line art??. included for completeness.
1514 ImageJPEG($this->image);
1515 break;
1516 default:
1517 Header("Content-type: image/png"); // preferred output format
1518 ImagePNG($this->image);
1519 break;
1521 } else {
1522 switch ($this->parameter['output_format']) {
1523 case 'GIF':
1524 ImageGIF($this->image, $this->parameter['file_name'].'.gif');
1525 break;
1526 case 'JPEG':
1527 ImageJPEG($this->image, $this->parameter['file_name'].'.jpg');
1528 break;
1529 default:
1530 ImagePNG($this->image, $this->parameter['file_name'].'.png');
1531 break;
1535 ImageDestroy($this->image);
1537 } // function output
1539 function init_variable(&$variable, $value, $default) {
1540 if (!empty($value)) $variable = $value;
1541 else if (isset($default)) $variable = $default;
1542 else unset($variable);
1545 // plot a point. options include square, circle, diamond, triangle, and dot. offset is used for drawing shadows.
1546 // for diamonds and triangles the size should be an even number to get nice look. if odd the points are crooked.
1547 function plot($x, $y, $type, $size, $colour, $offset) {
1548 //print("drawing point of type: $type, at offset: $offset");
1549 $u = $x + $offset;
1550 $v = $this->calculated['inner_border']['bottom'] - $y + $offset;
1551 $half = $size / 2;
1553 switch ($type) {
1554 case 'square':
1555 ImageFilledRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1556 break;
1557 case 'square-open':
1558 ImageRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1559 break;
1560 case 'circle':
1561 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1562 ImageFillToBorder($this->image, $u, $v, $this->colour[$colour], $this->colour[$colour]);
1563 break;
1564 case 'circle-open':
1565 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1566 break;
1567 case 'diamond':
1568 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1569 break;
1570 case 'diamond-open':
1571 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1572 break;
1573 case 'triangle':
1574 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1575 break;
1576 case 'triangle-open':
1577 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1578 break;
1579 case 'dot':
1580 ImageSetPixel($this->image, $u, $v, $this->colour[$colour]);
1581 break;
1585 function bar($x, $y, $type, $size, $colour, $offset, $index, $yoffset) {
1586 $index_offset = $this->calculated['bar_offset_index'][$index];
1587 if ( $yoffset ) {
1588 $bar_offsetx = 0;
1589 } else {
1590 $bar_offsetx = $this->calculated['bar_offset_x'][$index_offset];
1592 //$this->dbug("drawing bar at offset = $offset : index = $index: bar_offsetx = $bar_offsetx");
1594 $span = ($this->calculated['bar_width'] * $size) / 2;
1595 $x_left = $x + $bar_offsetx - $span;
1596 $x_right = $x + $bar_offsetx + $span;
1598 if ($this->parameter['zero_axis'] != 'none') {
1599 $zero = $this->calculated['zero_axis'];
1600 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1601 $u_left = $x_left + $offset;
1602 $u_right = $x_right + $offset - 1;
1603 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1605 if ($v > $zero) {
1606 $top = $zero +1;
1607 $bottom = $v;
1608 } else {
1609 $top = $v;
1610 $bottom = $zero - 1;
1613 switch ($type) {
1614 case 'open':
1615 //ImageRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1616 if ($v > $zero)
1617 ImageRectangle($this->image, round($u_left), $bottom, round($u_right), $bottom, $this->colour[$colour]);
1618 else
1619 ImageRectangle($this->image, round($u_left), $top, round($u_right), $top, $this->colour[$colour]);
1620 ImageRectangle($this->image, round($u_left), $top, round($u_left), $bottom, $this->colour[$colour]);
1621 ImageRectangle($this->image, round($u_right), $top, round($u_right), $bottom, $this->colour[$colour]);
1622 break;
1623 case 'fill':
1624 ImageFilledRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1625 break;
1628 } else {
1630 $bottom = $this->calculated['boundary_box']['bottom'];
1631 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1632 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1633 $u_left = $x_left + $offset;
1634 $u_right = $x_right + $offset - 1;
1635 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1637 // Moodle addition, plus the function parameter yoffset
1638 if ($yoffset) { // Moodle
1639 $yoffset = $yoffset - round(($bottom - $v) / 2.0); // Moodle
1640 $bottom -= $yoffset; // Moodle
1641 $v -= $yoffset; // Moodle
1642 } // Moodle
1644 switch ($type) {
1645 case 'open':
1646 ImageRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1647 break;
1648 case 'fill':
1649 ImageFilledRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1650 break;
1655 function area($x_start, $y_start, $x_end, $y_end, $type, $colour, $offset) {
1656 //dbug("drawing area type: $type, at offset: $offset");
1657 if ($this->parameter['zero_axis'] != 'none') {
1658 $bottom = $this->calculated['boundary_box']['bottom'];
1659 $zero = $this->calculated['zero_axis'];
1660 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1661 $u_start = $x_start + $offset;
1662 $u_end = $x_end + $offset;
1663 $v_start = $bottom - $y_start + $offset;
1664 $v_end = $bottom - $y_end + $offset;
1665 switch ($type) {
1666 case 'fill':
1667 // draw it this way 'cos the FilledPolygon routine seems a bit buggy.
1668 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1669 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1670 break;
1671 case 'open':
1672 //ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1673 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1674 ImageLine($this->image, $u_start, $v_start, $u_start, $zero, $this->colour[$colour]);
1675 ImageLine($this->image, $u_end, $v_end, $u_end, $zero, $this->colour[$colour]);
1676 break;
1678 } else {
1679 $bottom = $this->calculated['boundary_box']['bottom'];
1680 $u_start = $x_start + $offset;
1681 $u_end = $x_end + $offset;
1682 $v_start = $bottom - $y_start + $offset;
1683 $v_end = $bottom - $y_end + $offset;
1685 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1686 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1687 switch ($type) {
1688 case 'fill':
1689 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1690 break;
1691 case 'open':
1692 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1693 break;
1698 function line($x_start, $y_start, $x_end, $y_end, $type, $brush_type, $brush_size, $colour, $offset) {
1699 //dbug("drawing line of type: $type, at offset: $offset");
1700 $u_start = $x_start + $offset;
1701 $v_start = $this->calculated['boundary_box']['bottom'] - $y_start + $offset;
1702 $u_end = $x_end + $offset;
1703 $v_end = $this->calculated['boundary_box']['bottom'] - $y_end + $offset;
1705 switch ($type) {
1706 case 'brush':
1707 $this->draw_brush_line($u_start, $v_start, $u_end, $v_end, $brush_size, $brush_type, $colour);
1708 break;
1709 case 'line' :
1710 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1711 break;
1712 case 'dash':
1713 ImageDashedLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1714 break;
1718 // function to draw line. would prefer to use gdBrush but this is not supported yet.
1719 function draw_brush_line($x0, $y0, $x1, $y1, $size, $type, $colour) {
1720 //$this->dbug("line: $x0, $y0, $x1, $y1");
1721 $dy = $y1 - $y0;
1722 $dx = $x1 - $x0;
1723 $t = 0;
1724 $watchdog = 1024; // precaution to prevent infinite loops.
1726 $this->draw_brush($x0, $y0, $size, $type, $colour);
1727 if (abs($dx) > abs($dy)) { // slope < 1
1728 //$this->dbug("slope < 1");
1729 $m = $dy / $dx; // compute slope
1730 $t += $y0;
1731 $dx = ($dx < 0) ? -1 : 1;
1732 $m *= $dx;
1733 while (round($x0) != round($x1)) {
1734 if (!$watchdog--) break;
1735 $x0 += $dx; // step to next x value
1736 $t += $m; // add slope to y value
1737 $y = round($t);
1738 //$this->dbug("x0=$x0, x1=$x1, y=$y watchdog=$watchdog");
1739 $this->draw_brush($x0, $y, $size, $type, $colour);
1742 } else { // slope >= 1
1743 //$this->dbug("slope >= 1");
1744 $m = $dx / $dy; // compute slope
1745 $t += $x0;
1746 $dy = ($dy < 0) ? -1 : 1;
1747 $m *= $dy;
1748 while (round($y0) != round($y1)) {
1749 if (!$watchdog--) break;
1750 $y0 += $dy; // step to next y value
1751 $t += $m; // add slope to x value
1752 $x = round($t);
1753 //$this->dbug("x=$x, y0=$y0, y1=$y1 watchdog=$watchdog");
1754 $this->draw_brush($x, $y0, $size, $type, $colour);
1760 function draw_brush($x, $y, $size, $type, $colour) {
1761 $x = round($x);
1762 $y = round($y);
1763 $half = round($size / 2);
1764 switch ($type) {
1765 case 'circle':
1766 ImageArc($this->image, $x, $y, $size, $size, 0, 360, $this->colour[$colour]);
1767 ImageFillToBorder($this->image, $x, $y, $this->colour[$colour], $this->colour[$colour]);
1768 break;
1769 case 'square':
1770 ImageFilledRectangle($this->image, $x-$half, $y-$half, $x+$half, $y+$half, $this->colour[$colour]);
1771 break;
1772 case 'vertical':
1773 ImageFilledRectangle($this->image, $x, $y-$half, $x+1, $y+$half, $this->colour[$colour]);
1774 break;
1775 case 'horizontal':
1776 ImageFilledRectangle($this->image, $x-$half, $y, $x+$half, $y+1, $this->colour[$colour]);
1777 break;
1778 case 'slash':
1779 ImageFilledPolygon($this->image, array($x+$half, $y-$half,
1780 $x+$half+1, $y-$half,
1781 $x-$half+1, $y+$half,
1782 $x-$half, $y+$half
1783 ), 4, $this->colour[$colour]);
1784 break;
1785 case 'backslash':
1786 ImageFilledPolygon($this->image, array($x-$half, $y-$half,
1787 $x-$half+1, $y-$half,
1788 $x+$half+1, $y+$half,
1789 $x+$half, $y+$half
1790 ), 4, $this->colour[$colour]);
1791 break;
1792 default:
1793 @eval($type); // user can create own brush script.
1797 } // class graph