New translationd added.
[moodle.git] / lib / graphlib.php
blob4528577c46e6b01daf7e95a356f48949f4295d89
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' => 12, // 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' => 8, // padding around outer text. i.e. title, y label, and x label.
65 'inner_padding' => 6, // padding beteen axis text and graph.
66 'outer_border' => 'none', // colour of border aound image, or 'none'.
67 'inner_border' => 'black', // colour of border around actual graph, or 'none'.
68 'inner_border_type' => 'box', // 'box' for all four sides, 'axis' for x/y axis only,
69 // 'y' or 'y-left' for y axis only, 'y-right' for right y axis only,
70 // 'x' for x axis only, 'u' for both left and right y axis and x axis.
71 'outer_background' => 'none', // background colour of entire image.
72 'inner_background' => 'none', // background colour of plot area.
74 'y_min_left' => 0, // this will be reset to minimum value if there is a value lower than this.
75 'y_max_left' => 0, // this will be reset to maximum value if there is a value higher than this.
76 'y_min_right' => 0, // this will be reset to minimum value if there is a value lower than this.
77 'y_max_right' => 0, // this will be reset to maximum value if there is a value higher than this.
78 'x_min' => 0, // only used if x axis is numeric.
79 'x_max' => 0, // only used if x axis is numeric.
81 'y_resolution_left' => 1, // scaling for rounding of y axis max value.
82 // if max y value is 8645 then
83 // if y_resolution is 0, then y_max becomes 9000.
84 // if y_resolution is 1, then y_max becomes 8700.
85 // if y_resolution is 2, then y_max becomes 8650.
86 // if y_resolution is 3, then y_max becomes 8645.
87 // get it?
88 'y_decimal_left' => 0, // number of decimal places for y_axis text.
89 'y_resolution_right' => 2, // ... same for right hand side
90 'y_decimal_right' => 0, // ... same for right hand side
91 'x_resolution' => 2, // only used if x axis is numeric.
92 'x_decimal' => 0, // only used if x axis is numeric.
94 'point_size' => 4, // default point size. use even number for diamond or triangle to get nice look.
95 'brush_size' => 4, // default brush size for brush line.
96 'brush_type' => 'circle', // type of brush to use to draw line. choose from the following
97 // 'circle', 'square', 'horizontal', 'vertical', 'slash', 'backslash'
98 'bar_size' => 0.8, // size of bar to draw. <1 bars won't touch
99 // 1 is full width - i.e. bars will touch.
100 // >1 means bars will overlap.
101 'bar_spacing' => 10, // space in pixels between group of bars for each x value.
102 'shadow_offset' => 3, // draw shadow at this offset, unless overidden by data parameter.
103 'shadow' => 'grayCC', // 'none' or colour of shadow.
104 'shadow_below_axis' => true, // whether to draw shadows of bars and areas below the x/zero axis.
107 'x_axis_gridlines' => 'auto', // if set to a number then x axis is treated as numeric.
108 'y_axis_gridlines' => 6, // number of gridlines on y axis.
109 'zero_axis' => 'none', // colour to draw zero-axis, or 'none'.
112 'axis_font' => 'default.ttf', // axis text font. don't forget to set 'path_to_fonts' above.
113 'axis_size' => 12, // axis text font size in points
114 'axis_colour' => 'gray33', // colour of axis text.
115 'y_axis_angle' => 0, // rotation of axis text.
116 'x_axis_angle' => 90, // rotation of axis text.
118 'y_axis_text_left' => 1, // whether to print left hand y axis text. if 0 no text, if 1 all ticks have text,
119 'x_axis_text' => 1, // if 4 then print every 4th tick and text, etc...
120 'y_axis_text_right' => 0, // behaviour same as above for right hand y axis.
122 'x_offset' => 0.5, // x axis tick offset from y axis as fraction of tick spacing.
123 'y_ticks_colour' => 'black', // colour to draw y ticks, or 'none'
124 'x_ticks_colour' => 'black', // colour to draw x ticks, or 'none'
125 'y_grid' => 'line', // grid lines. set to 'line' or 'dash'...
126 'x_grid' => 'line', // or if set to 'none' print nothing.
127 'grid_colour' => 'grayEE', // default grid colour.
128 'tick_length' => 4, // length of ticks in pixels. can be negative. i.e. outside data drawing area.
130 'legend' => 'none', // default. no legend.
131 // otherwise: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
132 // 'outside-top', 'outside-bottom', 'outside-left', or 'outside-right'.
133 'legend_offset' => 10, // offset in pixels from graph or outside border.
134 'legend_padding' => 5, // padding around legend text.
135 'legend_font' => 'default.ttf', // legend text font. don't forget to set 'path_to_fonts' above.
136 'legend_size' => 9, // legend text point size.
137 'legend_colour' => 'black', // legend text colour.
138 'legend_border' => 'none', // legend border colour, or 'none'.
140 'decimal_point' => '.', // symbol for decimal separation '.' or ',' *european support.
141 'thousand_sep' => ',', // symbol for thousand separation ',' or ''
147 // init all text - title, labels, and axis text.
148 function init() {
151 /// Moodle mods: overrides the font path
152 global $CFG;
153 $currlang = current_language();
154 $fontpath = $CFG->dirroot."/lang/$currlang/fonts/";
155 if (!file_exists("$fontpath"."default.ttf")) {
156 $fontpath = $CFG->dirroot."/lang/en/fonts/";
158 $this->parameter['path_to_fonts'] = $fontpath;
160 if (file_exists("$fontpath"."lang_decode.php")) {
161 $this->parameter['lang_decode'] = "$fontpath"."lang_decode.php";
162 } else {
163 $this->parameter['lang_decode'] = "";
165 /// End Moodle mods
169 $this->calculated['outer_border'] = $this->calculated['boundary_box'];
171 // outer padding
172 $this->calculated['boundary_box']['left'] += $this->parameter['outer_padding'];
173 $this->calculated['boundary_box']['top'] += $this->parameter['outer_padding'];
174 $this->calculated['boundary_box']['right'] -= $this->parameter['outer_padding'];
175 $this->calculated['boundary_box']['bottom'] -= $this->parameter['outer_padding'];
177 $this->init_x_axis();
178 $this->init_y_axis();
179 $this->init_legend();
180 $this->init_labels();
182 // take into account tick lengths
183 $this->calculated['bottom_inner_padding'] = $this->parameter['inner_padding'];
184 if (($this->parameter['x_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
185 $this->calculated['bottom_inner_padding'] -= $this->parameter['tick_length'];
186 $this->calculated['boundary_box']['bottom'] -= $this->calculated['bottom_inner_padding'];
188 $this->calculated['left_inner_padding'] = $this->parameter['inner_padding'];
189 if ($this->parameter['y_axis_text_left']) {
190 if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
191 $this->calculated['left_inner_padding'] -= $this->parameter['tick_length'];
193 $this->calculated['boundary_box']['left'] += $this->calculated['left_inner_padding'];
195 $this->calculated['right_inner_padding'] = $this->parameter['inner_padding'];
196 if ($this->parameter['y_axis_text_right']) {
197 if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
198 $this->calculated['right_inner_padding'] -= $this->parameter['tick_length'];
200 $this->calculated['boundary_box']['right'] -= $this->calculated['right_inner_padding'];
202 // boundaryBox now has coords for plotting area.
203 $this->calculated['inner_border'] = $this->calculated['boundary_box'];
205 $this->init_data();
206 $this->init_x_ticks();
207 $this->init_y_ticks();
210 function draw_text() {
211 $colour = $this->parameter['outer_background'];
212 if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'fill'); // graph background
214 // draw border around image
215 $colour = $this->parameter['outer_border'];
216 if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'box'); // graph border
218 $this->draw_title();
219 $this->draw_x_label();
220 $this->draw_y_label_left();
221 $this->draw_y_label_right();
222 $this->draw_x_axis();
223 $this->draw_y_axis();
224 if ($this->calculated['y_axis_left']['has_data']) $this->draw_zero_axis_left(); // either draw zero axis on left
225 else if ($this->calculated['y_axis_right']['has_data']) $this->draw_zero_axis_right(); // ... or right.
226 $this->draw_legend();
228 // draw border around plot area
229 $colour = $this->parameter['inner_background'];
230 if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, 'fill'); // graph background
232 // draw border around image
233 $colour = $this->parameter['inner_border'];
234 if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, $this->parameter['inner_border_type']); // graph border
237 function draw_stack() {
238 $this->init();
239 $this->draw_text();
241 $yOrder = $this->y_order; // save y_order data.
242 // iterate over each data set. order is very important if you want to see data correctly. remember shadows!!
243 foreach ($yOrder as $set) {
244 $this->y_order = array($set);
245 $this->init_data();
246 $this->draw_data();
248 $this->y_order = $yOrder; // revert y_order data.
250 $this->output();
253 function draw() {
254 $this->init();
255 $this->draw_text();
256 $this->draw_data();
257 $this->output();
260 // draw a data set
261 function draw_set($order, $set, $offset) {
262 if ($offset) @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
263 else $colour = $this->y_format[$set]['colour'];
264 @$this->init_variable($point, $this->y_format[$set]['point'], 'none');
265 @$this->init_variable($pointSize, $this->y_format[$set]['point_size'], $this->parameter['point_size']);
266 @$this->init_variable($line, $this->y_format[$set]['line'], 'none');
267 @$this->init_variable($brushType, $this->y_format[$set]['brush_type'], $this->parameter['brush_type']);
268 @$this->init_variable($brushSize, $this->y_format[$set]['brush_size'], $this->parameter['brush_size']);
269 @$this->init_variable($bar, $this->y_format[$set]['bar'], 'none');
270 @$this->init_variable($barSize, $this->y_format[$set]['bar_size'], $this->parameter['bar_size']);
271 @$this->init_variable($area, $this->y_format[$set]['area'], 'none');
273 $lastX = 0;
274 $lastY = 'none';
275 $fromX = 0;
276 $fromY = 'none';
278 //print "set $set<BR>";
279 //expand_pre($this->calculated['y_plot']);
281 foreach ($this->x_data as $index => $x) {
282 //print "index $index<BR>";
283 $thisY = $this->calculated['y_plot'][$set][$index];
284 $thisX = $this->calculated['x_plot'][$index];
286 //print "$thisX, $thisY <BR>";
288 if (($bar!='none') && (string)$thisY != 'none') {
289 if ($relatedset = $this->offset_relation[$set]) { // Moodle
290 $yoffset = $this->calculated['y_plot'][$relatedset][$index]; // Moodle
291 } else { // Moodle
292 $yoffset = 0; // Moodle
293 } // Moodle
294 //$this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set); // Moodle
295 $this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set, $yoffset); // Moodle
298 if (($area!='none') && (((string)$lastY != 'none') && ((string)$thisY != 'none')))
299 $this->area($lastX, $lastY, $thisX, $thisY, $area, $colour, $offset);
301 if (($point!='none') && (string)$thisY != 'none') $this->plot($thisX, $thisY, $point, $pointSize, $colour, $offset);
303 if (($line!='none') && ((string)$thisY != 'none')) {
304 if ((string)$fromY != 'none')
305 $this->line($fromX, $fromY, $thisX, $thisY, $line, $brushType, $brushSize, $colour, $offset);
307 $fromY = $thisY; // start next line from here
308 $fromX = $thisX; // ...
309 } else {
310 $fromY = 'none';
311 $fromX = 'none';
314 $lastX = $thisX;
315 $lastY = $thisY;
319 function draw_data() {
320 // cycle thru y data to be plotted
321 // first check for drop shadows...
322 foreach ($this->y_order as $order => $set) {
323 @$this->init_variable($offset, $this->y_format[$set]['shadow_offset'], $this->parameter['shadow_offset']);
324 @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
325 if ($colour != 'none') $this->draw_set($order, $set, $offset);
329 // then draw data
330 foreach ($this->y_order as $order => $set) {
331 $this->draw_set($order, $set, 0);
335 function draw_legend() {
336 $position = $this->parameter['legend'];
337 if ($position == 'none') return; // abort if no border
339 $borderColour = $this->parameter['legend_border'];
340 $offset = $this->parameter['legend_offset'];
341 $padding = $this->parameter['legend_padding'];
342 $height = $this->calculated['legend']['boundary_box_all']['height'];
343 $width = $this->calculated['legend']['boundary_box_all']['width'];
344 $graphTop = $this->calculated['boundary_box']['top'];
345 $graphBottom = $this->calculated['boundary_box']['bottom'];
346 $graphLeft = $this->calculated['boundary_box']['left'];
347 $graphRight = $this->calculated['boundary_box']['right'];
348 $outsideRight = $this->calculated['outer_border']['right'];
349 $outsideBottom = $this->calculated['outer_border']['bottom'];
350 switch ($position) {
351 case 'top-left':
352 $top = $graphTop + $offset;
353 $bottom = $graphTop + $height + $offset;
354 $left = $graphLeft + $offset;
355 $right = $graphLeft + $width + $offset;
357 break;
358 case 'top-right':
359 $top = $graphTop + $offset;
360 $bottom = $graphTop + $height + $offset;
361 $left = $graphRight - $width - $offset;
362 $right = $graphRight - $offset;
364 break;
365 case 'bottom-left':
366 $top = $graphBottom - $height - $offset;
367 $bottom = $graphBottom - $offset;
368 $left = $graphLeft + $offset;
369 $right = $graphLeft + $width + $offset;
371 break;
372 case 'bottom-right':
373 $top = $graphBottom - $height - $offset;
374 $bottom = $graphBottom - $offset;
375 $left = $graphRight - $width - $offset;
376 $right = $graphRight - $offset;
377 break;
379 case 'outside-top' :
380 $top = $graphTop;
381 $bottom = $graphTop + $height;
382 $left = $outsideRight - $width - $offset;
383 $right = $outsideRight - $offset;
384 break;
386 case 'outside-bottom' :
387 $top = $graphBottom - $height;
388 $bottom = $graphBottom;
389 $left = $outsideRight - $width - $offset;
390 $right = $outsideRight - $offset;
391 break;
393 case 'outside-left' :
394 $top = $outsideBottom - $height - $offset;
395 $bottom = $outsideBottom - $offset;
396 $left = $graphLeft;
397 $right = $graphLeft + $width;
398 break;
400 case 'outside-right' :
401 $top = $outsideBottom - $height - $offset;
402 $bottom = $outsideBottom - $offset;
403 $left = $graphRight - $width;
404 $right = $graphRight;
405 break;
406 default: // default is top left. no particular reason.
407 $top = $this->calculated['boundary_box']['top'];
408 $bottom = $this->calculated['boundary_box']['top'] + $this->calculated['legend']['boundary_box_all']['height'];
409 $left = $this->calculated['boundary_box']['left'];
410 $right = $this->calculated['boundary_box']['right'] + $this->calculated['legend']['boundary_box_all']['width'];
413 // legend border
414 if($borderColour!='none') $this->draw_rectangle(array('top' => $top,
415 'left' => $left,
416 'bottom' => $bottom,
417 'right' => $right), $this->parameter['legend_border'], 'box');
419 // legend text
420 $legendText = array('points' => $this->parameter['legend_size'],
421 'angle' => 0,
422 'font' => $this->parameter['legend_font'],
423 'colour' => $this->parameter['legend_colour']);
425 $box = $this->calculated['legend']['boundary_box_max']['height']; // use max height for legend square size.
426 $x = $left + $padding;
427 $x_text = $x + $box * 2;
428 $y = $top + $padding;
430 foreach ($this->y_order as $set) {
431 $legendText['text'] = $this->calculated['legend']['text'][$set];
432 if ($legendText['text'] != 'none') {
433 // if text exists then draw box and text
434 $boxColour = $this->colour[$this->y_format[$set]['colour']];
436 // draw box
437 ImageFilledRectangle($this->image, $x, $y, $x + $box, $y + $box, $boxColour);
439 // draw text
440 $coords = array('x' => $x + $box * 2, 'y' => $y, 'reference' => 'top-left');
441 $legendText['boundary_box'] = $this->calculated['legend']['boundary_box'][$set];
442 $this->update_boundaryBox($legendText['boundary_box'], $coords);
443 $this->print_TTF($legendText);
444 $y += $padding + $box;
450 function draw_y_label_right() {
451 if (!$this->parameter['y_label_right']) return;
452 $x = $this->calculated['boundary_box']['right'] + $this->parameter['inner_padding'];
453 if ($this->parameter['y_axis_text_right']) $x += $this->calculated['y_axis_right']['boundary_box_max']['width']
454 + $this->calculated['right_inner_padding'];
455 $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
457 $label = $this->calculated['y_label_right'];
458 $coords = array('x' => $x, 'y' => $y, 'reference' => 'left-center');
459 $this->update_boundaryBox($label['boundary_box'], $coords);
460 $this->print_TTF($label);
464 function draw_y_label_left() {
465 if (!$this->parameter['y_label_left']) return;
466 $x = $this->calculated['boundary_box']['left'] - $this->parameter['inner_padding'];
467 if ($this->parameter['y_axis_text_left']) $x -= $this->calculated['y_axis_left']['boundary_box_max']['width']
468 + $this->calculated['left_inner_padding'];
469 $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
471 $label = $this->calculated['y_label_left'];
472 $coords = array('x' => $x, 'y' => $y, 'reference' => 'right-center');
473 $this->update_boundaryBox($label['boundary_box'], $coords);
474 $this->print_TTF($label);
477 function draw_title() {
478 if (!$this->parameter['title']) return;
479 //$y = $this->calculated['outside_border']['top'] + $this->parameter['outer_padding'];
480 $y = $this->calculated['boundary_box']['top'] - $this->parameter['outer_padding'];
481 $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
482 $label = $this->calculated['title'];
483 $coords = array('x' => $x, 'y' => $y, 'reference' => 'bottom-center');
484 $this->update_boundaryBox($label['boundary_box'], $coords);
485 $this->print_TTF($label);
488 function draw_x_label() {
489 if (!$this->parameter['x_label']) return;
490 $y = $this->calculated['boundary_box']['bottom'] + $this->parameter['inner_padding'];
491 if ($this->parameter['x_axis_text']) $y += $this->calculated['x_axis']['boundary_box_max']['height']
492 + $this->calculated['bottom_inner_padding'];
493 $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
494 $label = $this->calculated['x_label'];
495 $coords = array('x' => $x, 'y' => $y, 'reference' => 'top-center');
496 $this->update_boundaryBox($label['boundary_box'], $coords);
497 $this->print_TTF($label);
500 function draw_zero_axis_left() {
501 $colour = $this->parameter['zero_axis'];
502 if ($colour == 'none') return;
503 // draw zero axis on left hand side
504 $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top'] + ($this->calculated['y_axis_left']['max'] * $this->calculated['y_axis_left']['factor']));
505 ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
508 function draw_zero_axis_right() {
509 $colour = $this->parameter['zero_axis'];
510 if ($colour == 'none') return;
511 // draw zero axis on right hand side
512 $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top'] + ($this->calculated['y_axis_right']['max'] * $this->calculated['y_axis_right']['factor']));
513 ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
516 function draw_x_axis() {
517 $gridColour = $this->colour[$this->parameter['grid_colour']];
518 $tickColour = $this->colour[$this->parameter['x_ticks_colour']];
519 $axis_colour = $this->parameter['axis_colour'];
520 $xGrid = $this->parameter['x_grid'];
521 $gridTop = $this->calculated['boundary_box']['top'];
522 $gridBottom = $this->calculated['boundary_box']['bottom'];
524 if ($this->parameter['tick_length'] >= 0) {
525 $tickTop = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
526 $tickBottom = $this->calculated['boundary_box']['bottom'];
527 $textBottom = $tickBottom + $this->calculated['bottom_inner_padding'];
528 } else {
529 $tickTop = $this->calculated['boundary_box']['bottom'];
530 $tickBottom = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
531 $textBottom = $tickBottom + $this->calculated['bottom_inner_padding'];
534 $axis_font = $this->parameter['axis_font'];
535 $axis_size = $this->parameter['axis_size'];
536 $axis_angle = $this->parameter['x_axis_angle'];
538 if ($axis_angle == 0) $reference = 'top-center';
539 if ($axis_angle > 0) $reference = 'top-right';
540 if ($axis_angle < 0) $reference = 'top-left';
541 if ($axis_angle == 90) $reference = 'top-center';
543 //generic tag information. applies to all axis text.
544 $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
546 foreach ($this->calculated['x_axis']['tick_x'] as $set => $tickX) {
547 // draw x grid if colour specified
548 if ($xGrid != 'none') {
549 switch ($xGrid) {
550 case 'line':
551 ImageLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
552 break;
553 case 'dash':
554 ImageDashedLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
555 break;
559 if ($this->parameter['x_axis_text'] && !($set % $this->parameter['x_axis_text'])) { // test if tick should be displayed
560 // draw tick
561 if ($tickColour != 'none')
562 ImageLine($this->image, round($tickX), round($tickTop), round($tickX), round($tickBottom), $tickColour);
564 // draw axis text
565 $coords = array('x' => $tickX, 'y' => $textBottom, 'reference' => $reference);
566 $axisTag['text'] = $this->calculated['x_axis']['text'][$set];
567 $axisTag['boundary_box'] = $this->calculated['x_axis']['boundary_box'][$set];
568 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
569 $this->print_TTF($axisTag);
574 function draw_y_axis() {
575 $gridColour = $this->colour[$this->parameter['grid_colour']];
576 $tickColour = $this->colour[$this->parameter['y_ticks_colour']];
577 $axis_colour = $this->parameter['axis_colour'];
578 $yGrid = $this->parameter['y_grid'];
579 $gridLeft = $this->calculated['boundary_box']['left'];
580 $gridRight = $this->calculated['boundary_box']['right'];
582 // axis font information
583 $axis_font = $this->parameter['axis_font'];
584 $axis_size = $this->parameter['axis_size'];
585 $axis_angle = $this->parameter['y_axis_angle'];
586 $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
589 if ($this->calculated['y_axis_left']['has_data']) {
590 // LEFT HAND SIDE
591 // left and right coords for ticks
592 if ($this->parameter['tick_length'] >= 0) {
593 $tickLeft = $this->calculated['boundary_box']['left'];
594 $tickRight = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
595 } else {
596 $tickLeft = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
597 $tickRight = $this->calculated['boundary_box']['left'];
599 $textRight = $tickLeft - $this->calculated['left_inner_padding'];
601 if ($axis_angle == 0) $reference = 'right-center';
602 if ($axis_angle > 0) $reference = 'right-top';
603 if ($axis_angle < 0) $reference = 'right-bottom';
604 if ($axis_angle == 90) $reference = 'right-center';
606 foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
607 // draw y grid if colour specified
608 if ($yGrid != 'none') {
609 switch ($yGrid) {
610 case 'line':
611 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
612 break;
613 case 'dash':
614 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
615 break;
619 // y axis text
620 if ($this->parameter['y_axis_text_left'] && !($set % $this->parameter['y_axis_text_left'])) { // test if tick should be displayed
621 // draw tick
622 if ($tickColour != 'none')
623 ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
625 // draw axis text...
626 $coords = array('x' => $textRight, 'y' => $tickY, 'reference' => $reference);
627 $axisTag['text'] = $this->calculated['y_axis_left']['text'][$set];
628 $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
629 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
630 $this->print_TTF($axisTag);
635 if ($this->calculated['y_axis_right']['has_data']) {
636 // RIGHT HAND SIDE
637 // left and right coords for ticks
638 if ($this->parameter['tick_length'] >= 0) {
639 $tickLeft = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
640 $tickRight = $this->calculated['boundary_box']['right'];
641 } else {
642 $tickLeft = $this->calculated['boundary_box']['right'];
643 $tickRight = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
645 $textLeft = $tickRight+ $this->calculated['left_inner_padding'];
647 if ($axis_angle == 0) $reference = 'left-center';
648 if ($axis_angle > 0) $reference = 'left-bottom';
649 if ($axis_angle < 0) $reference = 'left-top';
650 if ($axis_angle == 90) $reference = 'left-center';
652 foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
653 if (!$this->calculated['y_axis_left']['has_data'] && $yGrid != 'none') { // draw grid if not drawn already (above)
654 switch ($yGrid) {
655 case 'line':
656 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
657 break;
658 case 'dash':
659 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
660 break;
664 if ($this->parameter['y_axis_text_right'] && !($set % $this->parameter['y_axis_text_right'])) { // test if tick should be displayed
665 // draw tick
666 if ($tickColour != 'none')
667 ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
669 // draw axis text...
670 $coords = array('x' => $textLeft, 'y' => $tickY, 'reference' => $reference);
671 $axisTag['text'] = $this->calculated['y_axis_right']['text'][$set];
672 $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
673 $this->update_boundaryBox($axisTag['boundary_box'], $coords);
674 $this->print_TTF($axisTag);
680 function init_data() {
681 $this->calculated['y_plot'] = array(); // array to hold pixel plotting coords for y axis
682 $height = $this->calculated['boundary_box']['bottom'] - $this->calculated['boundary_box']['top'];
683 $width = $this->calculated['boundary_box']['right'] - $this->calculated['boundary_box']['left'];
685 // calculate pixel steps between axis ticks.
686 $this->calculated['y_axis']['step'] = $height / ($this->parameter['y_axis_gridlines'] - 1);
688 // calculate x ticks spacing taking into account x offset for ticks.
689 $extraTick = 2 * $this->parameter['x_offset']; // extra tick to account for padding
690 $numTicks = $this->calculated['x_axis']['num_ticks'] - 1; // number of x ticks
692 $this->calculated['x_axis']['step'] = $width / ($numTicks + $extraTick);
693 $widthPlot = $width - ($this->calculated['x_axis']['step'] * $extraTick);
694 $this->calculated['x_axis']['step'] = $widthPlot / $numTicks;
696 //calculate factor for transforming x,y physical coords to logical coords for right hand y_axis.
697 $y_range = $this->calculated['y_axis_right']['max'] - $this->calculated['y_axis_right']['min'];
698 $y_range = ($y_range ? $y_range : 1);
699 $this->calculated['y_axis_right']['factor'] = $height / $y_range;
701 //calculate factor for transforming x,y physical coords to logical coords for left hand axis.
702 $yRange = $this->calculated['y_axis_left']['max'] - $this->calculated['y_axis_left']['min'];
703 $yRange = ($yRange ? $yRange : 1);
704 $this->calculated['y_axis_left']['factor'] = $height / $yRange;
705 if ($this->parameter['x_axis_gridlines'] != 'auto') {
706 $xRange = $this->calculated['x_axis']['max'] - $this->calculated['x_axis']['min'];
707 $xRange = ($xRange ? $xRange : 1);
708 $this->calculated['x_axis']['factor'] = $widthPlot / $xRange;
711 //expand_pre($this->calculated['boundary_box']);
712 // cycle thru all data sets...
713 $this->calculated['num_bars'] = 0;
714 foreach ($this->y_order as $order => $set) {
715 // determine how many bars there are
716 if (isset($this->y_format[$set]['bar']) && ($this->y_format[$set]['bar'] != 'none')) {
717 $this->calculated['bar_offset_index'][$set] = $this->calculated['num_bars']; // index to relate bar with data set.
718 $this->calculated['num_bars']++;
721 // calculate y coords for plotting data
722 foreach ($this->x_data as $index => $x) {
723 $this->calculated['y_plot'][$set][$index] = $this->y_data[$set][$index];
725 if ((string)$this->y_data[$set][$index] != 'none') {
727 if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
728 $this->calculated['y_plot'][$set][$index] =
729 round(($this->y_data[$set][$index] - $this->calculated['y_axis_right']['min'])
730 * $this->calculated['y_axis_right']['factor']);
731 } else {
732 //print "$set $index<BR>";
733 $this->calculated['y_plot'][$set][$index] =
734 round(($this->y_data[$set][$index] - $this->calculated['y_axis_left']['min'])
735 * $this->calculated['y_axis_left']['factor']);
741 //print "factor ".$this->calculated['x_axis']['factor']."<BR>";
742 //expand_pre($this->calculated['x_plot']);
744 // calculate bar parameters if bars are to be drawn.
745 if ($this->calculated['num_bars']) {
746 $xStep = $this->calculated['x_axis']['step'];
747 $totalWidth = $this->calculated['x_axis']['step'] - $this->parameter['bar_spacing'];
748 $barWidth = $totalWidth / $this->calculated['num_bars'];
750 $barX = ($barWidth - $totalWidth) / 2; // starting x offset
751 for ($i=0; $i < $this->calculated['num_bars']; $i++) {
752 $this->calculated['bar_offset_x'][$i] = $barX;
753 $barX += $barWidth; // add width of bar to x offset.
755 $this->calculated['bar_width'] = $barWidth;
761 function init_x_ticks() {
762 // get coords for x axis ticks and data plots
763 //$xGrid = $this->parameter['x_grid'];
764 $xStep = $this->calculated['x_axis']['step'];
765 $ticksOffset = $this->parameter['x_offset']; // where to start drawing ticks relative to y axis.
766 $gridLeft = $this->calculated['boundary_box']['left'] + ($xStep * $ticksOffset); // grid x start
767 $tickX = $gridLeft; // tick x coord
769 foreach ($this->calculated['x_axis']['text'] as $set => $value) {
770 //print "index: $set<BR>";
771 // x tick value
772 $this->calculated['x_axis']['tick_x'][$set] = $tickX;
773 // if num ticks is auto then x plot value is same as x tick
774 if ($this->parameter['x_axis_gridlines'] == 'auto') $this->calculated['x_plot'][$set] = round($tickX);
775 //print $this->calculated['x_plot'][$set].'<BR>';
776 $tickX += $xStep;
779 //print "xStep: $xStep <BR>";
780 // if numeric x axis then calculate x coords for each data point. this is seperate from x ticks.
781 $gridX = $gridLeft;
782 if (empty($this->calculated['x_axis']['factor'])) {
783 $this->calculated['x_axis']['factor'] = 0;
785 if (empty($this->calculated['x_axis']['min'])) {
786 $this->calculated['x_axis']['min'] = 0;
788 $factor = $this->calculated['x_axis']['factor'];
789 $min = $this->calculated['x_axis']['min'];
791 if ($this->parameter['x_axis_gridlines'] != 'auto') {
792 foreach ($this->x_data as $index => $x) {
793 //print "index: $index, x: $x<BR>";
794 $offset = $x - $this->calculated['x_axis']['min'];
796 //$gridX = ($offset * $this->calculated['x_axis']['factor']);
797 //print "offset: $offset <BR>";
798 //$this->calculated['x_plot'][$set] = $gridLeft + ($offset * $this->calculated['x_axis']['factor']);
800 $this->calculated['x_plot'][$index] = $gridLeft + ($x - $min) * $factor;
802 //print $this->calculated['x_plot'][$set].'<BR>';
805 //expand_pre($this->calculated['boundary_box']);
806 //print "factor ".$this->calculated['x_axis']['factor']."<BR>";
807 //expand_pre($this->calculated['x_plot']);
810 function init_y_ticks() {
811 // get coords for y axis ticks
813 $yStep = $this->calculated['y_axis']['step'];
814 $gridBottom = $this->calculated['boundary_box']['bottom'];
815 $tickY = $gridBottom; // tick y coord
817 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) {
818 $this->calculated['y_axis']['tick_y'][$i] = $tickY;
819 $tickY -= $yStep;
824 function init_labels() {
825 if ($this->parameter['title']) {
826 $size = $this->get_boundaryBox(
827 array('points' => $this->parameter['title_size'],
828 'angle' => 0,
829 'font' => $this->parameter['title_font'],
830 'text' => $this->parameter['title']));
831 $this->calculated['title']['boundary_box'] = $size;
832 $this->calculated['title']['text'] = $this->parameter['title'];
833 $this->calculated['title']['font'] = $this->parameter['title_font'];
834 $this->calculated['title']['points'] = $this->parameter['title_size'];
835 $this->calculated['title']['colour'] = $this->parameter['title_colour'];
836 $this->calculated['title']['angle'] = 0;
838 $this->calculated['boundary_box']['top'] += $size['height'] + $this->parameter['outer_padding'];
839 //$this->calculated['boundary_box']['top'] += $size['height'];
841 } else $this->calculated['title']['boundary_box'] = $this->get_null_size();
843 if ($this->parameter['y_label_left']) {
844 $this->calculated['y_label_left']['text'] = $this->parameter['y_label_left'];
845 $this->calculated['y_label_left']['angle'] = $this->parameter['y_label_angle'];
846 $this->calculated['y_label_left']['font'] = $this->parameter['label_font'];
847 $this->calculated['y_label_left']['points'] = $this->parameter['label_size'];
848 $this->calculated['y_label_left']['colour'] = $this->parameter['label_colour'];
850 $size = $this->get_boundaryBox($this->calculated['y_label_left']);
851 $this->calculated['y_label_left']['boundary_box'] = $size;
852 //$this->calculated['boundary_box']['left'] += $size['width'] + $this->parameter['inner_padding'];
853 $this->calculated['boundary_box']['left'] += $size['width'];
855 } else $this->calculated['y_label_left']['boundary_box'] = $this->get_null_size();
857 if ($this->parameter['y_label_right']) {
858 $this->calculated['y_label_right']['text'] = $this->parameter['y_label_right'];
859 $this->calculated['y_label_right']['angle'] = $this->parameter['y_label_angle'];
860 $this->calculated['y_label_right']['font'] = $this->parameter['label_font'];
861 $this->calculated['y_label_right']['points'] = $this->parameter['label_size'];
862 $this->calculated['y_label_right']['colour'] = $this->parameter['label_colour'];
864 $size = $this->get_boundaryBox($this->calculated['y_label_right']);
865 $this->calculated['y_label_right']['boundary_box'] = $size;
866 //$this->calculated['boundary_box']['right'] -= $size['width'] + $this->parameter['inner_padding'];
867 $this->calculated['boundary_box']['right'] -= $size['width'];
869 } else $this->calculated['y_label_right']['boundary_box'] = $this->get_null_size();
871 if ($this->parameter['x_label']) {
872 $this->calculated['x_label']['text'] = $this->parameter['x_label'];
873 $this->calculated['x_label']['angle'] = $this->parameter['x_label_angle'];
874 $this->calculated['x_label']['font'] = $this->parameter['label_font'];
875 $this->calculated['x_label']['points'] = $this->parameter['label_size'];
876 $this->calculated['x_label']['colour'] = $this->parameter['label_colour'];
878 $size = $this->get_boundaryBox($this->calculated['x_label']);
879 $this->calculated['x_label']['boundary_box'] = $size;
880 //$this->calculated['boundary_box']['bottom'] -= $size['height'] + $this->parameter['inner_padding'];
881 $this->calculated['boundary_box']['bottom'] -= $size['height'];
883 } else $this->calculated['x_label']['boundary_box'] = $this->get_null_size();
888 function init_legend() {
889 $this->calculated['legend'] = array(); // array to hold calculated values for legend.
890 //$this->calculated['legend']['boundary_box_max'] = array('height' => 0, 'width' => 0);
891 $this->calculated['legend']['boundary_box_max'] = $this->get_null_size();
892 if ($this->parameter['legend'] == 'none') return;
894 $position = $this->parameter['legend'];
895 $numSets = 0; // number of data sets with legends.
896 $sumTextHeight = 0; // total of height of all legend text items.
897 $width = 0;
898 $height = 0;
900 foreach ($this->y_order as $set) {
901 $text = isset($this->y_format[$set]['legend']) ? $this->y_format[$set]['legend'] : 'none';
902 $size = $this->get_boundaryBox(
903 array('points' => $this->parameter['legend_size'],
904 'angle' => 0,
905 'font' => $this->parameter['legend_font'],
906 'text' => $text));
908 $this->calculated['legend']['boundary_box'][$set] = $size;
909 $this->calculated['legend']['text'][$set] = $text;
910 //$this->calculated['legend']['font'][$set] = $this->parameter['legend_font'];
911 //$this->calculated['legend']['points'][$set] = $this->parameter['legend_size'];
912 //$this->calculated['legend']['angle'][$set] = 0;
914 if ($text && $text!='none') {
915 $numSets++;
916 $sumTextHeight += $size['height'];
919 if ($size['width'] > $this->calculated['legend']['boundary_box_max']['width'])
920 $this->calculated['legend']['boundary_box_max'] = $size;
923 $offset = $this->parameter['legend_offset']; // offset in pixels of legend box from graph border.
924 $padding = $this->parameter['legend_padding']; // padding in pixels around legend text.
925 $textWidth = $this->calculated['legend']['boundary_box_max']['width']; // width of largest legend item.
926 $textHeight = $this->calculated['legend']['boundary_box_max']['height']; // use height as size to use for colour square in legend.
927 $width = $padding * 2 + $textWidth + $textHeight * 2; // left and right padding + maximum text width + space for square
928 $height = $padding * ($numSets + 1) + $sumTextHeight; // top and bottom padding + padding between text + text.
931 $this->calculated['legend']['boundary_box_all'] = array('width' => $width,
932 'height' => $height,
933 'offset' => $offset,
934 'reference' => $position);
936 switch ($position) { // move in right or bottom if legend is outside data plotting area.
937 case 'outside-top' :
938 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
939 break;
941 case 'outside-bottom' :
942 $this->calculated['boundary_box']['right'] -= $offset + $width; // move in right hand side
943 break;
945 case 'outside-left' :
946 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
947 break;
949 case 'outside-right' :
950 $this->calculated['boundary_box']['bottom'] -= $offset + $height; // move in right hand side
951 break;
955 function init_y_axis() {
956 $this->calculated['y_axis_left'] = array(); // array to hold calculated values for y_axis on left.
957 $this->calculated['y_axis_left']['boundary_box_max'] = $this->get_null_size();
958 $this->calculated['y_axis_right'] = array(); // array to hold calculated values for y_axis on right.
959 $this->calculated['y_axis_right']['boundary_box_max'] = $this->get_null_size();
961 $axis_font = $this->parameter['axis_font'];
962 $axis_size = $this->parameter['axis_size'];
963 $axis_colour = $this->parameter['axis_colour'];
964 $axis_angle = $this->parameter['y_axis_angle'];
965 $y_tick_labels = $this->y_tick_labels;
967 $this->calculated['y_axis_left']['has_data'] = FALSE;
968 $this->calculated['y_axis_right']['has_data'] = FALSE;
970 // find min and max y values.
971 $minLeft = $this->parameter['y_min_left'];
972 $maxLeft = $this->parameter['y_max_left'];
973 $minRight = $this->parameter['y_min_right'];
974 $maxRight = $this->parameter['y_max_right'];
975 $dataLeft = array();
976 $dataRight = array();
977 foreach ($this->y_order as $order => $set) {
978 if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
979 $this->calculated['y_axis_right']['has_data'] = TRUE;
980 $dataRight = array_merge($dataRight, $this->y_data[$set]);
981 } else {
982 $this->calculated['y_axis_left']['has_data'] = TRUE;
983 $dataLeft = array_merge($dataLeft, $this->y_data[$set]);
986 $dataLeftRange = $this->find_range($dataLeft, $minLeft, $maxLeft, $this->parameter['y_resolution_left']);
987 $dataRightRange = $this->find_range($dataRight, $minRight, $maxRight, $this->parameter['y_resolution_right']);
988 $minLeft = $dataLeftRange['min'];
989 $maxLeft = $dataLeftRange['max'];
990 $minRight = $dataRightRange['min'];
991 $maxRight = $dataRightRange['max'];
993 $this->calculated['y_axis_left']['min'] = $minLeft;
994 $this->calculated['y_axis_left']['max'] = $maxLeft;
995 $this->calculated['y_axis_right']['min'] = $minRight;
996 $this->calculated['y_axis_right']['max'] = $maxRight;
998 $stepLeft = ($maxLeft - $minLeft) / ($this->parameter['y_axis_gridlines'] - 1);
999 $startLeft = $minLeft;
1000 $step_right = ($maxRight - $minRight) / ($this->parameter['y_axis_gridlines'] - 1);
1001 $start_right = $minRight;
1003 if ($this->parameter['y_axis_text_left']) {
1004 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1005 // left y axis
1006 if ($y_tick_labels) {
1007 $value = $y_tick_labels[$i];
1008 } else {
1009 $value = number_format($startLeft, $this->parameter['y_decimal_left'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1011 $this->calculated['y_axis_left']['data'][$i] = $startLeft;
1012 $this->calculated['y_axis_left']['text'][$i] = $value; // text is formatted raw data
1014 $size = $this->get_boundaryBox(
1015 array('points' => $axis_size,
1016 'font' => $axis_font,
1017 'angle' => $axis_angle,
1018 'colour' => $axis_colour,
1019 'text' => $value));
1020 $this->calculated['y_axis_left']['boundary_box'][$i] = $size;
1022 if ($size['height'] > $this->calculated['y_axis_left']['boundary_box_max']['height'])
1023 $this->calculated['y_axis_left']['boundary_box_max']['height'] = $size['height'];
1024 if ($size['width'] > $this->calculated['y_axis_left']['boundary_box_max']['width'])
1025 $this->calculated['y_axis_left']['boundary_box_max']['width'] = $size['width'];
1027 $startLeft += $stepLeft;
1029 $this->calculated['boundary_box']['left'] += $this->calculated['y_axis_left']['boundary_box_max']['width']
1030 + $this->parameter['inner_padding'];
1033 if ($this->parameter['y_axis_text_right']) {
1034 for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1035 // right y axis
1036 $value = number_format($start_right, $this->parameter['y_decimal_right'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1037 $this->calculated['y_axis_right']['data'][$i] = $start_right;
1038 $this->calculated['y_axis_right']['text'][$i] = $value; // text is formatted raw data
1039 $size = $this->get_boundaryBox(
1040 array('points' => $axis_size,
1041 'font' => $axis_font,
1042 'angle' => $axis_angle,
1043 'colour' => $axis_colour,
1044 'text' => $value));
1045 $this->calculated['y_axis_right']['boundary_box'][$i] = $size;
1047 if ($size['height'] > $this->calculated['y_axis_right']['boundary_box_max']['height'])
1048 $this->calculated['y_axis_right']['boundary_box_max'] = $size;
1049 if ($size['width'] > $this->calculated['y_axis_right']['boundary_box_max']['width'])
1050 $this->calculated['y_axis_right']['boundary_box_max']['width'] = $size['width'];
1052 $start_right += $step_right;
1054 $this->calculated['boundary_box']['right'] -= $this->calculated['y_axis_right']['boundary_box_max']['width']
1055 + $this->parameter['inner_padding'];
1059 function init_x_axis() {
1060 $this->calculated['x_axis'] = array(); // array to hold calculated values for x_axis.
1061 $this->calculated['x_axis']['boundary_box_max'] = array('height' => 0, 'width' => 0);
1063 $axis_font = $this->parameter['axis_font'];
1064 $axis_size = $this->parameter['axis_size'];
1065 $axis_colour = $this->parameter['axis_colour'];
1066 $axis_angle = $this->parameter['x_axis_angle'];
1068 // check whether to treat x axis as numeric
1069 if ($this->parameter['x_axis_gridlines'] == 'auto') { // auto means text based x_axis, not numeric...
1070 $this->calculated['x_axis']['num_ticks'] = sizeof($this->x_data);
1071 $data = $this->x_data;
1072 for ($i=0; $i < $this->calculated['x_axis']['num_ticks']; $i++) {
1073 $value = array_shift($data); // grab value from begin of array
1074 $this->calculated['x_axis']['data'][$i] = $value;
1075 $this->calculated['x_axis']['text'][$i] = $value; // raw data and text are both the same in this case
1076 $size = $this->get_boundaryBox(
1077 array('points' => $axis_size,
1078 'font' => $axis_font,
1079 'angle' => $axis_angle,
1080 'colour' => $axis_colour,
1081 'text' => $value));
1082 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1083 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1084 $this->calculated['x_axis']['boundary_box_max'] = $size;
1087 } else { // x axis is numeric so find max min values...
1088 $this->calculated['x_axis']['num_ticks'] = $this->parameter['x_axis_gridlines'];
1090 $min = $this->parameter['x_min'];
1091 $max = $this->parameter['x_max'];
1092 $data = array();
1093 $data = $this->find_range($this->x_data, $min, $max, $this->parameter['x_resolution']);
1094 $min = $data['min'];
1095 $max = $data['max'];
1096 $this->calculated['x_axis']['min'] = $min;
1097 $this->calculated['x_axis']['max'] = $max;
1099 $step = ($max - $min) / ($this->calculated['x_axis']['num_ticks'] - 1);
1100 $start = $min;
1102 for ($i = 0; $i < $this->calculated['x_axis']['num_ticks']; $i++) { // calculate x axis text sizes
1103 $value = number_format($start, $this->parameter['xDecimal'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1104 $this->calculated['x_axis']['data'][$i] = $start;
1105 $this->calculated['x_axis']['text'][$i] = $value; // text is formatted raw data
1107 $size = $this->get_boundaryBox(
1108 array('points' => $axis_size,
1109 'font' => $axis_font,
1110 'angle' => $axis_angle,
1111 'colour' => $axis_colour,
1112 'text' => $value));
1113 $this->calculated['x_axis']['boundary_box'][$i] = $size;
1115 if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1116 $this->calculated['x_axis']['boundary_box_max'] = $size;
1118 $start += $step;
1121 if ($this->parameter['x_axis_text'])
1122 $this->calculated['boundary_box']['bottom'] -= $this->calculated['x_axis']['boundary_box_max']['height']
1123 + $this->parameter['inner_padding'];
1126 // find max and min values for a data array given the resolution.
1127 function find_range($data, $min, $max, $resolution) {
1128 if (sizeof($data) == 0 ) return array('min' => 0, 'max' => 0);
1129 foreach ($data as $key => $value) {
1130 if ($value=='none') continue;
1131 if ($value > $max) $max = $value;
1132 if ($value < $min) $min = $value;
1135 if ($max == 0) {
1136 $factor = 1;
1137 } else {
1138 if ($max < 0) $factor = - pow(10, (floor(log10(abs($max))) + $resolution) );
1139 else $factor = pow(10, (floor(log10(abs($max))) - $resolution) );
1141 $factor = round($factor * 1000.0) / 1000.0; // To avoid some wierd rounding errors (Moodle)
1143 $max = $factor * @ceil($max / $factor);
1144 $min = $factor * @floor($min / $factor);
1146 //print "max=$max, min=$min<BR>";
1148 return array('min' => $min, 'max' => $max);
1151 function graph() {
1152 if (func_num_args() == 2) {
1153 $this->parameter['width'] = func_get_arg(0);
1154 $this->parameter['height'] = func_get_arg(1);
1156 //$this->boundaryBox = array(
1157 $this->calculated['boundary_box'] = array(
1158 'left' => 0,
1159 'top' => 0,
1160 'right' => $this->parameter['width'] - 1,
1161 'bottom' => $this->parameter['height'] - 1);
1163 $this->init_colours();
1165 //ImageColorTransparent($this->image, $this->colour['white']); // colour for transparency
1168 function print_TTF($message) {
1169 $points = $message['points'];
1170 $angle = $message['angle'];
1171 $text = $message['text'];
1172 $colour = $this->colour[$message['colour']];
1173 $font = $this->parameter['path_to_fonts'].$message['font'];
1175 $x = $message['boundary_box']['x'];
1176 $y = $message['boundary_box']['y'];
1177 $offsetX = $message['boundary_box']['offsetX'];
1178 $offsetY = $message['boundary_box']['offsetY'];
1179 $height = $message['boundary_box']['height'];
1180 $width = $message['boundary_box']['width'];
1181 $reference = $message['boundary_box']['reference'];
1183 switch ($reference) {
1184 case 'top-left':
1185 case 'left-top':
1186 $y += $height - $offsetY;
1187 //$y += $offsetY;
1188 $x += $offsetX;
1189 break;
1190 case 'left-center':
1191 $y += ($height / 2) - $offsetY;
1192 $x += $offsetX;
1193 break;
1194 case 'left-bottom':
1195 $y -= $offsetY;
1196 $x += $offsetX;
1197 break;
1198 case 'top-center':
1199 $y += $height - $offsetY;
1200 $x -= ($width / 2) - $offsetX;
1201 break;
1202 case 'top-right':
1203 case 'right-top':
1204 $y += $height - $offsetY;
1205 $x -= $width - $offsetX;
1206 break;
1207 case 'right-center':
1208 $y += ($height / 2) - $offsetY;
1209 $x -= $width - $offsetX;
1210 break;
1211 case 'right-bottom':
1212 $y -= $offsetY;
1213 $x -= $width - $offsetX;
1214 break;
1215 case 'bottom-center':
1216 $y -= $offsetY;
1217 $x -= ($width / 2) - $offsetX;
1218 break;
1219 default:
1220 $y = 0;
1221 $x = 0;
1222 break;
1224 if ($this->parameter['lang_decode']) { // Moodle addition
1225 include_once($this->parameter['lang_decode']);
1226 $text = lang_decode($text);
1228 ImageTTFText($this->image, $points, $angle, $x, $y, $colour, $font, $text);
1231 // move boundaryBox to coordinates specified
1232 function update_boundaryBox(&$boundaryBox, $coords) {
1233 $width = $boundaryBox['width'];
1234 $height = $boundaryBox['height'];
1235 $x = $coords['x'];
1236 $y = $coords['y'];
1237 $reference = $coords['reference'];
1238 switch ($reference) {
1239 case 'top-left':
1240 case 'left-top':
1241 $top = $y;
1242 $bottom = $y + $height;
1243 $left = $x;
1244 $right = $x + $width;
1245 break;
1246 case 'left-center':
1247 $top = $y - ($height / 2);
1248 $bottom = $y + ($height / 2);
1249 $left = $x;
1250 $right = $x + $width;
1251 break;
1252 case 'left-bottom':
1253 $top = $y - $height;
1254 $bottom = $y;
1255 $left = $x;
1256 $right = $x + $width;
1257 break;
1258 case 'top-center':
1259 $top = $y;
1260 $bottom = $y + $height;
1261 $left = $x - ($width / 2);
1262 $right = $x + ($width / 2);
1263 break;
1264 case 'right-top':
1265 case 'top-right':
1266 $top = $y;
1267 $bottom = $y + $height;
1268 $left = $x - $width;
1269 $right = $x;
1270 break;
1271 case 'right-center':
1272 $top = $y - ($height / 2);
1273 $bottom = $y + ($height / 2);
1274 $left = $x - $width;
1275 $right = $x;
1276 break;
1277 case 'bottom=right':
1278 case 'right-bottom':
1279 $top = $y - $height;
1280 $bottom = $y;
1281 $left = $x - $width;
1282 $right = $x;
1283 break;
1284 default:
1285 $top = 0;
1286 $bottom = $height;
1287 $left = 0;
1288 $right = $width;
1289 break;
1292 $boundaryBox = array_merge($boundaryBox, array('top' => $top,
1293 'bottom' => $bottom,
1294 'left' => $left,
1295 'right' => $right,
1296 'x' => $x,
1297 'y' => $y,
1298 'reference' => $reference));
1301 function get_null_size() {
1302 return array('width' => 0,
1303 'height' => 0,
1304 'offsetX' => 0,
1305 'offsetY' => 0,
1306 //'fontHeight' => 0
1310 function get_boundaryBox($message) {
1311 $points = $message['points'];
1312 $angle = $message['angle'];
1313 $font = $this->parameter['path_to_fonts'].$message['font'];
1314 $text = $message['text'];
1316 //print ('get_boundaryBox');
1317 //expandPre($message);
1319 // get font size
1320 $bounds = ImageTTFBBox($points, $angle, $font, "W");
1321 if ($angle < 0) {
1322 $fontHeight = abs($bounds[7]-$bounds[1]);
1323 } else if ($angle > 0) {
1324 $fontHeight = abs($bounds[1]-$bounds[7]);
1325 } else {
1326 $fontHeight = abs($bounds[7]-$bounds[1]);
1329 // get boundary box and offsets for printing at an angle
1330 if ($this->parameter['lang_decode']) { // Moodle addition
1331 include_once($this->parameter['lang_decode']);
1332 $text = lang_decode($text);
1334 $bounds = ImageTTFBBox($points, $angle, $font, $text);
1336 if ($angle < 0) {
1337 $width = abs($bounds[4]-$bounds[0]);
1338 $height = abs($bounds[3]-$bounds[7]);
1339 $offsetY = abs($bounds[3]-$bounds[1]);
1340 $offsetX = 0;
1342 } else if ($angle > 0) {
1343 $width = abs($bounds[2]-$bounds[6]);
1344 $height = abs($bounds[1]-$bounds[5]);
1345 $offsetY = 0;
1346 $offsetX = abs($bounds[0]-$bounds[6]);
1348 } else {
1349 $width = abs($bounds[4]-$bounds[6]);
1350 $height = abs($bounds[7]-$bounds[1]);
1351 $offsetY = 0;
1352 $offsetX = 0;
1355 //return values
1356 return array('width' => $width,
1357 'height' => $height,
1358 'offsetX' => $offsetX,
1359 'offsetY' => $offsetY,
1360 //'fontHeight' => $fontHeight
1364 function draw_rectangle($border, $colour, $type) {
1365 $colour = $this->colour[$colour];
1366 switch ($type) {
1367 case 'fill': // fill the rectangle
1368 ImageFilledRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1369 break;
1370 case 'box': // all sides
1371 ImageRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1372 break;
1373 case 'axis': // bottom x axis and left y axis
1374 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1375 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1376 break;
1377 case 'y': // left y axis only
1378 case 'y-left':
1379 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1380 break;
1381 case 'y-right': // right y axis only
1382 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1383 break;
1384 case 'x': // bottom x axis only
1385 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1386 break;
1387 case 'u': // u shaped. bottom x axis and both left and right y axis.
1388 ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1389 ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1390 ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1391 break;
1396 function init_colours() {
1397 $this->image = ImageCreate($this->parameter['width'], $this->parameter['height']);
1398 // standard colours
1399 $this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF); // first colour is background colour.
1400 $this->colour['black'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x00);
1401 $this->colour['maroon'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x00);
1402 $this->colour['green'] = ImageColorAllocate ($this->image, 0x00, 0x80, 0x00);
1403 $this->colour['ltgreen'] = ImageColorAllocate ($this->image, 0x52, 0xF1, 0x7F);
1404 $this->colour['ltltgreen']= ImageColorAllocate ($this->image, 0x99, 0xFF, 0x99);
1405 $this->colour['olive'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x00);
1406 $this->colour['navy'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0x80);
1407 $this->colour['purple'] = ImageColorAllocate ($this->image, 0x80, 0x00, 0x80);
1408 $this->colour['gray'] = ImageColorAllocate ($this->image, 0x80, 0x80, 0x80);
1409 $this->colour['red'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0x00);
1410 $this->colour['ltred'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x99);
1411 $this->colour['ltltred'] = ImageColorAllocate ($this->image, 0xFF, 0xCC, 0xCC);
1412 $this->colour['orange'] = ImageColorAllocate ($this->image, 0xFF, 0x66, 0x00);
1413 $this->colour['ltorange'] = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x66);
1414 $this->colour['ltltorange'] = ImageColorAllocate ($this->image, 0xFF, 0xcc, 0x99);
1415 $this->colour['lime'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0x00);
1416 $this->colour['yellow'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0x00);
1417 $this->colour['blue'] = ImageColorAllocate ($this->image, 0x00, 0x00, 0xFF);
1418 $this->colour['ltblue'] = ImageColorAllocate ($this->image, 0x00, 0xCC, 0xFF);
1419 $this->colour['ltltblue'] = ImageColorAllocate ($this->image, 0x99, 0xFF, 0xFF);
1420 $this->colour['fuchsia'] = ImageColorAllocate ($this->image, 0xFF, 0x00, 0xFF);
1421 $this->colour['aqua'] = ImageColorAllocate ($this->image, 0x00, 0xFF, 0xFF);
1422 //$this->colour['white'] = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF);
1423 // shades of gray
1424 $this->colour['grayF0'] = ImageColorAllocate ($this->image, 0xF0, 0xF0, 0xF0);
1425 $this->colour['grayEE'] = ImageColorAllocate ($this->image, 0xEE, 0xEE, 0xEE);
1426 $this->colour['grayDD'] = ImageColorAllocate ($this->image, 0xDD, 0xDD, 0xDD);
1427 $this->colour['grayCC'] = ImageColorAllocate ($this->image, 0xCC, 0xCC, 0xCC);
1428 $this->colour['gray33'] = ImageColorAllocate ($this->image, 0x33, 0x33, 0x33);
1429 $this->colour['gray66'] = ImageColorAllocate ($this->image, 0x66, 0x66, 0x66);
1430 $this->colour['gray99'] = ImageColorAllocate ($this->image, 0x99, 0x99, 0x99);
1432 $this->colour['none'] = 'none';
1433 return true;
1436 function output() {
1437 if ($this->debug) { // for debugging purposes.
1438 //expandPre($this->graph);
1439 //expandPre($this->y_data);
1440 //expandPre($this->x_data);
1441 //expandPre($this->parameter);
1442 } else {
1444 $expiresSeconds = $this->parameter['seconds_to_live'];
1445 $expiresHours = $this->parameter['hours_to_live'];
1447 if ($expiresHours || $expiresSeconds) {
1448 $now = mktime (date("H"),date("i"),date("s"),date("m"),date("d"),date("Y"));
1449 $expires = mktime (date("H")+$expiresHours,date("i"),date("s")+$expiresSeconds,date("m"),date("d"),date("Y"));
1450 $expiresGMT = gmdate('D, d M Y H:i:s', $expires).' GMT';
1451 $lastModifiedGMT = gmdate('D, d M Y H:i:s', $now).' GMT';
1453 Header('Last-modified: '.$lastModifiedGMT);
1454 Header('Expires: '.$expiresGMT);
1457 if ($this->parameter['file_name'] == 'none') {
1458 switch ($this->parameter['output_format']) {
1459 case 'GIF':
1460 Header("Content-type: image/gif"); // GIF??. switch to PNG guys!!
1461 ImageGIF($this->image);
1462 break;
1463 case 'JPEG':
1464 Header("Content-type: image/jpeg"); // JPEG for line art??. included for completeness.
1465 ImageJPEG($this->image);
1466 break;
1467 default:
1468 Header("Content-type: image/png"); // preferred output format
1469 ImagePNG($this->image);
1470 break;
1472 } else {
1473 switch ($this->parameter['output_format']) {
1474 case 'GIF':
1475 ImageGIF($this->image, $this->parameter['file_name'].'.gif');
1476 break;
1477 case 'JPEG':
1478 ImageJPEG($this->image, $this->parameter['file_name'].'.jpg');
1479 break;
1480 default:
1481 ImagePNG($this->image, $this->parameter['file_name'].'.png');
1482 break;
1486 ImageDestroy($this->image);
1488 } // function output
1490 function init_variable(&$variable, $value, $default) {
1491 if (!empty($value)) $variable = $value;
1492 else if (isset($default)) $variable = $default;
1493 else unset($variable);
1496 // plot a point. options include square, circle, diamond, triangle, and dot. offset is used for drawing shadows.
1497 // for diamonds and triangles the size should be an even number to get nice look. if odd the points are crooked.
1498 function plot($x, $y, $type, $size, $colour, $offset) {
1499 //print("drawing point of type: $type, at offset: $offset");
1500 $u = $x + $offset;
1501 $v = $this->calculated['inner_border']['bottom'] - $y + $offset;
1502 $half = $size / 2;
1504 switch ($type) {
1505 case 'square':
1506 ImageFilledRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1507 break;
1508 case 'square-open':
1509 ImageRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
1510 break;
1511 case 'circle':
1512 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1513 ImageFillToBorder($this->image, $u, $v, $this->colour[$colour], $this->colour[$colour]);
1514 break;
1515 case 'circle-open':
1516 ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
1517 break;
1518 case 'diamond':
1519 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1520 break;
1521 case 'diamond-open':
1522 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
1523 break;
1524 case 'triangle':
1525 ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1526 break;
1527 case 'triangle-open':
1528 ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
1529 break;
1530 case 'dot':
1531 ImageSetPixel($this->image, $u, $v, $this->colour[$colour]);
1532 break;
1536 function bar($x, $y, $type, $size, $colour, $offset, $index, $yoffset) {
1537 $index_offset = $this->calculated['bar_offset_index'][$index];
1538 if ( $yoffset ) {
1539 $bar_offsetx = 0;
1540 } else {
1541 $bar_offsetx = $this->calculated['bar_offset_x'][$index_offset];
1543 //$this->dbug("drawing bar at offset = $offset : index = $index: bar_offsetx = $bar_offsetx");
1545 $span = ($this->calculated['bar_width'] * $size) / 2;
1546 $x_left = $x + $bar_offsetx - $span;
1547 $x_right = $x + $bar_offsetx + $span;
1549 if ($this->parameter['zero_axis'] != 'none') {
1550 $zero = $this->calculated['zero_axis'];
1551 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1552 $u_left = $x_left + $offset;
1553 $u_right = $x_right + $offset - 1;
1554 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1556 if ($v > $zero) {
1557 $top = $zero +1;
1558 $bottom = $v;
1559 } else {
1560 $top = $v;
1561 $bottom = $zero - 1;
1564 switch ($type) {
1565 case 'open':
1566 //ImageRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1567 if ($v > $zero)
1568 ImageRectangle($this->image, round($u_left), $bottom, round($u_right), $bottom, $this->colour[$colour]);
1569 else
1570 ImageRectangle($this->image, round($u_left), $top, round($u_right), $top, $this->colour[$colour]);
1571 ImageRectangle($this->image, round($u_left), $top, round($u_left), $bottom, $this->colour[$colour]);
1572 ImageRectangle($this->image, round($u_right), $top, round($u_right), $bottom, $this->colour[$colour]);
1573 break;
1574 case 'fill':
1575 ImageFilledRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
1576 break;
1579 } else {
1581 $bottom = $this->calculated['boundary_box']['bottom'];
1582 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1583 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1584 $u_left = $x_left + $offset;
1585 $u_right = $x_right + $offset - 1;
1586 $v = $this->calculated['boundary_box']['bottom'] - $y + $offset;
1588 // Moodle addition, plus the function parameter yoffset
1589 if ($yoffset) { // Moodle
1590 $yoffset = $yoffset - round(($bottom - $v) / 2.0); // Moodle
1591 $bottom -= $yoffset; // Moodle
1592 $v -= $yoffset; // Moodle
1593 } // Moodle
1595 switch ($type) {
1596 case 'open':
1597 ImageRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1598 break;
1599 case 'fill':
1600 ImageFilledRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
1601 break;
1606 function area($x_start, $y_start, $x_end, $y_end, $type, $colour, $offset) {
1607 //dbug("drawing area type: $type, at offset: $offset");
1608 if ($this->parameter['zero_axis'] != 'none') {
1609 $bottom = $this->calculated['boundary_box']['bottom'];
1610 $zero = $this->calculated['zero_axis'];
1611 if ($this->parameter['shadow_below_axis'] ) $zero += $offset;
1612 $u_start = $x_start + $offset;
1613 $u_end = $x_end + $offset;
1614 $v_start = $bottom - $y_start + $offset;
1615 $v_end = $bottom - $y_end + $offset;
1616 switch ($type) {
1617 case 'fill':
1618 // draw it this way 'cos the FilledPolygon routine seems a bit buggy.
1619 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1620 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1621 break;
1622 case 'open':
1623 //ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
1624 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1625 ImageLine($this->image, $u_start, $v_start, $u_start, $zero, $this->colour[$colour]);
1626 ImageLine($this->image, $u_end, $v_end, $u_end, $zero, $this->colour[$colour]);
1627 break;
1629 } else {
1630 $bottom = $this->calculated['boundary_box']['bottom'];
1631 $u_start = $x_start + $offset;
1632 $u_end = $x_end + $offset;
1633 $v_start = $bottom - $y_start + $offset;
1634 $v_end = $bottom - $y_end + $offset;
1636 if ($this->parameter['shadow_below_axis'] ) $bottom += $offset;
1637 if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
1638 switch ($type) {
1639 case 'fill':
1640 ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1641 break;
1642 case 'open':
1643 ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
1644 break;
1649 function line($x_start, $y_start, $x_end, $y_end, $type, $brush_type, $brush_size, $colour, $offset) {
1650 //dbug("drawing line of type: $type, at offset: $offset");
1651 $u_start = $x_start + $offset;
1652 $v_start = $this->calculated['boundary_box']['bottom'] - $y_start + $offset;
1653 $u_end = $x_end + $offset;
1654 $v_end = $this->calculated['boundary_box']['bottom'] - $y_end + $offset;
1656 switch ($type) {
1657 case 'brush':
1658 $this->draw_brush_line($u_start, $v_start, $u_end, $v_end, $brush_size, $brush_type, $colour);
1659 break;
1660 case 'line' :
1661 ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1662 break;
1663 case 'dash':
1664 ImageDashedLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
1665 break;
1669 // function to draw line. would prefer to use gdBrush but this is not supported yet.
1670 function draw_brush_line($x0, $y0, $x1, $y1, $size, $type, $colour) {
1671 //$this->dbug("line: $x0, $y0, $x1, $y1");
1672 $dy = $y1 - $y0;
1673 $dx = $x1 - $x0;
1674 $t = 0;
1675 $watchdog = 1024; // precaution to prevent infinite loops.
1677 $this->draw_brush($x0, $y0, $size, $type, $colour);
1678 if (abs($dx) > abs($dy)) { // slope < 1
1679 //$this->dbug("slope < 1");
1680 $m = $dy / $dx; // compute slope
1681 $t += $y0;
1682 $dx = ($dx < 0) ? -1 : 1;
1683 $m *= $dx;
1684 while (round($x0) != round($x1)) {
1685 if (!$watchdog--) break;
1686 $x0 += $dx; // step to next x value
1687 $t += $m; // add slope to y value
1688 $y = round($t);
1689 //$this->dbug("x0=$x0, x1=$x1, y=$y watchdog=$watchdog");
1690 $this->draw_brush($x0, $y, $size, $type, $colour);
1693 } else { // slope >= 1
1694 //$this->dbug("slope >= 1");
1695 $m = $dx / $dy; // compute slope
1696 $t += $x0;
1697 $dy = ($dy < 0) ? -1 : 1;
1698 $m *= $dy;
1699 while (round($y0) != round($y1)) {
1700 if (!$watchdog--) break;
1701 $y0 += $dy; // step to next y value
1702 $t += $m; // add slope to x value
1703 $x = round($t);
1704 //$this->dbug("x=$x, y0=$y0, y1=$y1 watchdog=$watchdog");
1705 $this->draw_brush($x, $y0, $size, $type, $colour);
1711 function draw_brush($x, $y, $size, $type, $colour) {
1712 $x = round($x);
1713 $y = round($y);
1714 $half = round($size / 2);
1715 switch ($type) {
1716 case 'circle':
1717 ImageArc($this->image, $x, $y, $size, $size, 0, 360, $this->colour[$colour]);
1718 ImageFillToBorder($this->image, $x, $y, $this->colour[$colour], $this->colour[$colour]);
1719 break;
1720 case 'square':
1721 ImageFilledRectangle($this->image, $x-$half, $y-$half, $x+$half, $y+$half, $this->colour[$colour]);
1722 break;
1723 case 'vertical':
1724 ImageFilledRectangle($this->image, $x, $y-$half, $x+1, $y+$half, $this->colour[$colour]);
1725 break;
1726 case 'horizontal':
1727 ImageFilledRectangle($this->image, $x-$half, $y, $x+$half, $y+1, $this->colour[$colour]);
1728 break;
1729 case 'slash':
1730 ImageFilledPolygon($this->image, array($x+$half, $y-$half,
1731 $x+$half+1, $y-$half,
1732 $x-$half+1, $y+$half,
1733 $x-$half, $y+$half
1734 ), 4, $this->colour[$colour]);
1735 break;
1736 case 'backslash':
1737 ImageFilledPolygon($this->image, array($x-$half, $y-$half,
1738 $x-$half+1, $y-$half,
1739 $x+$half+1, $y+$half,
1740 $x+$half, $y+$half
1741 ), 4, $this->colour[$colour]);
1742 break;
1743 default:
1744 @eval($type); // user can create own brush script.
1748 } // class graph