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