OKAY! THIS IS IT! MOODLE 1.6!
[moodle.git] / lib / graphlib.php
blob712cc4c6caa1a3dc0fb3afbb80156385457f67ac
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
26 /* This file contains modifications by Martin Dougiamas
27 * as part of Moodle (http://moodle.com). Modified lines
28 * are marked with "Moodle".
32 class graph {
33 var $image;
34 var $debug = FALSE; // be careful!!
35 var $calculated = array(); // array of computed values for chart
36 var $parameter = array( // input parameters
37 'width' => 320, // default width of image
38 'height' => 240, // default height of image
39 'file_name' => 'none', // name of file for file to be saved as.
40 // NOTE: no suffix required. this is determined from output_format below.
41 'output_format' => 'PNG', // image output format. 'GIF', 'PNG', 'JPEG'. default 'PNG'.
43 'seconds_to_live' => 0, // expiry time in seconds (for HTTP header)
44 'hours_to_live' => 0, // expiry time in hours (for HTTP header)
45 'path_to_fonts' => 'fonts/', // path to fonts folder. don't forget *trailing* slash!!
46 // for WINDOZE this may need to be the full path, not relative.
48 'title' => 'Graph Title', // text for graph title
49 'title_font' => 'default.ttf', // title text font. don't forget to set 'path_to_fonts' above.
50 'title_size' => 16, // title text point size
51 'title_colour' => 'black', // colour for title text
53 'x_label' => '', // if this is set then this text is printed on bottom axis of graph.
54 'y_label_left' => '', // if this is set then this text is printed on left axis of graph.
55 'y_label_right' => '', // if this is set then this text is printed on right axis of graph.
57 'label_size' => 8, // label text point size
58 'label_font' => 'default.ttf', // label text font. don't forget to set 'path_to_fonts' above.
59 'label_colour' => 'gray33', // label text colour
60 'y_label_angle' => 90, // rotation of y axis label
62 'x_label_angle' => 90, // rotation of y axis label
64 'outer_padding' => 5, // padding around outer text. i.e. title, y label, and x label.
65 'inner_padding' => 0, // padding beteen axis text and graph.
66 'x_inner_padding' => 5, // padding beteen axis text and graph.
67 'y_inner_padding' => 6, // padding beteen axis text and graph.
68 'outer_border' => 'none', // colour of border aound image, or 'none'.
69 'inner_border' => 'black', // colour of border around actual graph, or 'none'.
70 'inner_border_type' => 'box', // 'box' for all four sides, 'axis' for x/y axis only,
71 // 'y' or 'y-left' for y axis only, 'y-right' for right y axis only,
72 // 'x' for x axis only, 'u' for both left and right y axis and x axis.
73 'outer_background' => 'none', // background colour of entire image.
74 'inner_background' => 'none', // background colour of plot area.
76 'y_min_left' => 0, // this will be reset to minimum value if there is a value lower than this.
77 'y_max_left' => 0, // this will be reset to maximum value if there is a value higher than this.
78 'y_min_right' => 0, // this will be reset to minimum value if there is a value lower than this.
79 'y_max_right' => 0, // this will be reset to maximum value if there is a value higher than this.
80 'x_min' => 0, // only used if x axis is numeric.
81 'x_max' => 0, // only used if x axis is numeric.
83 'y_resolution_left' => 1, // scaling for rounding of y axis max value.
84 // if max y value is 8645 then
85 // if y_resolution is 0, then y_max becomes 9000.
86 // if y_resolution is 1, then y_max becomes 8700.
87 // if y_resolution is 2, then y_max becomes 8650.
88 // if y_resolution is 3, then y_max becomes 8645.
89 // get it?
90 'y_decimal_left' => 0, // number of decimal places for y_axis text.
91 'y_resolution_right' => 2, // ... same for right hand side
92 'y_decimal_right' => 0, // ... same for right hand side
93 'x_resolution' => 2, // only used if x axis is numeric.
94 'x_decimal' => 0, // only used if x axis is numeric.
96 'point_size' => 4, // default point size. use even number for diamond or triangle to get nice look.
97 'brush_size' => 4, // default brush size for brush line.
98 'brush_type' => 'circle', // type of brush to use to draw line. choose from the following
99 // 'circle', 'square', 'horizontal', 'vertical', 'slash', 'backslash'
100 'bar_size' => 0.8, // size of bar to draw. <1 bars won't touch
101 // 1 is full width - i.e. bars will touch.
102 // >1 means bars will overlap.
103 'bar_spacing' => 10, // space in pixels between group of bars for each x value.
104 'shadow_offset' => 3, // draw shadow at this offset, unless overidden by data parameter.
105 'shadow' => 'grayCC', // 'none' or colour of shadow.
106 'shadow_below_axis' => true, // whether to draw shadows of bars and areas below the x/zero axis.
109 'x_axis_gridlines' => 'auto', // if set to a number then x axis is treated as numeric.
110 'y_axis_gridlines' => 6, // number of gridlines on y axis.
111 'zero_axis' => 'none', // colour to draw zero-axis, or 'none'.
114 'axis_font' => 'default.ttf', // axis text font. don't forget to set 'path_to_fonts' above.
115 'axis_size' => 8, // axis text font size in points
116 'axis_colour' => 'gray33', // colour of axis text.
117 'y_axis_angle' => 0, // rotation of axis text.
118 'x_axis_angle' => 0, // rotation of axis text.
120 'y_axis_text_left' => 1, // whether to print left hand y axis text. if 0 no text, if 1 all ticks have text,
121 'x_axis_text' => 1, // if 4 then print every 4th tick and text, etc...
122 'y_axis_text_right' => 0, // behaviour same as above for right hand y axis.
124 'x_offset' => 0.5, // x axis tick offset from y axis as fraction of tick spacing.
125 'y_ticks_colour' => 'black', // colour to draw y ticks, or 'none'
126 'x_ticks_colour' => 'black', // colour to draw x ticks, or 'none'
127 'y_grid' => 'line', // grid lines. set to 'line' or 'dash'...
128 'x_grid' => 'line', // or if set to 'none' print nothing.
129 'grid_colour' => 'grayEE', // default grid colour.
130 'tick_length' => 4, // length of ticks in pixels. can be negative. i.e. outside data drawing area.
132 'legend' => 'none', // default. no legend.
133 // otherwise: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
134 // 'outside-top', 'outside-bottom', 'outside-left', or 'outside-right'.
135 'legend_offset' => 10, // offset in pixels from graph or outside border.
136 'legend_padding' => 5, // padding around legend text.
137 'legend_font' => 'default.ttf', // legend text font. don't forget to set 'path_to_fonts' above.
138 'legend_size' => 8, // legend text point size.
139 'legend_colour' => 'black', // legend text colour.
140 'legend_border' => 'none', // legend border colour, or 'none'.
142 'decimal_point' => '.', // symbol for decimal separation '.' or ',' *european support.
143 'thousand_sep' => ',', // symbol for thousand separation ',' or ''
149 // init all text - title, labels, and axis text.
150 function init() {
152 /// Moodle mods: overrides the font path and encodings
154 global $CFG;
156 /// A default.ttf is searched for in this order:
157 /// dataroot/lang/xx/fonts
158 /// dirroot/lang/xx/fonts
159 /// lib/
161 $currlang = current_language();
162 if (file_exists("$CFG->dataroot/lang/$currlang/fonts/default.ttf")) {
163 $fontpath = "$CFG->dataroot/lang/$currlang/fonts/";
164 } else if (file_exists("$CFG->dirroot/lang/$currlang/fonts/default.ttf")) {
165 $fontpath = "$CFG->dirroot/lang/$currlang/fonts/";
166 } else {
167 $fontpath = "$CFG->libdir/";
170 $this->parameter['path_to_fonts'] = $fontpath;
172 if (file_exists("$fontpath"."lang_decode.php")) {
173 $this->parameter['lang_decode'] = "$fontpath"."lang_decode.php";
174 } else {
175 $this->parameter['lang_decode'] = "";
178 $this->parameter['lang_transcode'] = ''; /// by default
180 $charset = strtolower(current_charset());
182 if ($charset != 'iso-8859-1' and $charset != 'utf-8') {
183 $this->parameter['lang_transcode'] = $charset;
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 * ($numSets + 1) + $sumTextHeight; // top and bottom padding + padding between text + text.
955 $this->calculated['legend']['boundary_box_all'] = array('width' => $width,
956 'height' => $height,
957 'offset' => $offset,
958 'reference' => $position);
960 switch ($position) { // move in right or bottom if legend is outside data plotting area.
961 case 'outside-top' :
962 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
963 break;
965 case 'outside-bottom' :
966 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
967 break;
969 case 'outside-left' :
970 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
971 break;
973 case 'outside-right' :
974 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
975 break;
979 function init_y_axis() {
980 $this->calculated['y_axis_left'] = array(); // array to hold calculated values for y_axis on left.
981 $this->calculated['y_axis_left']['boundary_box_max'] = $this->get_null_size();
982 $this->calculated['y_axis_right'] = array(); // array to hold calculated values for y_axis on right.
983 $this->calculated['y_axis_right']['boundary_box_max'] = $this->get_null_size();
985 $axis_font = $this->parameter['axis_font'];
986 $axis_size = $this->parameter['axis_size'];
987 $axis_colour = $this->parameter['axis_colour'];
988 $axis_angle = $this->parameter['y_axis_angle'];
989 $y_tick_labels = $this->y_tick_labels;
991 $this->calculated['y_axis_left']['has_data'] = FALSE;
992 $this->calculated['y_axis_right']['has_data'] = FALSE;
994 // find min and max y values.
995 $minLeft = $this->parameter['y_min_left'];
996 $maxLeft = $this->parameter['y_max_left'];
997 $minRight = $this->parameter['y_min_right'];
998 $maxRight = $this->parameter['y_max_right'];
999 $dataLeft = array();
1000 $dataRight = array();
1001 foreach ($this->y_order as $order => $set) {
1002 if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
1003 $this->calculated['y_axis_right']['has_data'] = TRUE;
1004 $dataRight = array_merge($dataRight, $this->y_data[$set]);
1005 } else {
1006 $this->calculated['y_axis_left']['has_data'] = TRUE;
1007 $dataLeft = array_merge($dataLeft, $this->y_data[$set]);
1010 $dataLeftRange = $this->find_range($dataLeft, $minLeft, $maxLeft, $this->parameter['y_resolution_left']);
1011 $dataRightRange = $this->find_range($dataRight, $minRight, $maxRight, $this->parameter['y_resolution_right']);
1012 $minLeft = $dataLeftRange['min'];
1013 $maxLeft = $dataLeftRange['max'];
1014 $minRight = $dataRightRange['min'];
1015 $maxRight = $dataRightRange['max'];
1017 $this->calculated['y_axis_left']['min'] = $minLeft;
1018 $this->calculated['y_axis_left']['max'] = $maxLeft;
1019 $this->calculated['y_axis_right']['min'] = $minRight;
1020 $this->calculated['y_axis_right']['max'] = $maxRight;
1022 $stepLeft = ($maxLeft - $minLeft) / ($this->parameter['y_axis_gridlines'] - 1);
1023 $startLeft = $minLeft;
1024 $step_right = ($maxRight - $minRight) / ($this->parameter['y_axis_gridlines'] - 1);
1025 $start_right = $minRight;
1027 if ($this->parameter['y_axis_text_left']) {
1028 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1029 // left y axis
1030 if ($y_tick_labels) {
1031 $value = $y_tick_labels[$i];
1032 } else {
1033 $value = number_format($startLeft, $this->parameter['y_decimal_left'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1035 $this->calculated['y_axis_left']['data'][$i] = $startLeft;
1036 $this->calculated['y_axis_left']['text'][$i] = $value; // text is formatted raw data
1038 $size = $this->get_boundaryBox(
1039 array('points' => $axis_size,
1040 'font' => $axis_font,
1041 'angle' => $axis_angle,
1042 'colour' => $axis_colour,
1043 'text' => $value));
1044 $this->calculated['y_axis_left']['boundary_box'][$i] = $size;
1046 if ($size['height'] > $this->calculated['y_axis_left']['boundary_box_max']['height'])
1047 $this->calculated['y_axis_left']['boundary_box_max']['height'] = $size['height'];
1048 if ($size['width'] > $this->calculated['y_axis_left']['boundary_box_max']['width'])
1049 $this->calculated['y_axis_left']['boundary_box_max']['width'] = $size['width'];
1051 $startLeft += $stepLeft;
1053 $this->calculated['boundary_box']['left'] += $this->calculated['y_axis_left']['boundary_box_max']['width']
1054 + $this->parameter['y_inner_padding'];
1057 if ($this->parameter['y_axis_text_right']) {
1058 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1059 // right y axis
1060 $value = number_format($start_right, $this->parameter['y_decimal_right'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1061 $this->calculated['y_axis_right']['data'][$i] = $start_right;
1062 $this->calculated['y_axis_right']['text'][$i] = $value; // text is formatted raw data
1063 $size = $this->get_boundaryBox(
1064 array('points' => $axis_size,
1065 'font' => $axis_font,
1066 'angle' => $axis_angle,
1067 'colour' => $axis_colour,
1068 'text' => $value));
1069 $this->calculated['y_axis_right']['boundary_box'][$i] = $size;
1071 if ($size['height'] > $this->calculated['y_axis_right']['boundary_box_max']['height'])
1072 $this->calculated['y_axis_right']['boundary_box_max'] = $size;
1073 if ($size['width'] > $this->calculated['y_axis_right']['boundary_box_max']['width'])
1074 $this->calculated['y_axis_right']['boundary_box_max']['width'] = $size['width'];
1076 $start_right += $step_right;
1078 $this->calculated['boundary_box']['right'] -= $this->calculated['y_axis_right']['boundary_box_max']['width']
1079 + $this->parameter['y_inner_padding'];
1083 function init_x_axis() {
1084 $this->calculated['x_axis'] = array(); // array to hold calculated values for x_axis.
1085 $this->calculated['x_axis']['boundary_box_max'] = array('height' => 0, 'width' => 0);
1087 $axis_font = $this->parameter['axis_font'];
1088 $axis_size = $this->parameter['axis_size'];
1089 $axis_colour = $this->parameter['axis_colour'];
1090 $axis_angle = $this->parameter['x_axis_angle'];
1092 // check whether to treat x axis as numeric
1093 if ($this->parameter['x_axis_gridlines'] == 'auto') { // auto means text based x_axis, not numeric...
1094 $this->calculated['x_axis']['num_ticks'] = sizeof($this->x_data);
1095 $data = $this->x_data;
1096 for ($i=0; $i < $this->calculated['x_axis']['num_ticks']; $i++) {
1097 $value = array_shift($data); // grab value from begin of array
1098 $this->calculated['x_axis']['data'][$i] = $value;
1099 $this->calculated['x_axis']['text'][$i] = $value; // raw data and text are both the same in this case
1100 $size = $this->get_boundaryBox(
1101 array('points' => $axis_size,
1102 'font' => $axis_font,
1103 'angle' => $axis_angle,
1104 'colour' => $axis_colour,
1105 'text' => $value));
1106 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1107 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1108 $this->calculated['x_axis']['boundary_box_max'] = $size;
1111 } else { // x axis is numeric so find max min values...
1112 $this->calculated['x_axis']['num_ticks'] = $this->parameter['x_axis_gridlines'];
1114 $min = $this->parameter['x_min'];
1115 $max = $this->parameter['x_max'];
1116 $data = array();
1117 $data = $this->find_range($this->x_data, $min, $max, $this->parameter['x_resolution']);
1118 $min = $data['min'];
1119 $max = $data['max'];
1120 $this->calculated['x_axis']['min'] = $min;
1121 $this->calculated['x_axis']['max'] = $max;
1123 $step = ($max - $min) / ($this->calculated['x_axis']['num_ticks'] - 1);
1124 $start = $min;
1126 for ($i = 0; $i < $this->calculated['x_axis']['num_ticks']; $i++) { // calculate x axis text sizes
1127 $value = number_format($start, $this->parameter['xDecimal'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1128 $this->calculated['x_axis']['data'][$i] = $start;
1129 $this->calculated['x_axis']['text'][$i] = $value; // text is formatted raw data
1131 $size = $this->get_boundaryBox(
1132 array('points' => $axis_size,
1133 'font' => $axis_font,
1134 'angle' => $axis_angle,
1135 'colour' => $axis_colour,
1136 'text' => $value));
1137 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1139 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1140 $this->calculated['x_axis']['boundary_box_max'] = $size;
1142 $start += $step;
1145 if ($this->parameter['x_axis_text'])
1146 $this->calculated['boundary_box']['bottom'] -= $this->calculated['x_axis']['boundary_box_max']['height']
1147 + $this->parameter['x_inner_padding'];
1150 // find max and min values for a data array given the resolution.
1151 function find_range($data, $min, $max, $resolution) {
1152 if (sizeof($data) == 0 ) return array('min' => 0, 'max' => 0);
1153 foreach ($data as $key => $value) {
1154 if ($value=='none') continue;
1155 if ($value > $max) $max = $value;
1156 if ($value < $min) $min = $value;
1159 if ($max == 0) {
1160 $factor = 1;
1161 } else {
1162 if ($max < 0) $factor = - pow(10, (floor(log10(abs($max))) + $resolution) );
1163 else $factor = pow(10, (floor(log10(abs($max))) - $resolution) );
1165 $factor = round($factor * 1000.0) / 1000.0; // To avoid some wierd rounding errors (Moodle)
1167 $max = $factor * @ceil($max / $factor);
1168 $min = $factor * @floor($min / $factor);
1170 //print "max=$max, min=$min<br />";
1172 return array('min' => $min, 'max' => $max);
1175 function graph() {
1176 if (func_num_args() == 2) {
1177 $this->parameter['width'] = func_get_arg(0);
1178 $this->parameter['height'] = func_get_arg(1);
1180 //$this->boundaryBox = array(
1181 $this->calculated['boundary_box'] = array(
1182 'left' => 0,
1183 'top' => 0,
1184 'right' => $this->parameter['width'] - 1,
1185 'bottom' => $this->parameter['height'] - 1);
1187 $this->init_colours();
1189 //ImageColorTransparent($this->image, $this->colour['white']); // colour for transparency
1192 function print_TTF($message) {
1193 $points = $message['points'];
1194 $angle = $message['angle'];
1195 $text = $message['text'];
1196 $colour = $this->colour[$message['colour']];
1197 $font = $this->parameter['path_to_fonts'].$message['font'];
1199 $x = $message['boundary_box']['x'];
1200 $y = $message['boundary_box']['y'];
1201 $offsetX = $message['boundary_box']['offsetX'];
1202 $offsetY = $message['boundary_box']['offsetY'];
1203 $height = $message['boundary_box']['height'];
1204 $width = $message['boundary_box']['width'];
1205 $reference = $message['boundary_box']['reference'];
1207 switch ($reference) {
1208 case 'top-left':
1209 case 'left-top':
1210 $y += $height - $offsetY;
1211 //$y += $offsetY;
1212 $x += $offsetX;
1213 break;
1214 case 'left-center':
1215 $y += ($height / 2) - $offsetY;
1216 $x += $offsetX;
1217 break;
1218 case 'left-bottom':
1219 $y -= $offsetY;
1220 $x += $offsetX;
1221 break;
1222 case 'top-center':
1223 $y += $height - $offsetY;
1224 $x -= ($width / 2) - $offsetX;
1225 break;
1226 case 'top-right':
1227 case 'right-top':
1228 $y += $height - $offsetY;
1229 $x -= $width - $offsetX;
1230 break;
1231 case 'right-center':
1232 $y += ($height / 2) - $offsetY;
1233 $x -= $width - $offsetX;
1234 break;
1235 case 'right-bottom':
1236 $y -= $offsetY;
1237 $x -= $width - $offsetX;
1238 break;
1239 case 'bottom-center':
1240 $y -= $offsetY;
1241 $x -= ($width / 2) - $offsetX;
1242 break;
1243 default:
1244 $y = 0;
1245 $x = 0;
1246 break;
1248 if ($this->parameter['lang_decode']) { // Moodle addition
1249 include_once($this->parameter['lang_decode']);
1250 $text = lang_decode($text);
1252 } else if ($this->parameter['lang_transcode']) {
1253 $text = iconv($this->parameter['lang_transcode'], 'UTF-8', $text);
1255 ImageTTFText($this->image, $points, $angle, $x, $y, $colour, $font, $text);
1258 // move boundaryBox to coordinates specified
1259 function update_boundaryBox(&$boundaryBox, $coords) {
1260 $width = $boundaryBox['width'];
1261 $height = $boundaryBox['height'];
1262 $x = $coords['x'];
1263 $y = $coords['y'];
1264 $reference = $coords['reference'];
1265 switch ($reference) {
1266 case 'top-left':
1267 case 'left-top':
1268 $top = $y;
1269 $bottom = $y + $height;
1270 $left = $x;
1271 $right = $x + $width;
1272 break;
1273 case 'left-center':
1274 $top = $y - ($height / 2);
1275 $bottom = $y + ($height / 2);
1276 $left = $x;
1277 $right = $x + $width;
1278 break;
1279 case 'left-bottom':
1280 $top = $y - $height;
1281 $bottom = $y;
1282 $left = $x;
1283 $right = $x + $width;
1284 break;
1285 case 'top-center':
1286 $top = $y;
1287 $bottom = $y + $height;
1288 $left = $x - ($width / 2);
1289 $right = $x + ($width / 2);
1290 break;
1291 case 'right-top':
1292 case 'top-right':
1293 $top = $y;
1294 $bottom = $y + $height;
1295 $left = $x - $width;
1296 $right = $x;
1297 break;
1298 case 'right-center':
1299 $top = $y - ($height / 2);
1300 $bottom = $y + ($height / 2);
1301 $left = $x - $width;
1302 $right = $x;
1303 break;
1304 case 'bottom=right':
1305 case 'right-bottom':
1306 $top = $y - $height;
1307 $bottom = $y;
1308 $left = $x - $width;
1309 $right = $x;
1310 break;
1311 default:
1312 $top = 0;
1313 $bottom = $height;
1314 $left = 0;
1315 $right = $width;
1316 break;
1319 $boundaryBox = array_merge($boundaryBox, array('top' => $top,
1320 'bottom' => $bottom,
1321 'left' => $left,
1322 'right' => $right,
1323 'x' => $x,
1324 'y' => $y,
1325 'reference' => $reference));
1328 function get_null_size() {
1329 return array('width' => 0,
1330 'height' => 0,
1331 'offsetX' => 0,
1332 'offsetY' => 0,
1333 //'fontHeight' => 0
1337 function get_boundaryBox($message) {
1338 $points = $message['points'];
1339 $angle = $message['angle'];
1340 $font = $this->parameter['path_to_fonts'].$message['font'];
1341 $text = $message['text'];
1343 //print ('get_boundaryBox');
1344 //expandPre($message);
1346 // get font size
1347 $bounds = ImageTTFBBox($points, $angle, $font, "W");
1348 if ($angle < 0) {
1349 $fontHeight = abs($bounds[7]-$bounds[1]);
1350 } else if ($angle > 0) {
1351 $fontHeight = abs($bounds[1]-$bounds[7]);
1352 } else {
1353 $fontHeight = abs($bounds[7]-$bounds[1]);
1356 // get boundary box and offsets for printing at an angle
1357 if ($this->parameter['lang_decode']) { // Moodle addition
1358 include_once($this->parameter['lang_decode']);
1359 $text = lang_decode($text);
1361 } else if ($this->parameter['lang_transcode']) {
1362 $text = iconv($this->parameter['lang_transcode'], 'UTF-8', $text);
1364 $bounds = ImageTTFBBox($points, $angle, $font, $text);
1366 if ($angle < 0) {
1367 $width = abs($bounds[4]-$bounds[0]);
1368 $height = abs($bounds[3]-$bounds[7]);
1369 $offsetY = abs($bounds[3]-$bounds[1]);
1370 $offsetX = 0;
1372 } else if ($angle > 0) {
1373 $width = abs($bounds[2]-$bounds[6]);
1374 $height = abs($bounds[1]-$bounds[5]);
1375 $offsetY = 0;
1376 $offsetX = abs($bounds[0]-$bounds[6]);
1378 } else {
1379 $width = abs($bounds[4]-$bounds[6]);
1380 $height = abs($bounds[7]-$bounds[1]);
1381 $offsetY = 0;
1382 $offsetX = 0;
1385 //return values
1386 return array('width' => $width,
1387 'height' => $height,
1388 'offsetX' => $offsetX,
1389 'offsetY' => $offsetY,
1390 //'fontHeight' => $fontHeight
1394 function draw_rectangle($border, $colour, $type) {
1395 $colour = $this->colour[$colour];
1396 switch ($type) {
1397 case 'fill': // fill the rectangle
1398 ImageFilledRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1399 break;
1400 case 'box': // all sides
1401 ImageRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1402 break;
1403 case 'axis': // bottom x axis and left y axis
1404 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1405 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1406 break;
1407 case 'y': // left y axis only
1408 case 'y-left':
1409 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1410 break;
1411 case 'y-right': // right y axis only
1412 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1413 break;
1414 case 'x': // bottom x axis only
1415 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1416 break;
1417 case 'u': // u shaped. bottom x axis and both left and right y axis.
1418 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1419 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1420 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1421 break;
1426 function init_colours() {
1427 $this->image = ImageCreate($this->parameter['width'], $this->parameter['height']);
1428 // standard colours
1429 $this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF); // first colour is background colour.
1430 $this->colour['black'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x00);
1431 $this->colour['maroon'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x00);
1432 $this->colour['green'] = ImageColorAllocate ($this->image, 0x00, 0x80, 0x00);
1433 $this->colour['ltgreen'] = ImageColorAllocate ($this->image, 0x52, 0xF1, 0x7F);
1434 $this->colour['ltltgreen']= ImageColorAllocate ($this->image, 0x99, 0xFF, 0x99);
1435 $this->colour['olive'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x00);
1436 $this->colour['navy'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x80);
1437 $this->colour['purple'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x80);
1438 $this->colour['gray'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x80);
1439 $this->colour['red'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0x00);
1440 $this->colour['ltred'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x99);
1441 $this->colour['ltltred'] = ImageColorAllocate ($this->image, 0xFF, 0xCC, 0xCC);
1442 $this->colour['orange'] = ImageColorAllocate ($this->image, 0xFF, 0x66, 0x00);
1443 $this->colour['ltorange'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x66);
1444 $this->colour['ltltorange'] = ImageColorAllocate ($this->image, 0xFF, 0xcc, 0x99);
1445 $this->colour['lime'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0x00);
1446 $this->colour['yellow'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0x00);
1447 $this->colour['blue'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0xFF);
1448 $this->colour['ltblue'] = ImageColorAllocate ($this->image, 0x00, 0xCC, 0xFF);
1449 $this->colour['ltltblue'] = ImageColorAllocate ($this->image, 0x99, 0xFF, 0xFF);
1450 $this->colour['fuchsia'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0xFF);
1451 $this->colour['aqua'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0xFF);
1452 //$this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF);
1453 // shades of gray
1454 $this->colour['grayF0'] = ImageColorAllocate ($this->image, 0xF0, 0xF0, 0xF0);
1455 $this->colour['grayEE'] = ImageColorAllocate ($this->image, 0xEE, 0xEE, 0xEE);
1456 $this->colour['grayDD'] = ImageColorAllocate ($this->image, 0xDD, 0xDD, 0xDD);
1457 $this->colour['grayCC'] = ImageColorAllocate ($this->image, 0xCC, 0xCC, 0xCC);
1458 $this->colour['gray33'] = ImageColorAllocate ($this->image, 0x33, 0x33, 0x33);
1459 $this->colour['gray66'] = ImageColorAllocate ($this->image, 0x66, 0x66, 0x66);
1460 $this->colour['gray99'] = ImageColorAllocate ($this->image, 0x99, 0x99, 0x99);
1462 $this->colour['none'] = 'none';
1463 return true;
1466 function output() {
1467 if ($this->debug) { // for debugging purposes.
1468 //expandPre($this->graph);
1469 //expandPre($this->y_data);
1470 //expandPre($this->x_data);
1471 //expandPre($this->parameter);
1472 } else {
1474 $expiresSeconds = $this->parameter['seconds_to_live'];
1475 $expiresHours = $this->parameter['hours_to_live'];
1477 if ($expiresHours || $expiresSeconds) {
1478 $now = mktime (date("H"),date("i"),date("s"),date("m"),date("d"),date("Y"));
1479 $expires = mktime (date("H")+$expiresHours,date("i"),date("s")+$expiresSeconds,date("m"),date("d"),date("Y"));
1480 $expiresGMT = gmdate('D, d M Y H:i:s', $expires).' GMT';
1481 $lastModifiedGMT = gmdate('D, d M Y H:i:s', $now).' GMT';
1483 Header('Last-modified: '.$lastModifiedGMT);
1484 Header('Expires: '.$expiresGMT);
1487 if ($this->parameter['file_name'] == 'none') {
1488 switch ($this->parameter['output_format']) {
1489 case 'GIF':
1490 Header("Content-type: image/gif"); // GIF??. switch to PNG guys!!
1491 ImageGIF($this->image);
1492 break;
1493 case 'JPEG':
1494 Header("Content-type: image/jpeg"); // JPEG for line art??. included for completeness.
1495 ImageJPEG($this->image);
1496 break;
1497 default:
1498 Header("Content-type: image/png"); // preferred output format
1499 ImagePNG($this->image);
1500 break;
1502 } else {
1503 switch ($this->parameter['output_format']) {
1504 case 'GIF':
1505 ImageGIF($this->image, $this->parameter['file_name'].'.gif');
1506 break;
1507 case 'JPEG':
1508 ImageJPEG($this->image, $this->parameter['file_name'].'.jpg');
1509 break;
1510 default:
1511 ImagePNG($this->image, $this->parameter['file_name'].'.png');
1512 break;
1516 ImageDestroy($this->image);
1518 } // function output
1520 function init_variable(&$variable, $value, $default) {
1521 if (!empty($value)) $variable = $value;
1522 else if (isset($default)) $variable = $default;
1523 else unset($variable);
1526 // plot a point. options include square, circle, diamond, triangle, and dot. offset is used for drawing shadows.
1527 // for diamonds and triangles the size should be an even number to get nice look. if odd the points are crooked.
1528 function plot($x, $y, $type, $size, $colour, $offset) {
1529 //print("drawing point of type: $type, at offset: $offset");
1530 $u = $x + $offset;
1531 $v = $this->calculated['inner_border']['bottom'] - $y + $offset;
1532 $half = $size / 2;
1534 switch ($type) {
1535 case 'square':
1536 ImageFilledRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1537 break;
1538 case 'square-open':
1539 ImageRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1540 break;
1541 case 'circle':
1542 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1543 ImageFillToBorder($this->image, $u, $v, $this->colour[$colour], $this->colour[$colour]);
1544 break;
1545 case 'circle-open':
1546 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1547 break;
1548 case 'diamond':
1549 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1550 break;
1551 case 'diamond-open':
1552 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1553 break;
1554 case 'triangle':
1555 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1556 break;
1557 case 'triangle-open':
1558 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1559 break;
1560 case 'dot':
1561 ImageSetPixel($this->image, $u, $v, $this->colour[$colour]);
1562 break;
1566 function bar($x, $y, $type, $size, $colour, $offset, $index, $yoffset) {
1567 $index_offset = $this->calculated['bar_offset_index'][$index];
1568 if ( $yoffset ) {
1569 $bar_offsetx = 0;
1570 } else {
1571 $bar_offsetx = $this->calculated['bar_offset_x'][$index_offset];
1573 //$this->dbug("drawing bar at offset = $offset : index = $index: bar_offsetx = $bar_offsetx");
1575 $span = ($this->calculated['bar_width'] * $size) / 2;
1576 $x_left = $x + $bar_offsetx - $span;
1577 $x_right = $x + $bar_offsetx + $span;
1579 if ($this->parameter['zero_axis'] != 'none') {
1580 $zero = $this->calculated['zero_axis'];
1581 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1582 $u_left = $x_left + $offset;
1583 $u_right = $x_right + $offset - 1;
1584 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1586 if ($v > $zero) {
1587 $top = $zero +1;
1588 $bottom = $v;
1589 } else {
1590 $top = $v;
1591 $bottom = $zero - 1;
1594 switch ($type) {
1595 case 'open':
1596 //ImageRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1597 if ($v > $zero)
1598 ImageRectangle($this->image, round($u_left), $bottom, round($u_right), $bottom, $this->colour[$colour]);
1599 else
1600 ImageRectangle($this->image, round($u_left), $top, round($u_right), $top, $this->colour[$colour]);
1601 ImageRectangle($this->image, round($u_left), $top, round($u_left), $bottom, $this->colour[$colour]);
1602 ImageRectangle($this->image, round($u_right), $top, round($u_right), $bottom, $this->colour[$colour]);
1603 break;
1604 case 'fill':
1605 ImageFilledRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1606 break;
1609 } else {
1611 $bottom = $this->calculated['boundary_box']['bottom'];
1612 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1613 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1614 $u_left = $x_left + $offset;
1615 $u_right = $x_right + $offset - 1;
1616 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1618 // Moodle addition, plus the function parameter yoffset
1619 if ($yoffset) { // Moodle
1620 $yoffset = $yoffset - round(($bottom - $v) / 2.0); // Moodle
1621 $bottom -= $yoffset; // Moodle
1622 $v -= $yoffset; // Moodle
1623 } // Moodle
1625 switch ($type) {
1626 case 'open':
1627 ImageRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1628 break;
1629 case 'fill':
1630 ImageFilledRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1631 break;
1636 function area($x_start, $y_start, $x_end, $y_end, $type, $colour, $offset) {
1637 //dbug("drawing area type: $type, at offset: $offset");
1638 if ($this->parameter['zero_axis'] != 'none') {
1639 $bottom = $this->calculated['boundary_box']['bottom'];
1640 $zero = $this->calculated['zero_axis'];
1641 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1642 $u_start = $x_start + $offset;
1643 $u_end = $x_end + $offset;
1644 $v_start = $bottom - $y_start + $offset;
1645 $v_end = $bottom - $y_end + $offset;
1646 switch ($type) {
1647 case 'fill':
1648 // draw it this way 'cos the FilledPolygon routine seems a bit buggy.
1649 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1650 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1651 break;
1652 case 'open':
1653 //ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1654 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1655 ImageLine($this->image, $u_start, $v_start, $u_start, $zero, $this->colour[$colour]);
1656 ImageLine($this->image, $u_end, $v_end, $u_end, $zero, $this->colour[$colour]);
1657 break;
1659 } else {
1660 $bottom = $this->calculated['boundary_box']['bottom'];
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;
1666 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1667 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1668 switch ($type) {
1669 case 'fill':
1670 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1671 break;
1672 case 'open':
1673 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1674 break;
1679 function line($x_start, $y_start, $x_end, $y_end, $type, $brush_type, $brush_size, $colour, $offset) {
1680 //dbug("drawing line of type: $type, at offset: $offset");
1681 $u_start = $x_start + $offset;
1682 $v_start = $this->calculated['boundary_box']['bottom'] - $y_start + $offset;
1683 $u_end = $x_end + $offset;
1684 $v_end = $this->calculated['boundary_box']['bottom'] - $y_end + $offset;
1686 switch ($type) {
1687 case 'brush':
1688 $this->draw_brush_line($u_start, $v_start, $u_end, $v_end, $brush_size, $brush_type, $colour);
1689 break;
1690 case 'line' :
1691 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1692 break;
1693 case 'dash':
1694 ImageDashedLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1695 break;
1699 // function to draw line. would prefer to use gdBrush but this is not supported yet.
1700 function draw_brush_line($x0, $y0, $x1, $y1, $size, $type, $colour) {
1701 //$this->dbug("line: $x0, $y0, $x1, $y1");
1702 $dy = $y1 - $y0;
1703 $dx = $x1 - $x0;
1704 $t = 0;
1705 $watchdog = 1024; // precaution to prevent infinite loops.
1707 $this->draw_brush($x0, $y0, $size, $type, $colour);
1708 if (abs($dx) > abs($dy)) { // slope < 1
1709 //$this->dbug("slope < 1");
1710 $m = $dy / $dx; // compute slope
1711 $t += $y0;
1712 $dx = ($dx < 0) ? -1 : 1;
1713 $m *= $dx;
1714 while (round($x0) != round($x1)) {
1715 if (!$watchdog--) break;
1716 $x0 += $dx; // step to next x value
1717 $t += $m; // add slope to y value
1718 $y = round($t);
1719 //$this->dbug("x0=$x0, x1=$x1, y=$y watchdog=$watchdog");
1720 $this->draw_brush($x0, $y, $size, $type, $colour);
1723 } else { // slope >= 1
1724 //$this->dbug("slope >= 1");
1725 $m = $dx / $dy; // compute slope
1726 $t += $x0;
1727 $dy = ($dy < 0) ? -1 : 1;
1728 $m *= $dy;
1729 while (round($y0) != round($y1)) {
1730 if (!$watchdog--) break;
1731 $y0 += $dy; // step to next y value
1732 $t += $m; // add slope to x value
1733 $x = round($t);
1734 //$this->dbug("x=$x, y0=$y0, y1=$y1 watchdog=$watchdog");
1735 $this->draw_brush($x, $y0, $size, $type, $colour);
1741 function draw_brush($x, $y, $size, $type, $colour) {
1742 $x = round($x);
1743 $y = round($y);
1744 $half = round($size / 2);
1745 switch ($type) {
1746 case 'circle':
1747 ImageArc($this->image, $x, $y, $size, $size, 0, 360, $this->colour[$colour]);
1748 ImageFillToBorder($this->image, $x, $y, $this->colour[$colour], $this->colour[$colour]);
1749 break;
1750 case 'square':
1751 ImageFilledRectangle($this->image, $x-$half, $y-$half, $x+$half, $y+$half, $this->colour[$colour]);
1752 break;
1753 case 'vertical':
1754 ImageFilledRectangle($this->image, $x, $y-$half, $x+1, $y+$half, $this->colour[$colour]);
1755 break;
1756 case 'horizontal':
1757 ImageFilledRectangle($this->image, $x-$half, $y, $x+$half, $y+1, $this->colour[$colour]);
1758 break;
1759 case 'slash':
1760 ImageFilledPolygon($this->image, array($x+$half, $y-$half,
1761 $x+$half+1, $y-$half,
1762 $x-$half+1, $y+$half,
1763 $x-$half, $y+$half
1764 ), 4, $this->colour[$colour]);
1765 break;
1766 case 'backslash':
1767 ImageFilledPolygon($this->image, array($x-$half, $y-$half,
1768 $x-$half+1, $y-$half,
1769 $x+$half+1, $y+$half,
1770 $x+$half, $y+$half
1771 ), 4, $this->colour[$colour]);
1772 break;
1773 default:
1774 @eval($type); // user can create own brush script.
1778 } // class graph