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