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