file maxhighscores.html was added on branch MOODLE_15_STABLE on 2005-07-07 16:59...
[moodle.git] / lib / graphlib.php
blobb284468088931a65edc3a04499d6a421227bc478
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 /// lang/xx/fonts
158 /// lib/
159 /// lang/en
161 $currlang = current_language();
162 if (file_exists("$CFG->dirroot/lang/$currlang/fonts/default.ttf")) {
163 $fontpath = "$CFG->dirroot/lang/$currlang/fonts/";
165 } else if (file_exists("$CFG->libdir/default.ttf")) {
166 $fontpath = "$CFG->libdir/";
168 } else {
169 $fontpath = "$CFG->dirroot/lang/en/fonts/";
172 $this->parameter['path_to_fonts'] = $fontpath;
174 if (file_exists("$fontpath"."lang_decode.php")) {
175 $this->parameter['lang_decode'] = "$fontpath"."lang_decode.php";
176 } else {
177 $this->parameter['lang_decode'] = "";
180 $this->parameter['lang_transcode'] = ''; /// by default
182 if (function_exists('iconv')) {
183 $charset = strtolower(get_string('thischarset'));
185 if ($charset != 'iso-8859-1' and $charset != 'utf-8') {
186 $this->parameter['lang_transcode'] = $charset;
190 /// End Moodle mods
194 $this->calculated['outer_border'] = $this->calculated['boundary_box'];
196 // outer padding
197 $this->calculated['boundary_box']['left'] += $this->parameter['outer_padding'];
198 $this->calculated['boundary_box']['top'] += $this->parameter['outer_padding'];
199 $this->calculated['boundary_box']['right'] -= $this->parameter['outer_padding'];
200 $this->calculated['boundary_box']['bottom'] -= $this->parameter['outer_padding'];
202 $this->init_x_axis();
203 $this->init_y_axis();
204 $this->init_legend();
205 $this->init_labels();
207 // take into account tick lengths
208 $this->calculated['bottom_inner_padding'] = $this->parameter['x_inner_padding'];
209 if (($this->parameter['x_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
210 $this->calculated['bottom_inner_padding'] -= $this->parameter['tick_length'];
211 $this->calculated['boundary_box']['bottom'] -= $this->calculated['bottom_inner_padding'];
213 $this->calculated['left_inner_padding'] = $this->parameter['y_inner_padding'];
214 if ($this->parameter['y_axis_text_left']) {
215 if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
216 $this->calculated['left_inner_padding'] -= $this->parameter['tick_length'];
218 $this->calculated['boundary_box']['left'] += $this->calculated['left_inner_padding'];
220 $this->calculated['right_inner_padding'] = $this->parameter['y_inner_padding'];
221 if ($this->parameter['y_axis_text_right']) {
222 if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
223 $this->calculated['right_inner_padding'] -= $this->parameter['tick_length'];
225 $this->calculated['boundary_box']['right'] -= $this->calculated['right_inner_padding'];
227 // boundaryBox now has coords for plotting area.
228 $this->calculated['inner_border'] = $this->calculated['boundary_box'];
230 $this->init_data();
231 $this->init_x_ticks();
232 $this->init_y_ticks();
235 function draw_text() {
236 $colour = $this->parameter['outer_background'];
237 if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'fill'); // graph background
239 // draw border around image
240 $colour = $this->parameter['outer_border'];
241 if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'box'); // graph border
243 $this->draw_title();
244 $this->draw_x_label();
245 $this->draw_y_label_left();
246 $this->draw_y_label_right();
247 $this->draw_x_axis();
248 $this->draw_y_axis();
249 if ($this->calculated['y_axis_left']['has_data']) $this->draw_zero_axis_left(); // either draw zero axis on left
250 else if ($this->calculated['y_axis_right']['has_data']) $this->draw_zero_axis_right(); // ... or right.
251 $this->draw_legend();
253 // draw border around plot area
254 $colour = $this->parameter['inner_background'];
255 if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, 'fill'); // graph background
257 // draw border around image
258 $colour = $this->parameter['inner_border'];
259 if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, $this->parameter['inner_border_type']); // graph border
262 function draw_stack() {
263 $this->init();
264 $this->draw_text();
266 $yOrder = $this->y_order; // save y_order data.
267 // iterate over each data set. order is very important if you want to see data correctly. remember shadows!!
268 foreach ($yOrder as $set) {
269 $this->y_order = array($set);
270 $this->init_data();
271 $this->draw_data();
273 $this->y_order = $yOrder; // revert y_order data.
275 $this->output();
278 function draw() {
279 $this->init();
280 $this->draw_text();
281 $this->draw_data();
282 $this->output();
285 // draw a data set
286 function draw_set($order, $set, $offset) {
287 if ($offset) @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
288 else $colour = $this->y_format[$set]['colour'];
289 @$this->init_variable($point, $this->y_format[$set]['point'], 'none');
290 @$this->init_variable($pointSize, $this->y_format[$set]['point_size'], $this->parameter['point_size']);
291 @$this->init_variable($line, $this->y_format[$set]['line'], 'none');
292 @$this->init_variable($brushType, $this->y_format[$set]['brush_type'], $this->parameter['brush_type']);
293 @$this->init_variable($brushSize, $this->y_format[$set]['brush_size'], $this->parameter['brush_size']);
294 @$this->init_variable($bar, $this->y_format[$set]['bar'], 'none');
295 @$this->init_variable($barSize, $this->y_format[$set]['bar_size'], $this->parameter['bar_size']);
296 @$this->init_variable($area, $this->y_format[$set]['area'], 'none');
298 $lastX = 0;
299 $lastY = 'none';
300 $fromX = 0;
301 $fromY = 'none';
303 //print "set $set<br />";
304 //expand_pre($this->calculated['y_plot']);
306 foreach ($this->x_data as $index => $x) {
307 //print "index $index<br />";
308 $thisY = $this->calculated['y_plot'][$set][$index];
309 $thisX = $this->calculated['x_plot'][$index];
311 //print "$thisX, $thisY <br />";
313 if (($bar!='none') && (string)$thisY != 'none') {
314 if ($relatedset = $this->offset_relation[$set]) { // Moodle
315 $yoffset = $this->calculated['y_plot'][$relatedset][$index]; // Moodle
316 } else { // Moodle
317 $yoffset = 0; // Moodle
318 } // Moodle
319 //$this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set); // Moodle
320 $this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set, $yoffset); // Moodle
323 if (($area!='none') && (((string)$lastY != 'none') && ((string)$thisY != 'none')))
324 $this->area($lastX, $lastY, $thisX, $thisY, $area, $colour, $offset);
326 if (($point!='none') && (string)$thisY != 'none') $this->plot($thisX, $thisY, $point, $pointSize, $colour, $offset);
328 if (($line!='none') && ((string)$thisY != 'none')) {
329 if ((string)$fromY != 'none')
330 $this->line($fromX, $fromY, $thisX, $thisY, $line, $brushType, $brushSize, $colour, $offset);
332 $fromY = $thisY; // start next line from here
333 $fromX = $thisX; // ...
334 } else {
335 $fromY = 'none';
336 $fromX = 'none';
339 $lastX = $thisX;
340 $lastY = $thisY;
344 function draw_data() {
345 // cycle thru y data to be plotted
346 // first check for drop shadows...
347 foreach ($this->y_order as $order => $set) {
348 @$this->init_variable($offset, $this->y_format[$set]['shadow_offset'], $this->parameter['shadow_offset']);
349 @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
350 if ($colour != 'none') $this->draw_set($order, $set, $offset);
354 // then draw data
355 foreach ($this->y_order as $order => $set) {
356 $this->draw_set($order, $set, 0);
360 function draw_legend() {
361 $position = $this->parameter['legend'];
362 if ($position == 'none') return; // abort if no border
364 $borderColour = $this->parameter['legend_border'];
365 $offset = $this->parameter['legend_offset'];
366 $padding = $this->parameter['legend_padding'];
367 $height = $this->calculated['legend']['boundary_box_all']['height'];
368 $width = $this->calculated['legend']['boundary_box_all']['width'];
369 $graphTop = $this->calculated['boundary_box']['top'];
370 $graphBottom = $this->calculated['boundary_box']['bottom'];
371 $graphLeft = $this->calculated['boundary_box']['left'];
372 $graphRight = $this->calculated['boundary_box']['right'];
373 $outsideRight = $this->calculated['outer_border']['right'];
374 $outsideBottom = $this->calculated['outer_border']['bottom'];
375 switch ($position) {
376 case 'top-left':
377 $top = $graphTop + $offset;
378 $bottom = $graphTop + $height + $offset;
379 $left = $graphLeft + $offset;
380 $right = $graphLeft + $width + $offset;
382 break;
383 case 'top-right':
384 $top = $graphTop + $offset;
385 $bottom = $graphTop + $height + $offset;
386 $left = $graphRight - $width - $offset;
387 $right = $graphRight - $offset;
389 break;
390 case 'bottom-left':
391 $top = $graphBottom - $height - $offset;
392 $bottom = $graphBottom - $offset;
393 $left = $graphLeft + $offset;
394 $right = $graphLeft + $width + $offset;
396 break;
397 case 'bottom-right':
398 $top = $graphBottom - $height - $offset;
399 $bottom = $graphBottom - $offset;
400 $left = $graphRight - $width - $offset;
401 $right = $graphRight - $offset;
402 break;
404 case 'outside-top' :
405 $top = $graphTop;
406 $bottom = $graphTop + $height;
407 $left = $outsideRight - $width - $offset;
408 $right = $outsideRight - $offset;
409 break;
411 case 'outside-bottom' :
412 $top = $graphBottom - $height;
413 $bottom = $graphBottom;
414 $left = $outsideRight - $width - $offset;
415 $right = $outsideRight - $offset;
416 break;
418 case 'outside-left' :
419 $top = $outsideBottom - $height - $offset;
420 $bottom = $outsideBottom - $offset;
421 $left = $graphLeft;
422 $right = $graphLeft + $width;
423 break;
425 case 'outside-right' :
426 $top = $outsideBottom - $height - $offset;
427 $bottom = $outsideBottom - $offset;
428 $left = $graphRight - $width;
429 $right = $graphRight;
430 break;
431 default: // default is top left. no particular reason.
432 $top = $this->calculated['boundary_box']['top'];
433 $bottom = $this->calculated['boundary_box']['top'] + $this->calculated['legend']['boundary_box_all']['height'];
434 $left = $this->calculated['boundary_box']['left'];
435 $right = $this->calculated['boundary_box']['right'] + $this->calculated['legend']['boundary_box_all']['width'];
438 // legend border
439 if($borderColour!='none') $this->draw_rectangle(array('top' => $top,
440 'left' => $left,
441 'bottom' => $bottom,
442 'right' => $right), $this->parameter['legend_border'], 'box');
444 // legend text
445 $legendText = array('points' => $this->parameter['legend_size'],
446 'angle' => 0,
447 'font' => $this->parameter['legend_font'],
448 'colour' => $this->parameter['legend_colour']);
450 $box = $this->calculated['legend']['boundary_box_max']['height']; // use max height for legend square size.
451 $x = $left + $padding;
452 $x_text = $x + $box * 2;
453 $y = $top + $padding;
455 foreach ($this->y_order as $set) {
456 $legendText['text'] = $this->calculated['legend']['text'][$set];
457 if ($legendText['text'] != 'none') {
458 // if text exists then draw box and text
459 $boxColour = $this->colour[$this->y_format[$set]['colour']];
461 // draw box
462 ImageFilledRectangle($this->image, $x, $y, $x + $box, $y + $box, $boxColour);
464 // draw text
465 $coords = array('x' => $x + $box * 2, 'y' => $y, 'reference' => 'top-left');
466 $legendText['boundary_box'] = $this->calculated['legend']['boundary_box'][$set];
467 $this->update_boundaryBox($legendText['boundary_box'], $coords);
468 $this->print_TTF($legendText);
469 $y += $padding + $box;
475 function draw_y_label_right() {
476 if (!$this->parameter['y_label_right']) return;
477 $x = $this->calculated['boundary_box']['right'] + $this->parameter['y_inner_padding'];
478 if ($this->parameter['y_axis_text_right']) $x += $this->calculated['y_axis_right']['boundary_box_max']['width']
479 + $this->calculated['right_inner_padding'];
480 $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
482 $label = $this->calculated['y_label_right'];
483 $coords = array('x' => $x, 'y' => $y, 'reference' => 'left-center');
484 $this->update_boundaryBox($label['boundary_box'], $coords);
485 $this->print_TTF($label);
489 function draw_y_label_left() {
490 if (!$this->parameter['y_label_left']) return;
491 $x = $this->calculated['boundary_box']['left'] - $this->parameter['y_inner_padding'];
492 if ($this->parameter['y_axis_text_left']) $x -= $this->calculated['y_axis_left']['boundary_box_max']['width']
493 + $this->calculated['left_inner_padding'];
494 $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
496 $label = $this->calculated['y_label_left'];
497 $coords = array('x' => $x, 'y' => $y, 'reference' => 'right-center');
498 $this->update_boundaryBox($label['boundary_box'], $coords);
499 $this->print_TTF($label);
502 function draw_title() {
503 if (!$this->parameter['title']) return;
504 //$y = $this->calculated['outside_border']['top'] + $this->parameter['outer_padding'];
505 $y = $this->calculated['boundary_box']['top'] - $this->parameter['outer_padding'];
506 $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
507 $label = $this->calculated['title'];
508 $coords = array('x' => $x, 'y' => $y, 'reference' => 'bottom-center');
509 $this->update_boundaryBox($label['boundary_box'], $coords);
510 $this->print_TTF($label);
513 function draw_x_label() {
514 if (!$this->parameter['x_label']) return;
515 $y = $this->calculated['boundary_box']['bottom'] + $this->parameter['x_inner_padding'];
516 if ($this->parameter['x_axis_text']) $y += $this->calculated['x_axis']['boundary_box_max']['height']
517 + $this->calculated['bottom_inner_padding'];
518 $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
519 $label = $this->calculated['x_label'];
520 $coords = array('x' => $x, 'y' => $y, 'reference' => 'top-center');
521 $this->update_boundaryBox($label['boundary_box'], $coords);
522 $this->print_TTF($label);
525 function draw_zero_axis_left() {
526 $colour = $this->parameter['zero_axis'];
527 if ($colour == 'none') return;
528 // draw zero axis on left hand side
529 $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top'] + ($this->calculated['y_axis_left']['max'] * $this->calculated['y_axis_left']['factor']));
530 ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
533 function draw_zero_axis_right() {
534 $colour = $this->parameter['zero_axis'];
535 if ($colour == 'none') return;
536 // draw zero axis on right hand side
537 $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top'] + ($this->calculated['y_axis_right']['max'] * $this->calculated['y_axis_right']['factor']));
538 ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
541 function draw_x_axis() {
542 $gridColour = $this->colour[$this->parameter['grid_colour']];
543 $tickColour = $this->colour[$this->parameter['x_ticks_colour']];
544 $axis_colour = $this->parameter['axis_colour'];
545 $xGrid = $this->parameter['x_grid'];
546 $gridTop = $this->calculated['boundary_box']['top'];
547 $gridBottom = $this->calculated['boundary_box']['bottom'];
549 if ($this->parameter['tick_length'] >= 0) {
550 $tickTop = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
551 $tickBottom = $this->calculated['boundary_box']['bottom'];
552 $textBottom = $tickBottom + $this->calculated['bottom_inner_padding'];
553 } else {
554 $tickTop = $this->calculated['boundary_box']['bottom'];
555 $tickBottom = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
556 $textBottom = $tickBottom + $this->calculated['bottom_inner_padding'];
559 $axis_font = $this->parameter['axis_font'];
560 $axis_size = $this->parameter['axis_size'];
561 $axis_angle = $this->parameter['x_axis_angle'];
563 if ($axis_angle == 0) $reference = 'top-center';
564 if ($axis_angle > 0) $reference = 'top-right';
565 if ($axis_angle < 0) $reference = 'top-left';
566 if ($axis_angle == 90) $reference = 'top-center';
568 //generic tag information. applies to all axis text.
569 $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
571 foreach ($this->calculated['x_axis']['tick_x'] as $set => $tickX) {
572 // draw x grid if colour specified
573 if ($xGrid != 'none') {
574 switch ($xGrid) {
575 case 'line':
576 ImageLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
577 break;
578 case 'dash':
579 ImageDashedLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
580 break;
584 if ($this->parameter['x_axis_text'] && !($set % $this->parameter['x_axis_text'])) { // test if tick should be displayed
585 // draw tick
586 if ($tickColour != 'none')
587 ImageLine($this->image, round($tickX), round($tickTop), round($tickX), round($tickBottom), $tickColour);
589 // draw axis text
590 $coords = array('x' => $tickX, 'y' => $textBottom, 'reference' => $reference);
591 $axisTag['text'] = $this->calculated['x_axis']['text'][$set];
592 $axisTag['boundary_box'] = $this->calculated['x_axis']['boundary_box'][$set];
593 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
594 $this->print_TTF($axisTag);
599 function draw_y_axis() {
600 $gridColour = $this->colour[$this->parameter['grid_colour']];
601 $tickColour = $this->colour[$this->parameter['y_ticks_colour']];
602 $axis_colour = $this->parameter['axis_colour'];
603 $yGrid = $this->parameter['y_grid'];
604 $gridLeft = $this->calculated['boundary_box']['left'];
605 $gridRight = $this->calculated['boundary_box']['right'];
607 // axis font information
608 $axis_font = $this->parameter['axis_font'];
609 $axis_size = $this->parameter['axis_size'];
610 $axis_angle = $this->parameter['y_axis_angle'];
611 $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
614 if ($this->calculated['y_axis_left']['has_data']) {
615 // LEFT HAND SIDE
616 // left and right coords for ticks
617 if ($this->parameter['tick_length'] >= 0) {
618 $tickLeft = $this->calculated['boundary_box']['left'];
619 $tickRight = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
620 } else {
621 $tickLeft = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
622 $tickRight = $this->calculated['boundary_box']['left'];
624 $textRight = $tickLeft - $this->calculated['left_inner_padding'];
626 if ($axis_angle == 0) $reference = 'right-center';
627 if ($axis_angle > 0) $reference = 'right-top';
628 if ($axis_angle < 0) $reference = 'right-bottom';
629 if ($axis_angle == 90) $reference = 'right-center';
631 foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
632 // draw y grid if colour specified
633 if ($yGrid != 'none') {
634 switch ($yGrid) {
635 case 'line':
636 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
637 break;
638 case 'dash':
639 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
640 break;
644 // y axis text
645 if ($this->parameter['y_axis_text_left'] && !($set % $this->parameter['y_axis_text_left'])) { // test if tick should be displayed
646 // draw tick
647 if ($tickColour != 'none')
648 ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
650 // draw axis text...
651 $coords = array('x' => $textRight, 'y' => $tickY, 'reference' => $reference);
652 $axisTag['text'] = $this->calculated['y_axis_left']['text'][$set];
653 $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
654 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
655 $this->print_TTF($axisTag);
660 if ($this->calculated['y_axis_right']['has_data']) {
661 // RIGHT HAND SIDE
662 // left and right coords for ticks
663 if ($this->parameter['tick_length'] >= 0) {
664 $tickLeft = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
665 $tickRight = $this->calculated['boundary_box']['right'];
666 } else {
667 $tickLeft = $this->calculated['boundary_box']['right'];
668 $tickRight = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
670 $textLeft = $tickRight+ $this->calculated['left_inner_padding'];
672 if ($axis_angle == 0) $reference = 'left-center';
673 if ($axis_angle > 0) $reference = 'left-bottom';
674 if ($axis_angle < 0) $reference = 'left-top';
675 if ($axis_angle == 90) $reference = 'left-center';
677 foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
678 if (!$this->calculated['y_axis_left']['has_data'] && $yGrid != 'none') { // draw grid if not drawn already (above)
679 switch ($yGrid) {
680 case 'line':
681 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
682 break;
683 case 'dash':
684 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
685 break;
689 if ($this->parameter['y_axis_text_right'] && !($set % $this->parameter['y_axis_text_right'])) { // test if tick should be displayed
690 // draw tick
691 if ($tickColour != 'none')
692 ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
694 // draw axis text...
695 $coords = array('x' => $textLeft, 'y' => $tickY, 'reference' => $reference);
696 $axisTag['text'] = $this->calculated['y_axis_right']['text'][$set];
697 $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
698 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
699 $this->print_TTF($axisTag);
705 function init_data() {
706 $this->calculated['y_plot'] = array(); // array to hold pixel plotting coords for y axis
707 $height = $this->calculated['boundary_box']['bottom'] - $this->calculated['boundary_box']['top'];
708 $width = $this->calculated['boundary_box']['right'] - $this->calculated['boundary_box']['left'];
710 // calculate pixel steps between axis ticks.
711 $this->calculated['y_axis']['step'] = $height / ($this->parameter['y_axis_gridlines'] - 1);
713 // calculate x ticks spacing taking into account x offset for ticks.
714 $extraTick = 2 * $this->parameter['x_offset']; // extra tick to account for padding
715 $numTicks = $this->calculated['x_axis']['num_ticks'] - 1; // number of x ticks
717 // Hack by rodger to avoid division by zero, see bug 1231
718 if ($numTicks==0) $numTicks=1;
720 $this->calculated['x_axis']['step'] = $width / ($numTicks + $extraTick);
721 $widthPlot = $width - ($this->calculated['x_axis']['step'] * $extraTick);
722 $this->calculated['x_axis']['step'] = $widthPlot / $numTicks;
724 //calculate factor for transforming x,y physical coords to logical coords for right hand y_axis.
725 $y_range = $this->calculated['y_axis_right']['max'] - $this->calculated['y_axis_right']['min'];
726 $y_range = ($y_range ? $y_range : 1);
727 $this->calculated['y_axis_right']['factor'] = $height / $y_range;
729 //calculate factor for transforming x,y physical coords to logical coords for left hand axis.
730 $yRange = $this->calculated['y_axis_left']['max'] - $this->calculated['y_axis_left']['min'];
731 $yRange = ($yRange ? $yRange : 1);
732 $this->calculated['y_axis_left']['factor'] = $height / $yRange;
733 if ($this->parameter['x_axis_gridlines'] != 'auto') {
734 $xRange = $this->calculated['x_axis']['max'] - $this->calculated['x_axis']['min'];
735 $xRange = ($xRange ? $xRange : 1);
736 $this->calculated['x_axis']['factor'] = $widthPlot / $xRange;
739 //expand_pre($this->calculated['boundary_box']);
740 // cycle thru all data sets...
741 $this->calculated['num_bars'] = 0;
742 foreach ($this->y_order as $order => $set) {
743 // determine how many bars there are
744 if (isset($this->y_format[$set]['bar']) && ($this->y_format[$set]['bar'] != 'none')) {
745 $this->calculated['bar_offset_index'][$set] = $this->calculated['num_bars']; // index to relate bar with data set.
746 $this->calculated['num_bars']++;
749 // calculate y coords for plotting data
750 foreach ($this->x_data as $index => $x) {
751 $this->calculated['y_plot'][$set][$index] = $this->y_data[$set][$index];
753 if ((string)$this->y_data[$set][$index] != 'none') {
755 if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
756 $this->calculated['y_plot'][$set][$index] =
757 round(($this->y_data[$set][$index] - $this->calculated['y_axis_right']['min'])
758 * $this->calculated['y_axis_right']['factor']);
759 } else {
760 //print "$set $index<br />";
761 $this->calculated['y_plot'][$set][$index] =
762 round(($this->y_data[$set][$index] - $this->calculated['y_axis_left']['min'])
763 * $this->calculated['y_axis_left']['factor']);
769 //print "factor ".$this->calculated['x_axis']['factor']."<br />";
770 //expand_pre($this->calculated['x_plot']);
772 // calculate bar parameters if bars are to be drawn.
773 if ($this->calculated['num_bars']) {
774 $xStep = $this->calculated['x_axis']['step'];
775 $totalWidth = $this->calculated['x_axis']['step'] - $this->parameter['bar_spacing'];
776 $barWidth = $totalWidth / $this->calculated['num_bars'];
778 $barX = ($barWidth - $totalWidth) / 2; // starting x offset
779 for ($i=0; $i < $this->calculated['num_bars']; $i++) {
780 $this->calculated['bar_offset_x'][$i] = $barX;
781 $barX += $barWidth; // add width of bar to x offset.
783 $this->calculated['bar_width'] = $barWidth;
789 function init_x_ticks() {
790 // get coords for x axis ticks and data plots
791 //$xGrid = $this->parameter['x_grid'];
792 $xStep = $this->calculated['x_axis']['step'];
793 $ticksOffset = $this->parameter['x_offset']; // where to start drawing ticks relative to y axis.
794 $gridLeft = $this->calculated['boundary_box']['left'] + ($xStep * $ticksOffset); // grid x start
795 $tickX = $gridLeft; // tick x coord
797 foreach ($this->calculated['x_axis']['text'] as $set => $value) {
798 //print "index: $set<br />";
799 // x tick value
800 $this->calculated['x_axis']['tick_x'][$set] = $tickX;
801 // if num ticks is auto then x plot value is same as x tick
802 if ($this->parameter['x_axis_gridlines'] == 'auto') $this->calculated['x_plot'][$set] = round($tickX);
803 //print $this->calculated['x_plot'][$set].'<br />';
804 $tickX += $xStep;
807 //print "xStep: $xStep <br />";
808 // if numeric x axis then calculate x coords for each data point. this is seperate from x ticks.
809 $gridX = $gridLeft;
810 if (empty($this->calculated['x_axis']['factor'])) {
811 $this->calculated['x_axis']['factor'] = 0;
813 if (empty($this->calculated['x_axis']['min'])) {
814 $this->calculated['x_axis']['min'] = 0;
816 $factor = $this->calculated['x_axis']['factor'];
817 $min = $this->calculated['x_axis']['min'];
819 if ($this->parameter['x_axis_gridlines'] != 'auto') {
820 foreach ($this->x_data as $index => $x) {
821 //print "index: $index, x: $x<br />";
822 $offset = $x - $this->calculated['x_axis']['min'];
824 //$gridX = ($offset * $this->calculated['x_axis']['factor']);
825 //print "offset: $offset <br />";
826 //$this->calculated['x_plot'][$set] = $gridLeft + ($offset * $this->calculated['x_axis']['factor']);
828 $this->calculated['x_plot'][$index] = $gridLeft + ($x - $min) * $factor;
830 //print $this->calculated['x_plot'][$set].'<br />';
833 //expand_pre($this->calculated['boundary_box']);
834 //print "factor ".$this->calculated['x_axis']['factor']."<br />";
835 //expand_pre($this->calculated['x_plot']);
838 function init_y_ticks() {
839 // get coords for y axis ticks
841 $yStep = $this->calculated['y_axis']['step'];
842 $gridBottom = $this->calculated['boundary_box']['bottom'];
843 $tickY = $gridBottom; // tick y coord
845 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) {
846 $this->calculated['y_axis']['tick_y'][$i] = $tickY;
847 $tickY -= $yStep;
852 function init_labels() {
853 if ($this->parameter['title']) {
854 $size = $this->get_boundaryBox(
855 array('points' => $this->parameter['title_size'],
856 'angle' => 0,
857 'font' => $this->parameter['title_font'],
858 'text' => $this->parameter['title']));
859 $this->calculated['title']['boundary_box'] = $size;
860 $this->calculated['title']['text'] = $this->parameter['title'];
861 $this->calculated['title']['font'] = $this->parameter['title_font'];
862 $this->calculated['title']['points'] = $this->parameter['title_size'];
863 $this->calculated['title']['colour'] = $this->parameter['title_colour'];
864 $this->calculated['title']['angle'] = 0;
866 $this->calculated['boundary_box']['top'] += $size['height'] + $this->parameter['outer_padding'];
867 //$this->calculated['boundary_box']['top'] += $size['height'];
869 } else $this->calculated['title']['boundary_box'] = $this->get_null_size();
871 if ($this->parameter['y_label_left']) {
872 $this->calculated['y_label_left']['text'] = $this->parameter['y_label_left'];
873 $this->calculated['y_label_left']['angle'] = $this->parameter['y_label_angle'];
874 $this->calculated['y_label_left']['font'] = $this->parameter['label_font'];
875 $this->calculated['y_label_left']['points'] = $this->parameter['label_size'];
876 $this->calculated['y_label_left']['colour'] = $this->parameter['label_colour'];
878 $size = $this->get_boundaryBox($this->calculated['y_label_left']);
879 $this->calculated['y_label_left']['boundary_box'] = $size;
880 //$this->calculated['boundary_box']['left'] += $size['width'] + $this->parameter['inner_padding'];
881 $this->calculated['boundary_box']['left'] += $size['width'];
883 } else $this->calculated['y_label_left']['boundary_box'] = $this->get_null_size();
885 if ($this->parameter['y_label_right']) {
886 $this->calculated['y_label_right']['text'] = $this->parameter['y_label_right'];
887 $this->calculated['y_label_right']['angle'] = $this->parameter['y_label_angle'];
888 $this->calculated['y_label_right']['font'] = $this->parameter['label_font'];
889 $this->calculated['y_label_right']['points'] = $this->parameter['label_size'];
890 $this->calculated['y_label_right']['colour'] = $this->parameter['label_colour'];
892 $size = $this->get_boundaryBox($this->calculated['y_label_right']);
893 $this->calculated['y_label_right']['boundary_box'] = $size;
894 //$this->calculated['boundary_box']['right'] -= $size['width'] + $this->parameter['inner_padding'];
895 $this->calculated['boundary_box']['right'] -= $size['width'];
897 } else $this->calculated['y_label_right']['boundary_box'] = $this->get_null_size();
899 if ($this->parameter['x_label']) {
900 $this->calculated['x_label']['text'] = $this->parameter['x_label'];
901 $this->calculated['x_label']['angle'] = $this->parameter['x_label_angle'];
902 $this->calculated['x_label']['font'] = $this->parameter['label_font'];
903 $this->calculated['x_label']['points'] = $this->parameter['label_size'];
904 $this->calculated['x_label']['colour'] = $this->parameter['label_colour'];
906 $size = $this->get_boundaryBox($this->calculated['x_label']);
907 $this->calculated['x_label']['boundary_box'] = $size;
908 //$this->calculated['boundary_box']['bottom'] -= $size['height'] + $this->parameter['inner_padding'];
909 $this->calculated['boundary_box']['bottom'] -= $size['height'];
911 } else $this->calculated['x_label']['boundary_box'] = $this->get_null_size();
916 function init_legend() {
917 $this->calculated['legend'] = array(); // array to hold calculated values for legend.
918 //$this->calculated['legend']['boundary_box_max'] = array('height' => 0, 'width' => 0);
919 $this->calculated['legend']['boundary_box_max'] = $this->get_null_size();
920 if ($this->parameter['legend'] == 'none') return;
922 $position = $this->parameter['legend'];
923 $numSets = 0; // number of data sets with legends.
924 $sumTextHeight = 0; // total of height of all legend text items.
925 $width = 0;
926 $height = 0;
928 foreach ($this->y_order as $set) {
929 $text = isset($this->y_format[$set]['legend']) ? $this->y_format[$set]['legend'] : 'none';
930 $size = $this->get_boundaryBox(
931 array('points' => $this->parameter['legend_size'],
932 'angle' => 0,
933 'font' => $this->parameter['legend_font'],
934 'text' => $text));
936 $this->calculated['legend']['boundary_box'][$set] = $size;
937 $this->calculated['legend']['text'][$set] = $text;
938 //$this->calculated['legend']['font'][$set] = $this->parameter['legend_font'];
939 //$this->calculated['legend']['points'][$set] = $this->parameter['legend_size'];
940 //$this->calculated['legend']['angle'][$set] = 0;
942 if ($text && $text!='none') {
943 $numSets++;
944 $sumTextHeight += $size['height'];
947 if ($size['width'] > $this->calculated['legend']['boundary_box_max']['width'])
948 $this->calculated['legend']['boundary_box_max'] = $size;
951 $offset = $this->parameter['legend_offset']; // offset in pixels of legend box from graph border.
952 $padding = $this->parameter['legend_padding']; // padding in pixels around legend text.
953 $textWidth = $this->calculated['legend']['boundary_box_max']['width']; // width of largest legend item.
954 $textHeight = $this->calculated['legend']['boundary_box_max']['height']; // use height as size to use for colour square in legend.
955 $width = $padding * 2 + $textWidth + $textHeight * 2; // left and right padding + maximum text width + space for square
956 $height = $padding * ($numSets + 1) + $sumTextHeight; // top and bottom padding + padding between text + text.
959 $this->calculated['legend']['boundary_box_all'] = array('width' => $width,
960 'height' => $height,
961 'offset' => $offset,
962 'reference' => $position);
964 switch ($position) { // move in right or bottom if legend is outside data plotting area.
965 case 'outside-top' :
966 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
967 break;
969 case 'outside-bottom' :
970 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
971 break;
973 case 'outside-left' :
974 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
975 break;
977 case 'outside-right' :
978 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
979 break;
983 function init_y_axis() {
984 $this->calculated['y_axis_left'] = array(); // array to hold calculated values for y_axis on left.
985 $this->calculated['y_axis_left']['boundary_box_max'] = $this->get_null_size();
986 $this->calculated['y_axis_right'] = array(); // array to hold calculated values for y_axis on right.
987 $this->calculated['y_axis_right']['boundary_box_max'] = $this->get_null_size();
989 $axis_font = $this->parameter['axis_font'];
990 $axis_size = $this->parameter['axis_size'];
991 $axis_colour = $this->parameter['axis_colour'];
992 $axis_angle = $this->parameter['y_axis_angle'];
993 $y_tick_labels = $this->y_tick_labels;
995 $this->calculated['y_axis_left']['has_data'] = FALSE;
996 $this->calculated['y_axis_right']['has_data'] = FALSE;
998 // find min and max y values.
999 $minLeft = $this->parameter['y_min_left'];
1000 $maxLeft = $this->parameter['y_max_left'];
1001 $minRight = $this->parameter['y_min_right'];
1002 $maxRight = $this->parameter['y_max_right'];
1003 $dataLeft = array();
1004 $dataRight = array();
1005 foreach ($this->y_order as $order => $set) {
1006 if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
1007 $this->calculated['y_axis_right']['has_data'] = TRUE;
1008 $dataRight = array_merge($dataRight, $this->y_data[$set]);
1009 } else {
1010 $this->calculated['y_axis_left']['has_data'] = TRUE;
1011 $dataLeft = array_merge($dataLeft, $this->y_data[$set]);
1014 $dataLeftRange = $this->find_range($dataLeft, $minLeft, $maxLeft, $this->parameter['y_resolution_left']);
1015 $dataRightRange = $this->find_range($dataRight, $minRight, $maxRight, $this->parameter['y_resolution_right']);
1016 $minLeft = $dataLeftRange['min'];
1017 $maxLeft = $dataLeftRange['max'];
1018 $minRight = $dataRightRange['min'];
1019 $maxRight = $dataRightRange['max'];
1021 $this->calculated['y_axis_left']['min'] = $minLeft;
1022 $this->calculated['y_axis_left']['max'] = $maxLeft;
1023 $this->calculated['y_axis_right']['min'] = $minRight;
1024 $this->calculated['y_axis_right']['max'] = $maxRight;
1026 $stepLeft = ($maxLeft - $minLeft) / ($this->parameter['y_axis_gridlines'] - 1);
1027 $startLeft = $minLeft;
1028 $step_right = ($maxRight - $minRight) / ($this->parameter['y_axis_gridlines'] - 1);
1029 $start_right = $minRight;
1031 if ($this->parameter['y_axis_text_left']) {
1032 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1033 // left y axis
1034 if ($y_tick_labels) {
1035 $value = $y_tick_labels[$i];
1036 } else {
1037 $value = number_format($startLeft, $this->parameter['y_decimal_left'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1039 $this->calculated['y_axis_left']['data'][$i] = $startLeft;
1040 $this->calculated['y_axis_left']['text'][$i] = $value; // text is formatted raw data
1042 $size = $this->get_boundaryBox(
1043 array('points' => $axis_size,
1044 'font' => $axis_font,
1045 'angle' => $axis_angle,
1046 'colour' => $axis_colour,
1047 'text' => $value));
1048 $this->calculated['y_axis_left']['boundary_box'][$i] = $size;
1050 if ($size['height'] > $this->calculated['y_axis_left']['boundary_box_max']['height'])
1051 $this->calculated['y_axis_left']['boundary_box_max']['height'] = $size['height'];
1052 if ($size['width'] > $this->calculated['y_axis_left']['boundary_box_max']['width'])
1053 $this->calculated['y_axis_left']['boundary_box_max']['width'] = $size['width'];
1055 $startLeft += $stepLeft;
1057 $this->calculated['boundary_box']['left'] += $this->calculated['y_axis_left']['boundary_box_max']['width']
1058 + $this->parameter['y_inner_padding'];
1061 if ($this->parameter['y_axis_text_right']) {
1062 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1063 // right y axis
1064 $value = number_format($start_right, $this->parameter['y_decimal_right'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1065 $this->calculated['y_axis_right']['data'][$i] = $start_right;
1066 $this->calculated['y_axis_right']['text'][$i] = $value; // text is formatted raw data
1067 $size = $this->get_boundaryBox(
1068 array('points' => $axis_size,
1069 'font' => $axis_font,
1070 'angle' => $axis_angle,
1071 'colour' => $axis_colour,
1072 'text' => $value));
1073 $this->calculated['y_axis_right']['boundary_box'][$i] = $size;
1075 if ($size['height'] > $this->calculated['y_axis_right']['boundary_box_max']['height'])
1076 $this->calculated['y_axis_right']['boundary_box_max'] = $size;
1077 if ($size['width'] > $this->calculated['y_axis_right']['boundary_box_max']['width'])
1078 $this->calculated['y_axis_right']['boundary_box_max']['width'] = $size['width'];
1080 $start_right += $step_right;
1082 $this->calculated['boundary_box']['right'] -= $this->calculated['y_axis_right']['boundary_box_max']['width']
1083 + $this->parameter['y_inner_padding'];
1087 function init_x_axis() {
1088 $this->calculated['x_axis'] = array(); // array to hold calculated values for x_axis.
1089 $this->calculated['x_axis']['boundary_box_max'] = array('height' => 0, 'width' => 0);
1091 $axis_font = $this->parameter['axis_font'];
1092 $axis_size = $this->parameter['axis_size'];
1093 $axis_colour = $this->parameter['axis_colour'];
1094 $axis_angle = $this->parameter['x_axis_angle'];
1096 // check whether to treat x axis as numeric
1097 if ($this->parameter['x_axis_gridlines'] == 'auto') { // auto means text based x_axis, not numeric...
1098 $this->calculated['x_axis']['num_ticks'] = sizeof($this->x_data);
1099 $data = $this->x_data;
1100 for ($i=0; $i < $this->calculated['x_axis']['num_ticks']; $i++) {
1101 $value = array_shift($data); // grab value from begin of array
1102 $this->calculated['x_axis']['data'][$i] = $value;
1103 $this->calculated['x_axis']['text'][$i] = $value; // raw data and text are both the same in this case
1104 $size = $this->get_boundaryBox(
1105 array('points' => $axis_size,
1106 'font' => $axis_font,
1107 'angle' => $axis_angle,
1108 'colour' => $axis_colour,
1109 'text' => $value));
1110 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1111 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1112 $this->calculated['x_axis']['boundary_box_max'] = $size;
1115 } else { // x axis is numeric so find max min values...
1116 $this->calculated['x_axis']['num_ticks'] = $this->parameter['x_axis_gridlines'];
1118 $min = $this->parameter['x_min'];
1119 $max = $this->parameter['x_max'];
1120 $data = array();
1121 $data = $this->find_range($this->x_data, $min, $max, $this->parameter['x_resolution']);
1122 $min = $data['min'];
1123 $max = $data['max'];
1124 $this->calculated['x_axis']['min'] = $min;
1125 $this->calculated['x_axis']['max'] = $max;
1127 $step = ($max - $min) / ($this->calculated['x_axis']['num_ticks'] - 1);
1128 $start = $min;
1130 for ($i = 0; $i < $this->calculated['x_axis']['num_ticks']; $i++) { // calculate x axis text sizes
1131 $value = number_format($start, $this->parameter['xDecimal'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1132 $this->calculated['x_axis']['data'][$i] = $start;
1133 $this->calculated['x_axis']['text'][$i] = $value; // text is formatted raw data
1135 $size = $this->get_boundaryBox(
1136 array('points' => $axis_size,
1137 'font' => $axis_font,
1138 'angle' => $axis_angle,
1139 'colour' => $axis_colour,
1140 'text' => $value));
1141 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1143 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1144 $this->calculated['x_axis']['boundary_box_max'] = $size;
1146 $start += $step;
1149 if ($this->parameter['x_axis_text'])
1150 $this->calculated['boundary_box']['bottom'] -= $this->calculated['x_axis']['boundary_box_max']['height']
1151 + $this->parameter['x_inner_padding'];
1154 // find max and min values for a data array given the resolution.
1155 function find_range($data, $min, $max, $resolution) {
1156 if (sizeof($data) == 0 ) return array('min' => 0, 'max' => 0);
1157 foreach ($data as $key => $value) {
1158 if ($value=='none') continue;
1159 if ($value > $max) $max = $value;
1160 if ($value < $min) $min = $value;
1163 if ($max == 0) {
1164 $factor = 1;
1165 } else {
1166 if ($max < 0) $factor = - pow(10, (floor(log10(abs($max))) + $resolution) );
1167 else $factor = pow(10, (floor(log10(abs($max))) - $resolution) );
1169 $factor = round($factor * 1000.0) / 1000.0; // To avoid some wierd rounding errors (Moodle)
1171 $max = $factor * @ceil($max / $factor);
1172 $min = $factor * @floor($min / $factor);
1174 //print "max=$max, min=$min<br />";
1176 return array('min' => $min, 'max' => $max);
1179 function graph() {
1180 if (func_num_args() == 2) {
1181 $this->parameter['width'] = func_get_arg(0);
1182 $this->parameter['height'] = func_get_arg(1);
1184 //$this->boundaryBox = array(
1185 $this->calculated['boundary_box'] = array(
1186 'left' => 0,
1187 'top' => 0,
1188 'right' => $this->parameter['width'] - 1,
1189 'bottom' => $this->parameter['height'] - 1);
1191 $this->init_colours();
1193 //ImageColorTransparent($this->image, $this->colour['white']); // colour for transparency
1196 function print_TTF($message) {
1197 $points = $message['points'];
1198 $angle = $message['angle'];
1199 $text = $message['text'];
1200 $colour = $this->colour[$message['colour']];
1201 $font = $this->parameter['path_to_fonts'].$message['font'];
1203 $x = $message['boundary_box']['x'];
1204 $y = $message['boundary_box']['y'];
1205 $offsetX = $message['boundary_box']['offsetX'];
1206 $offsetY = $message['boundary_box']['offsetY'];
1207 $height = $message['boundary_box']['height'];
1208 $width = $message['boundary_box']['width'];
1209 $reference = $message['boundary_box']['reference'];
1211 switch ($reference) {
1212 case 'top-left':
1213 case 'left-top':
1214 $y += $height - $offsetY;
1215 //$y += $offsetY;
1216 $x += $offsetX;
1217 break;
1218 case 'left-center':
1219 $y += ($height / 2) - $offsetY;
1220 $x += $offsetX;
1221 break;
1222 case 'left-bottom':
1223 $y -= $offsetY;
1224 $x += $offsetX;
1225 break;
1226 case 'top-center':
1227 $y += $height - $offsetY;
1228 $x -= ($width / 2) - $offsetX;
1229 break;
1230 case 'top-right':
1231 case 'right-top':
1232 $y += $height - $offsetY;
1233 $x -= $width - $offsetX;
1234 break;
1235 case 'right-center':
1236 $y += ($height / 2) - $offsetY;
1237 $x -= $width - $offsetX;
1238 break;
1239 case 'right-bottom':
1240 $y -= $offsetY;
1241 $x -= $width - $offsetX;
1242 break;
1243 case 'bottom-center':
1244 $y -= $offsetY;
1245 $x -= ($width / 2) - $offsetX;
1246 break;
1247 default:
1248 $y = 0;
1249 $x = 0;
1250 break;
1252 if ($this->parameter['lang_decode']) { // Moodle addition
1253 include_once($this->parameter['lang_decode']);
1254 $text = lang_decode($text);
1256 } else if ($this->parameter['lang_transcode']) {
1257 $text = iconv($this->parameter['lang_transcode'], 'UTF-8', $text);
1259 ImageTTFText($this->image, $points, $angle, $x, $y, $colour, $font, $text);
1262 // move boundaryBox to coordinates specified
1263 function update_boundaryBox(&$boundaryBox, $coords) {
1264 $width = $boundaryBox['width'];
1265 $height = $boundaryBox['height'];
1266 $x = $coords['x'];
1267 $y = $coords['y'];
1268 $reference = $coords['reference'];
1269 switch ($reference) {
1270 case 'top-left':
1271 case 'left-top':
1272 $top = $y;
1273 $bottom = $y + $height;
1274 $left = $x;
1275 $right = $x + $width;
1276 break;
1277 case 'left-center':
1278 $top = $y - ($height / 2);
1279 $bottom = $y + ($height / 2);
1280 $left = $x;
1281 $right = $x + $width;
1282 break;
1283 case 'left-bottom':
1284 $top = $y - $height;
1285 $bottom = $y;
1286 $left = $x;
1287 $right = $x + $width;
1288 break;
1289 case 'top-center':
1290 $top = $y;
1291 $bottom = $y + $height;
1292 $left = $x - ($width / 2);
1293 $right = $x + ($width / 2);
1294 break;
1295 case 'right-top':
1296 case 'top-right':
1297 $top = $y;
1298 $bottom = $y + $height;
1299 $left = $x - $width;
1300 $right = $x;
1301 break;
1302 case 'right-center':
1303 $top = $y - ($height / 2);
1304 $bottom = $y + ($height / 2);
1305 $left = $x - $width;
1306 $right = $x;
1307 break;
1308 case 'bottom=right':
1309 case 'right-bottom':
1310 $top = $y - $height;
1311 $bottom = $y;
1312 $left = $x - $width;
1313 $right = $x;
1314 break;
1315 default:
1316 $top = 0;
1317 $bottom = $height;
1318 $left = 0;
1319 $right = $width;
1320 break;
1323 $boundaryBox = array_merge($boundaryBox, array('top' => $top,
1324 'bottom' => $bottom,
1325 'left' => $left,
1326 'right' => $right,
1327 'x' => $x,
1328 'y' => $y,
1329 'reference' => $reference));
1332 function get_null_size() {
1333 return array('width' => 0,
1334 'height' => 0,
1335 'offsetX' => 0,
1336 'offsetY' => 0,
1337 //'fontHeight' => 0
1341 function get_boundaryBox($message) {
1342 $points = $message['points'];
1343 $angle = $message['angle'];
1344 $font = $this->parameter['path_to_fonts'].$message['font'];
1345 $text = $message['text'];
1347 //print ('get_boundaryBox');
1348 //expandPre($message);
1350 // get font size
1351 $bounds = ImageTTFBBox($points, $angle, $font, "W");
1352 if ($angle < 0) {
1353 $fontHeight = abs($bounds[7]-$bounds[1]);
1354 } else if ($angle > 0) {
1355 $fontHeight = abs($bounds[1]-$bounds[7]);
1356 } else {
1357 $fontHeight = abs($bounds[7]-$bounds[1]);
1360 // get boundary box and offsets for printing at an angle
1361 if ($this->parameter['lang_decode']) { // Moodle addition
1362 include_once($this->parameter['lang_decode']);
1363 $text = lang_decode($text);
1365 } else if ($this->parameter['lang_transcode']) {
1366 $text = iconv($this->parameter['lang_transcode'], 'UTF-8', $text);
1368 $bounds = ImageTTFBBox($points, $angle, $font, $text);
1370 if ($angle < 0) {
1371 $width = abs($bounds[4]-$bounds[0]);
1372 $height = abs($bounds[3]-$bounds[7]);
1373 $offsetY = abs($bounds[3]-$bounds[1]);
1374 $offsetX = 0;
1376 } else if ($angle > 0) {
1377 $width = abs($bounds[2]-$bounds[6]);
1378 $height = abs($bounds[1]-$bounds[5]);
1379 $offsetY = 0;
1380 $offsetX = abs($bounds[0]-$bounds[6]);
1382 } else {
1383 $width = abs($bounds[4]-$bounds[6]);
1384 $height = abs($bounds[7]-$bounds[1]);
1385 $offsetY = 0;
1386 $offsetX = 0;
1389 //return values
1390 return array('width' => $width,
1391 'height' => $height,
1392 'offsetX' => $offsetX,
1393 'offsetY' => $offsetY,
1394 //'fontHeight' => $fontHeight
1398 function draw_rectangle($border, $colour, $type) {
1399 $colour = $this->colour[$colour];
1400 switch ($type) {
1401 case 'fill': // fill the rectangle
1402 ImageFilledRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1403 break;
1404 case 'box': // all sides
1405 ImageRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1406 break;
1407 case 'axis': // bottom x axis and left y axis
1408 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1409 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1410 break;
1411 case 'y': // left y axis only
1412 case 'y-left':
1413 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1414 break;
1415 case 'y-right': // right y axis only
1416 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1417 break;
1418 case 'x': // bottom x axis only
1419 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1420 break;
1421 case 'u': // u shaped. bottom x axis and both left and right y axis.
1422 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1423 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1424 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1425 break;
1430 function init_colours() {
1431 $this->image = ImageCreate($this->parameter['width'], $this->parameter['height']);
1432 // standard colours
1433 $this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF); // first colour is background colour.
1434 $this->colour['black'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x00);
1435 $this->colour['maroon'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x00);
1436 $this->colour['green'] = ImageColorAllocate ($this->image, 0x00, 0x80, 0x00);
1437 $this->colour['ltgreen'] = ImageColorAllocate ($this->image, 0x52, 0xF1, 0x7F);
1438 $this->colour['ltltgreen']= ImageColorAllocate ($this->image, 0x99, 0xFF, 0x99);
1439 $this->colour['olive'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x00);
1440 $this->colour['navy'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x80);
1441 $this->colour['purple'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x80);
1442 $this->colour['gray'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x80);
1443 $this->colour['red'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0x00);
1444 $this->colour['ltred'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x99);
1445 $this->colour['ltltred'] = ImageColorAllocate ($this->image, 0xFF, 0xCC, 0xCC);
1446 $this->colour['orange'] = ImageColorAllocate ($this->image, 0xFF, 0x66, 0x00);
1447 $this->colour['ltorange'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x66);
1448 $this->colour['ltltorange'] = ImageColorAllocate ($this->image, 0xFF, 0xcc, 0x99);
1449 $this->colour['lime'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0x00);
1450 $this->colour['yellow'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0x00);
1451 $this->colour['blue'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0xFF);
1452 $this->colour['ltblue'] = ImageColorAllocate ($this->image, 0x00, 0xCC, 0xFF);
1453 $this->colour['ltltblue'] = ImageColorAllocate ($this->image, 0x99, 0xFF, 0xFF);
1454 $this->colour['fuchsia'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0xFF);
1455 $this->colour['aqua'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0xFF);
1456 //$this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF);
1457 // shades of gray
1458 $this->colour['grayF0'] = ImageColorAllocate ($this->image, 0xF0, 0xF0, 0xF0);
1459 $this->colour['grayEE'] = ImageColorAllocate ($this->image, 0xEE, 0xEE, 0xEE);
1460 $this->colour['grayDD'] = ImageColorAllocate ($this->image, 0xDD, 0xDD, 0xDD);
1461 $this->colour['grayCC'] = ImageColorAllocate ($this->image, 0xCC, 0xCC, 0xCC);
1462 $this->colour['gray33'] = ImageColorAllocate ($this->image, 0x33, 0x33, 0x33);
1463 $this->colour['gray66'] = ImageColorAllocate ($this->image, 0x66, 0x66, 0x66);
1464 $this->colour['gray99'] = ImageColorAllocate ($this->image, 0x99, 0x99, 0x99);
1466 $this->colour['none'] = 'none';
1467 return true;
1470 function output() {
1471 if ($this->debug) { // for debugging purposes.
1472 //expandPre($this->graph);
1473 //expandPre($this->y_data);
1474 //expandPre($this->x_data);
1475 //expandPre($this->parameter);
1476 } else {
1478 $expiresSeconds = $this->parameter['seconds_to_live'];
1479 $expiresHours = $this->parameter['hours_to_live'];
1481 if ($expiresHours || $expiresSeconds) {
1482 $now = mktime (date("H"),date("i"),date("s"),date("m"),date("d"),date("Y"));
1483 $expires = mktime (date("H")+$expiresHours,date("i"),date("s")+$expiresSeconds,date("m"),date("d"),date("Y"));
1484 $expiresGMT = gmdate('D, d M Y H:i:s', $expires).' GMT';
1485 $lastModifiedGMT = gmdate('D, d M Y H:i:s', $now).' GMT';
1487 Header('Last-modified: '.$lastModifiedGMT);
1488 Header('Expires: '.$expiresGMT);
1491 if ($this->parameter['file_name'] == 'none') {
1492 switch ($this->parameter['output_format']) {
1493 case 'GIF':
1494 Header("Content-type: image/gif"); // GIF??. switch to PNG guys!!
1495 ImageGIF($this->image);
1496 break;
1497 case 'JPEG':
1498 Header("Content-type: image/jpeg"); // JPEG for line art??. included for completeness.
1499 ImageJPEG($this->image);
1500 break;
1501 default:
1502 Header("Content-type: image/png"); // preferred output format
1503 ImagePNG($this->image);
1504 break;
1506 } else {
1507 switch ($this->parameter['output_format']) {
1508 case 'GIF':
1509 ImageGIF($this->image, $this->parameter['file_name'].'.gif');
1510 break;
1511 case 'JPEG':
1512 ImageJPEG($this->image, $this->parameter['file_name'].'.jpg');
1513 break;
1514 default:
1515 ImagePNG($this->image, $this->parameter['file_name'].'.png');
1516 break;
1520 ImageDestroy($this->image);
1522 } // function output
1524 function init_variable(&$variable, $value, $default) {
1525 if (!empty($value)) $variable = $value;
1526 else if (isset($default)) $variable = $default;
1527 else unset($variable);
1530 // plot a point. options include square, circle, diamond, triangle, and dot. offset is used for drawing shadows.
1531 // for diamonds and triangles the size should be an even number to get nice look. if odd the points are crooked.
1532 function plot($x, $y, $type, $size, $colour, $offset) {
1533 //print("drawing point of type: $type, at offset: $offset");
1534 $u = $x + $offset;
1535 $v = $this->calculated['inner_border']['bottom'] - $y + $offset;
1536 $half = $size / 2;
1538 switch ($type) {
1539 case 'square':
1540 ImageFilledRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1541 break;
1542 case 'square-open':
1543 ImageRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1544 break;
1545 case 'circle':
1546 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1547 ImageFillToBorder($this->image, $u, $v, $this->colour[$colour], $this->colour[$colour]);
1548 break;
1549 case 'circle-open':
1550 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1551 break;
1552 case 'diamond':
1553 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1554 break;
1555 case 'diamond-open':
1556 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1557 break;
1558 case 'triangle':
1559 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1560 break;
1561 case 'triangle-open':
1562 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1563 break;
1564 case 'dot':
1565 ImageSetPixel($this->image, $u, $v, $this->colour[$colour]);
1566 break;
1570 function bar($x, $y, $type, $size, $colour, $offset, $index, $yoffset) {
1571 $index_offset = $this->calculated['bar_offset_index'][$index];
1572 if ( $yoffset ) {
1573 $bar_offsetx = 0;
1574 } else {
1575 $bar_offsetx = $this->calculated['bar_offset_x'][$index_offset];
1577 //$this->dbug("drawing bar at offset = $offset : index = $index: bar_offsetx = $bar_offsetx");
1579 $span = ($this->calculated['bar_width'] * $size) / 2;
1580 $x_left = $x + $bar_offsetx - $span;
1581 $x_right = $x + $bar_offsetx + $span;
1583 if ($this->parameter['zero_axis'] != 'none') {
1584 $zero = $this->calculated['zero_axis'];
1585 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1586 $u_left = $x_left + $offset;
1587 $u_right = $x_right + $offset - 1;
1588 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1590 if ($v > $zero) {
1591 $top = $zero +1;
1592 $bottom = $v;
1593 } else {
1594 $top = $v;
1595 $bottom = $zero - 1;
1598 switch ($type) {
1599 case 'open':
1600 //ImageRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1601 if ($v > $zero)
1602 ImageRectangle($this->image, round($u_left), $bottom, round($u_right), $bottom, $this->colour[$colour]);
1603 else
1604 ImageRectangle($this->image, round($u_left), $top, round($u_right), $top, $this->colour[$colour]);
1605 ImageRectangle($this->image, round($u_left), $top, round($u_left), $bottom, $this->colour[$colour]);
1606 ImageRectangle($this->image, round($u_right), $top, round($u_right), $bottom, $this->colour[$colour]);
1607 break;
1608 case 'fill':
1609 ImageFilledRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1610 break;
1613 } else {
1615 $bottom = $this->calculated['boundary_box']['bottom'];
1616 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1617 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1618 $u_left = $x_left + $offset;
1619 $u_right = $x_right + $offset - 1;
1620 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1622 // Moodle addition, plus the function parameter yoffset
1623 if ($yoffset) { // Moodle
1624 $yoffset = $yoffset - round(($bottom - $v) / 2.0); // Moodle
1625 $bottom -= $yoffset; // Moodle
1626 $v -= $yoffset; // Moodle
1627 } // Moodle
1629 switch ($type) {
1630 case 'open':
1631 ImageRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1632 break;
1633 case 'fill':
1634 ImageFilledRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1635 break;
1640 function area($x_start, $y_start, $x_end, $y_end, $type, $colour, $offset) {
1641 //dbug("drawing area type: $type, at offset: $offset");
1642 if ($this->parameter['zero_axis'] != 'none') {
1643 $bottom = $this->calculated['boundary_box']['bottom'];
1644 $zero = $this->calculated['zero_axis'];
1645 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1646 $u_start = $x_start + $offset;
1647 $u_end = $x_end + $offset;
1648 $v_start = $bottom - $y_start + $offset;
1649 $v_end = $bottom - $y_end + $offset;
1650 switch ($type) {
1651 case 'fill':
1652 // draw it this way 'cos the FilledPolygon routine seems a bit buggy.
1653 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1654 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1655 break;
1656 case 'open':
1657 //ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1658 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1659 ImageLine($this->image, $u_start, $v_start, $u_start, $zero, $this->colour[$colour]);
1660 ImageLine($this->image, $u_end, $v_end, $u_end, $zero, $this->colour[$colour]);
1661 break;
1663 } else {
1664 $bottom = $this->calculated['boundary_box']['bottom'];
1665 $u_start = $x_start + $offset;
1666 $u_end = $x_end + $offset;
1667 $v_start = $bottom - $y_start + $offset;
1668 $v_end = $bottom - $y_end + $offset;
1670 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1671 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1672 switch ($type) {
1673 case 'fill':
1674 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1675 break;
1676 case 'open':
1677 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1678 break;
1683 function line($x_start, $y_start, $x_end, $y_end, $type, $brush_type, $brush_size, $colour, $offset) {
1684 //dbug("drawing line of type: $type, at offset: $offset");
1685 $u_start = $x_start + $offset;
1686 $v_start = $this->calculated['boundary_box']['bottom'] - $y_start + $offset;
1687 $u_end = $x_end + $offset;
1688 $v_end = $this->calculated['boundary_box']['bottom'] - $y_end + $offset;
1690 switch ($type) {
1691 case 'brush':
1692 $this->draw_brush_line($u_start, $v_start, $u_end, $v_end, $brush_size, $brush_type, $colour);
1693 break;
1694 case 'line' :
1695 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1696 break;
1697 case 'dash':
1698 ImageDashedLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1699 break;
1703 // function to draw line. would prefer to use gdBrush but this is not supported yet.
1704 function draw_brush_line($x0, $y0, $x1, $y1, $size, $type, $colour) {
1705 //$this->dbug("line: $x0, $y0, $x1, $y1");
1706 $dy = $y1 - $y0;
1707 $dx = $x1 - $x0;
1708 $t = 0;
1709 $watchdog = 1024; // precaution to prevent infinite loops.
1711 $this->draw_brush($x0, $y0, $size, $type, $colour);
1712 if (abs($dx) > abs($dy)) { // slope < 1
1713 //$this->dbug("slope < 1");
1714 $m = $dy / $dx; // compute slope
1715 $t += $y0;
1716 $dx = ($dx < 0) ? -1 : 1;
1717 $m *= $dx;
1718 while (round($x0) != round($x1)) {
1719 if (!$watchdog--) break;
1720 $x0 += $dx; // step to next x value
1721 $t += $m; // add slope to y value
1722 $y = round($t);
1723 //$this->dbug("x0=$x0, x1=$x1, y=$y watchdog=$watchdog");
1724 $this->draw_brush($x0, $y, $size, $type, $colour);
1727 } else { // slope >= 1
1728 //$this->dbug("slope >= 1");
1729 $m = $dx / $dy; // compute slope
1730 $t += $x0;
1731 $dy = ($dy < 0) ? -1 : 1;
1732 $m *= $dy;
1733 while (round($y0) != round($y1)) {
1734 if (!$watchdog--) break;
1735 $y0 += $dy; // step to next y value
1736 $t += $m; // add slope to x value
1737 $x = round($t);
1738 //$this->dbug("x=$x, y0=$y0, y1=$y1 watchdog=$watchdog");
1739 $this->draw_brush($x, $y0, $size, $type, $colour);
1745 function draw_brush($x, $y, $size, $type, $colour) {
1746 $x = round($x);
1747 $y = round($y);
1748 $half = round($size / 2);
1749 switch ($type) {
1750 case 'circle':
1751 ImageArc($this->image, $x, $y, $size, $size, 0, 360, $this->colour[$colour]);
1752 ImageFillToBorder($this->image, $x, $y, $this->colour[$colour], $this->colour[$colour]);
1753 break;
1754 case 'square':
1755 ImageFilledRectangle($this->image, $x-$half, $y-$half, $x+$half, $y+$half, $this->colour[$colour]);
1756 break;
1757 case 'vertical':
1758 ImageFilledRectangle($this->image, $x, $y-$half, $x+1, $y+$half, $this->colour[$colour]);
1759 break;
1760 case 'horizontal':
1761 ImageFilledRectangle($this->image, $x-$half, $y, $x+$half, $y+1, $this->colour[$colour]);
1762 break;
1763 case 'slash':
1764 ImageFilledPolygon($this->image, array($x+$half, $y-$half,
1765 $x+$half+1, $y-$half,
1766 $x-$half+1, $y+$half,
1767 $x-$half, $y+$half
1768 ), 4, $this->colour[$colour]);
1769 break;
1770 case 'backslash':
1771 ImageFilledPolygon($this->image, array($x-$half, $y-$half,
1772 $x-$half+1, $y-$half,
1773 $x+$half+1, $y+$half,
1774 $x+$half, $y+$half
1775 ), 4, $this->colour[$colour]);
1776 break;
1777 default:
1778 @eval($type); // user can create own brush script.
1782 } // class graph