[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/lib/ -> graphlib.php (source)

   1  <?php  // $Id: graphlib.php,v 1.20.10.3 2008/06/11 08:22:35 jmg324 Exp $
   2  
   3  /*
   4  Graph Class. PHP Class to draw line, point, bar, and area graphs, including numeric x-axis and double y-axis.
   5  Version: 1.6.3
   6  Copyright (C) 2000  Herman Veluwenkamp
   7  
   8  This library is free software; you can redistribute it and/or
   9  modify it under the terms of the GNU Lesser General Public
  10  License as published by the Free Software Foundation; either
  11  version 2.1 of the License, or (at your option) any later version.
  12  
  13  This library is distributed in the hope that it will be useful,
  14  but WITHOUT ANY WARRANTY; without even the implied warranty of
  15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16  Lesser General Public License for more details.
  17  
  18  You should have received a copy of the GNU Lesser General Public
  19  License along with this library; if not, write to the Free Software
  20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21  
  22  Copy of GNU Lesser General Public License at: http://www.gnu.org/copyleft/lesser.txt
  23  Contact author at: hermanV@mindless.com
  24  */
  25  
  26  /* This file contains modifications by Martin Dougiamas
  27   * as part of Moodle (http://moodle.com).  Modified lines
  28   * are marked with "Moodle".
  29   */
  30  
  31  
  32  class graph {
  33    var $image;
  34    var $debug             =   FALSE;        // be careful!!
  35    var $calculated        =   array();      // array of computed values for chart
  36    var $parameter         =   array(        // input parameters
  37      'width'              =>  320,          // default width of image
  38      'height'             =>  240,          // default height of image
  39      'file_name'          => 'none',        // name of file for file to be saved as.
  40                                             //  NOTE: no suffix required. this is determined from output_format below.
  41      'output_format'      => 'PNG',         // image output format. 'GIF', 'PNG', 'JPEG'. default 'PNG'.
  42  
  43      'seconds_to_live'    =>  0,            // expiry time in seconds (for HTTP header)
  44      'hours_to_live'      =>  0,            // expiry time in hours (for HTTP header)
  45      'path_to_fonts'      => 'fonts/',      // path to fonts folder. don't forget *trailing* slash!!
  46                                             //   for WINDOZE this may need to be the full path, not relative.
  47  
  48      'title'              => 'Graph Title', // text for graph title
  49      'title_font'         => 'default.ttf',   // title text font. don't forget to set 'path_to_fonts' above.
  50      'title_size'         =>  16,           // title text point size
  51      'title_colour'       => 'black',       // colour for title text
  52  
  53      'x_label'            => '',            // if this is set then this text is printed on bottom axis of graph.
  54      'y_label_left'       => '',            // if this is set then this text is printed on left axis of graph.
  55      'y_label_right'      => '',            // if this is set then this text is printed on right axis of graph.
  56  
  57      'label_size'         =>  8,           // label text point size
  58      'label_font'         => 'default.ttf', // label text font. don't forget to set 'path_to_fonts' above.
  59      'label_colour'       => 'gray33',      // label text colour
  60      'y_label_angle'      =>  90,           // rotation of y axis label
  61  
  62      'x_label_angle'      =>  90,            // rotation of y axis label
  63  
  64      'outer_padding'      =>  5,            // padding around outer text. i.e. title, y label, and x label.
  65      'inner_padding'      =>  0,            // padding beteen axis text and graph.
  66      'x_inner_padding'      =>  5,            // padding beteen axis text and graph.
  67      'y_inner_padding'      =>  6,            // padding beteen axis text and graph.
  68      'outer_border'       => 'none',        // colour of border aound image, or 'none'.
  69      'inner_border'       => 'black',       // colour of border around actual graph, or 'none'.
  70      'inner_border_type'  => 'box',         // 'box' for all four sides, 'axis' for x/y axis only,
  71                                             // 'y' or 'y-left' for y axis only, 'y-right' for right y axis only,
  72                                             // 'x' for x axis only, 'u' for both left and right y axis and x axis.
  73      'outer_background'   => 'none',        // background colour of entire image.
  74      'inner_background'   => 'none',        // background colour of plot area.
  75  
  76      'y_min_left'         =>  0,            // this will be reset to minimum value if there is a value lower than this.
  77      'y_max_left'         =>  0,            // this will be reset to maximum value if there is a value higher than this.
  78      'y_min_right'        =>  0,            // this will be reset to minimum value if there is a value lower than this.
  79      'y_max_right'        =>  0,            // this will be reset to maximum value if there is a value higher than this.
  80      'x_min'              =>  0,            // only used if x axis is numeric.
  81      'x_max'              =>  0,            // only used if x axis is numeric.
  82  
  83      'y_resolution_left'  =>  1,            // scaling for rounding of y axis max value.
  84                                             // if max y value is 8645 then
  85                                             // if y_resolution is 0, then y_max becomes 9000.
  86                                             // if y_resolution is 1, then y_max becomes 8700.
  87                                             // if y_resolution is 2, then y_max becomes 8650.
  88                                             // if y_resolution is 3, then y_max becomes 8645.
  89                                             // get it?
  90      'y_decimal_left'     =>  0,            // number of decimal places for y_axis text.
  91      'y_resolution_right' =>  2,            // ... same for right hand side
  92      'y_decimal_right'    =>  0,            // ... same for right hand side
  93      'x_resolution'       =>  2,            // only used if x axis is numeric.
  94      'x_decimal'          =>  0,            // only used if x axis is numeric.
  95  
  96      'point_size'         =>  4,            // default point size. use even number for diamond or triangle to get nice look.
  97      'brush_size'         =>  4,            // default brush size for brush line.
  98      'brush_type'         => 'circle',      // type of brush to use to draw line. choose from the following
  99                                             //   'circle', 'square', 'horizontal', 'vertical', 'slash', 'backslash'
 100      'bar_size'           =>  0.8,          // size of bar to draw. <1 bars won't touch
 101                                             //   1 is full width - i.e. bars will touch.
 102                                             //   >1 means bars will overlap.
 103      'bar_spacing'        =>  10,           // space in pixels between group of bars for each x value.
 104      'shadow_offset'      =>  3,            // draw shadow at this offset, unless overidden by data parameter.
 105      'shadow'             => 'grayCC',      // 'none' or colour of shadow.
 106      'shadow_below_axis'  => true,         // whether to draw shadows of bars and areas below the x/zero axis.
 107  
 108  
 109      'x_axis_gridlines'   => 'auto',        // if set to a number then x axis is treated as numeric.
 110      'y_axis_gridlines'   =>  6,            // number of gridlines on y axis.
 111      'zero_axis'          => 'none',        // colour to draw zero-axis, or 'none'.
 112  
 113  
 114      'axis_font'          => 'default.ttf', // axis text font. don't forget to set 'path_to_fonts' above.
 115      'axis_size'          =>  8,            // axis text font size in points
 116      'axis_colour'        => 'gray33',      // colour of axis text.
 117      'y_axis_angle'       =>  0,            // rotation of axis text.
 118      'x_axis_angle'       =>  0,            // rotation of axis text.
 119  
 120      'y_axis_text_left'   =>  1,            // whether to print left hand y axis text. if 0 no text, if 1 all ticks have text,
 121      'x_axis_text'        =>  1,            //   if 4 then print every 4th tick and text, etc...
 122      'y_axis_text_right'  =>  0,            // behaviour same as above for right hand y axis.
 123  
 124      'x_offset'           =>  0.5,          // x axis tick offset from y axis as fraction of tick spacing.
 125      'y_ticks_colour'     => 'black',       // colour to draw y ticks, or 'none'
 126      'x_ticks_colour'     => 'black',       // colour to draw x ticks, or 'none'
 127      'y_grid'             => 'line',        // grid lines. set to 'line' or 'dash'...
 128      'x_grid'             => 'line',        //   or if set to 'none' print nothing.
 129      'grid_colour'        => 'grayEE',      // default grid colour.
 130      'tick_length'        =>  4,            // length of ticks in pixels. can be negative. i.e. outside data drawing area.
 131  
 132      'legend'             => 'none',        // default. no legend.
 133                                            // otherwise: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
 134                                            //   'outside-top', 'outside-bottom', 'outside-left', or 'outside-right'.
 135      'legend_offset'      =>  10,           // offset in pixels from graph or outside border.
 136      'legend_padding'     =>  5,            // padding around legend text.
 137      'legend_font'        => 'default.ttf',   // legend text font. don't forget to set 'path_to_fonts' above.
 138      'legend_size'        =>  8,            // legend text point size.
 139      'legend_colour'      => 'black',       // legend text colour.
 140      'legend_border'      => 'none',        // legend border colour, or 'none'.
 141  
 142      'decimal_point'      => '.',           // symbol for decimal separation  '.' or ',' *european support.
 143      'thousand_sep'       => ',',           // symbol for thousand separation ',' or ''
 144  
 145    );
 146    var $y_tick_labels     =   null;         // array of text values for y-axis tick labels
 147    var $offset_relation   =   null;         // array of offsets for different sets of data
 148  
 149  
 150  
 151  // init all text - title, labels, and axis text.
 152  function init() {
 153  
 154    /// Moodle mods:  overrides the font path and encodings
 155  
 156    global $CFG;
 157  
 158    /// A default.ttf is searched for in this order:
 159    ///      dataroot/lang/xx_local/fonts
 160    ///      dataroot/lang/xx/fonts
 161    ///      dirroot/lang/xx/fonts
 162    ///      dataroot/lang
 163    ///      lib/
 164  
 165    $currlang = current_language();
 166    if (file_exists("$CFG->dataroot/lang/".$currlang."_local/fonts/default.ttf")) {
 167        $fontpath = "$CFG->dataroot/lang/".$currlang."_local/fonts/";
 168    } else if (file_exists("$CFG->dataroot/lang/$currlang/fonts/default.ttf")) {
 169        $fontpath = "$CFG->dataroot/lang/$currlang/fonts/";
 170    } else if (file_exists("$CFG->dirroot/lang/$currlang/fonts/default.ttf")) {
 171        $fontpath = "$CFG->dirroot/lang/$currlang/fonts/";
 172    } else if (file_exists("$CFG->dataroot/lang/default.ttf")) {
 173        $fontpath = "$CFG->dataroot/lang/";
 174    } else {
 175        $fontpath = "$CFG->libdir/";
 176    }
 177  
 178    $this->parameter['path_to_fonts'] = $fontpath;
 179  
 180    /// End Moodle mods
 181  
 182  
 183  
 184    $this->calculated['outer_border'] = $this->calculated['boundary_box'];
 185  
 186    // outer padding
 187    $this->calculated['boundary_box']['left']   += $this->parameter['outer_padding'];
 188    $this->calculated['boundary_box']['top']    += $this->parameter['outer_padding'];
 189    $this->calculated['boundary_box']['right']  -= $this->parameter['outer_padding'];
 190    $this->calculated['boundary_box']['bottom'] -= $this->parameter['outer_padding'];
 191  
 192    $this->init_x_axis();
 193    $this->init_y_axis();
 194    $this->init_legend();
 195    $this->init_labels();
 196  
 197    //  take into account tick lengths
 198    $this->calculated['bottom_inner_padding'] = $this->parameter['x_inner_padding'];
 199    if (($this->parameter['x_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
 200      $this->calculated['bottom_inner_padding'] -= $this->parameter['tick_length'];
 201    $this->calculated['boundary_box']['bottom'] -= $this->calculated['bottom_inner_padding'];
 202  
 203    $this->calculated['left_inner_padding'] = $this->parameter['y_inner_padding'];
 204    if ($this->parameter['y_axis_text_left']) {
 205      if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
 206        $this->calculated['left_inner_padding'] -= $this->parameter['tick_length'];
 207    }
 208    $this->calculated['boundary_box']['left'] += $this->calculated['left_inner_padding'];
 209  
 210    $this->calculated['right_inner_padding'] = $this->parameter['y_inner_padding'];
 211    if ($this->parameter['y_axis_text_right']) {
 212      if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
 213        $this->calculated['right_inner_padding'] -= $this->parameter['tick_length'];
 214    }
 215    $this->calculated['boundary_box']['right'] -= $this->calculated['right_inner_padding'];
 216  
 217    // boundaryBox now has coords for plotting area.
 218    $this->calculated['inner_border'] = $this->calculated['boundary_box'];
 219  
 220    $this->init_data();
 221    $this->init_x_ticks();
 222    $this->init_y_ticks();
 223  }
 224  
 225  function draw_text() {
 226    $colour = $this->parameter['outer_background'];
 227    if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'fill'); // graph background
 228  
 229    // draw border around image
 230    $colour = $this->parameter['outer_border'];
 231    if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'box'); // graph border
 232  
 233    $this->draw_title();
 234    $this->draw_x_label();
 235    $this->draw_y_label_left();
 236    $this->draw_y_label_right();
 237    $this->draw_x_axis();
 238    $this->draw_y_axis();
 239    if      ($this->calculated['y_axis_left']['has_data'])  $this->draw_zero_axis_left();  // either draw zero axis on left
 240    else if ($this->calculated['y_axis_right']['has_data']) $this->draw_zero_axis_right(); // ... or right.
 241    $this->draw_legend();
 242  
 243    // draw border around plot area
 244    $colour = $this->parameter['inner_background'];
 245    if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, 'fill'); // graph background
 246  
 247    // draw border around image
 248    $colour = $this->parameter['inner_border'];
 249    if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, $this->parameter['inner_border_type']); // graph border
 250  }
 251  
 252  function draw_stack() {
 253    $this->init();
 254    $this->draw_text();
 255  
 256    $yOrder = $this->y_order; // save y_order data.
 257    // iterate over each data set. order is very important if you want to see data correctly. remember shadows!!
 258    foreach ($yOrder as $set) {
 259      $this->y_order = array($set);
 260      $this->init_data();
 261      $this->draw_data();
 262    }
 263    $this->y_order = $yOrder; // revert y_order data.
 264  
 265    $this->output();
 266  }
 267  
 268  function draw() {
 269    $this->init();
 270    $this->draw_text();
 271    $this->draw_data();
 272    $this->output();
 273  }
 274  
 275  // draw a data set
 276  function draw_set($order, $set, $offset) {
 277    if ($offset) @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
 278    else $colour  = $this->y_format[$set]['colour'];
 279    @$this->init_variable($point,      $this->y_format[$set]['point'],      'none');
 280    @$this->init_variable($pointSize,  $this->y_format[$set]['point_size'],  $this->parameter['point_size']);
 281    @$this->init_variable($line,       $this->y_format[$set]['line'],       'none');
 282    @$this->init_variable($brushType,  $this->y_format[$set]['brush_type'],  $this->parameter['brush_type']);
 283    @$this->init_variable($brushSize,  $this->y_format[$set]['brush_size'],  $this->parameter['brush_size']);
 284    @$this->init_variable($bar,        $this->y_format[$set]['bar'],        'none');
 285    @$this->init_variable($barSize,    $this->y_format[$set]['bar_size'],    $this->parameter['bar_size']);
 286    @$this->init_variable($area,       $this->y_format[$set]['area'],       'none');
 287  
 288    $lastX = 0;
 289    $lastY = 'none';
 290    $fromX = 0;
 291    $fromY = 'none';
 292  
 293    //print "set $set<br />";
 294    //expand_pre($this->calculated['y_plot']);
 295  
 296    foreach ($this->x_data as $index => $x) {
 297      //print "index $index<br />";
 298      $thisY = $this->calculated['y_plot'][$set][$index];
 299      $thisX = $this->calculated['x_plot'][$index];
 300  
 301      //print "$thisX, $thisY <br />";
 302  
 303      if (($bar!='none') && (string)$thisY != 'none') {
 304          if ($relatedset = $this->offset_relation[$set]) {                               // Moodle
 305              $yoffset = $this->calculated['y_plot'][$relatedset][$index];                // Moodle
 306          } else {                                                                        // Moodle
 307              $yoffset = 0;                                                               // Moodle
 308          }                                                                               // Moodle
 309          //$this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set);           // Moodle
 310          $this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set, $yoffset);   // Moodle
 311      }
 312  
 313      if (($area!='none') && (((string)$lastY != 'none') && ((string)$thisY != 'none')))
 314        $this->area($lastX, $lastY, $thisX, $thisY, $area, $colour, $offset);
 315  
 316      if (($point!='none') && (string)$thisY != 'none') $this->plot($thisX, $thisY, $point, $pointSize, $colour, $offset);
 317  
 318      if (($line!='none') && ((string)$thisY != 'none')) {
 319        if ((string)$fromY != 'none')
 320          $this->line($fromX, $fromY, $thisX, $thisY, $line, $brushType, $brushSize, $colour, $offset);
 321  
 322        $fromY = $thisY; // start next line from here
 323        $fromX = $thisX; // ...
 324      } else {
 325        $fromY = 'none';
 326        $fromX = 'none';
 327      }
 328  
 329      $lastX = $thisX;
 330      $lastY = $thisY;
 331    }
 332  }
 333  
 334  function draw_data() {
 335    // cycle thru y data to be plotted
 336    // first check for drop shadows...
 337    foreach ($this->y_order as $order => $set) {
 338      @$this->init_variable($offset, $this->y_format[$set]['shadow_offset'], $this->parameter['shadow_offset']);
 339      @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
 340      if ($colour != 'none') $this->draw_set($order, $set, $offset);
 341  
 342    }
 343  
 344    // then draw data
 345    foreach ($this->y_order as $order => $set) {
 346      $this->draw_set($order, $set, 0);
 347    }
 348  }
 349  
 350  function draw_legend() {
 351    $position      = $this->parameter['legend'];
 352    if ($position == 'none') return; // abort if no border
 353  
 354    $borderColour  = $this->parameter['legend_border'];
 355    $offset        = $this->parameter['legend_offset'];
 356    $padding       = $this->parameter['legend_padding'];
 357    $height        = $this->calculated['legend']['boundary_box_all']['height'];
 358    $width         = $this->calculated['legend']['boundary_box_all']['width'];
 359    $graphTop      = $this->calculated['boundary_box']['top'];
 360    $graphBottom   = $this->calculated['boundary_box']['bottom'];
 361    $graphLeft     = $this->calculated['boundary_box']['left'];
 362    $graphRight    = $this->calculated['boundary_box']['right'];
 363    $outsideRight  = $this->calculated['outer_border']['right'];
 364    $outsideBottom = $this->calculated['outer_border']['bottom'];
 365    switch ($position) {
 366      case 'top-left':
 367        $top    = $graphTop  + $offset;
 368        $bottom = $graphTop  + $height + $offset;
 369        $left   = $graphLeft + $offset;
 370        $right  = $graphLeft + $width + $offset;
 371  
 372        break;
 373      case 'top-right':
 374        $top    = $graphTop   + $offset;
 375        $bottom = $graphTop   + $height + $offset;
 376        $left   = $graphRight - $width - $offset;
 377        $right  = $graphRight - $offset;
 378  
 379        break;
 380      case 'bottom-left':
 381        $top    = $graphBottom - $height - $offset;
 382        $bottom = $graphBottom - $offset;
 383        $left   = $graphLeft   + $offset;
 384        $right  = $graphLeft   + $width + $offset;
 385  
 386        break;
 387      case 'bottom-right':
 388        $top    = $graphBottom - $height - $offset;
 389        $bottom = $graphBottom - $offset;
 390        $left   = $graphRight  - $width - $offset;
 391        $right  = $graphRight  - $offset;
 392        break;
 393  
 394      case 'outside-top' :
 395        $top    = $graphTop;
 396        $bottom = $graphTop     + $height;
 397        $left   = $outsideRight - $width - $offset;
 398        $right  = $outsideRight - $offset;
 399        break;
 400  
 401      case 'outside-bottom' :
 402        $top    = $graphBottom  - $height;
 403        $bottom = $graphBottom;
 404        $left   = $outsideRight - $width - $offset;
 405        $right  = $outsideRight - $offset;
 406       break;
 407  
 408      case 'outside-left' :
 409        $top    = $outsideBottom - $height - $offset;
 410        $bottom = $outsideBottom - $offset;
 411        $left   = $graphLeft;
 412        $right  = $graphLeft     + $width;
 413       break;
 414  
 415      case 'outside-right' :
 416        $top    = $outsideBottom - $height - $offset;
 417        $bottom = $outsideBottom - $offset;
 418        $left   = $graphRight    - $width;
 419        $right  = $graphRight;
 420        break;
 421      default: // default is top left. no particular reason.
 422        $top    = $this->calculated['boundary_box']['top'];
 423        $bottom = $this->calculated['boundary_box']['top'] + $this->calculated['legend']['boundary_box_all']['height'];
 424        $left   = $this->calculated['boundary_box']['left'];
 425        $right  = $this->calculated['boundary_box']['right'] + $this->calculated['legend']['boundary_box_all']['width'];
 426  
 427  }
 428    // legend border
 429    if($borderColour!='none') $this->draw_rectangle(array('top' => $top,
 430                                                          'left' => $left,
 431                                                          'bottom' => $bottom,
 432                                                          'right' => $right), $this->parameter['legend_border'], 'box');
 433  
 434    // legend text
 435    $legendText = array('points' => $this->parameter['legend_size'],
 436                        'angle'  => 0,
 437                        'font'   => $this->parameter['legend_font'],
 438                        'colour' => $this->parameter['legend_colour']);
 439  
 440    $box = $this->calculated['legend']['boundary_box_max']['height']; // use max height for legend square size.
 441    $x = $left + $padding;
 442    $x_text = $x + $box * 2;
 443    $y = $top + $padding;
 444  
 445    foreach ($this->y_order as $set) {
 446      $legendText['text'] = $this->calculated['legend']['text'][$set];
 447      if ($legendText['text'] != 'none') {
 448        // if text exists then draw box and text
 449        $boxColour = $this->colour[$this->y_format[$set]['colour']];
 450  
 451        // draw box
 452        ImageFilledRectangle($this->image, $x, $y, $x + $box, $y + $box, $boxColour);
 453  
 454        // draw text
 455        $coords = array('x' => $x + $box * 2, 'y' => $y, 'reference' => 'top-left');
 456        $legendText['boundary_box'] = $this->calculated['legend']['boundary_box'][$set];
 457        $this->update_boundaryBox($legendText['boundary_box'], $coords);
 458        $this->print_TTF($legendText);
 459        $y += $padding + $box;
 460      }
 461    }
 462  
 463  }
 464  
 465  function draw_y_label_right() {
 466    if (!$this->parameter['y_label_right']) return;
 467    $x = $this->calculated['boundary_box']['right'] + $this->parameter['y_inner_padding'];
 468    if ($this->parameter['y_axis_text_right']) $x += $this->calculated['y_axis_right']['boundary_box_max']['width']
 469                                             + $this->calculated['right_inner_padding'];
 470    $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
 471  
 472    $label = $this->calculated['y_label_right'];
 473    $coords = array('x' => $x, 'y' => $y, 'reference' => 'left-center');
 474    $this->update_boundaryBox($label['boundary_box'], $coords);
 475    $this->print_TTF($label);
 476  }
 477  
 478  
 479  function draw_y_label_left() {
 480    if (!$this->parameter['y_label_left']) return;
 481    $x = $this->calculated['boundary_box']['left'] - $this->parameter['y_inner_padding'];
 482    if ($this->parameter['y_axis_text_left']) $x -= $this->calculated['y_axis_left']['boundary_box_max']['width']
 483                                             + $this->calculated['left_inner_padding'];
 484    $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
 485  
 486    $label = $this->calculated['y_label_left'];
 487    $coords = array('x' => $x, 'y' => $y, 'reference' => 'right-center');
 488    $this->update_boundaryBox($label['boundary_box'], $coords);
 489    $this->print_TTF($label);
 490  }
 491  
 492  function draw_title() {
 493    if (!$this->parameter['title']) return;
 494    //$y = $this->calculated['outside_border']['top'] + $this->parameter['outer_padding'];
 495    $y = $this->calculated['boundary_box']['top'] - $this->parameter['outer_padding'];
 496    $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
 497    $label = $this->calculated['title'];
 498    $coords = array('x' => $x, 'y' => $y, 'reference' => 'bottom-center');
 499    $this->update_boundaryBox($label['boundary_box'], $coords);
 500    $this->print_TTF($label);
 501  }
 502  
 503  function draw_x_label() {
 504    if (!$this->parameter['x_label']) return;
 505    $y = $this->calculated['boundary_box']['bottom'] + $this->parameter['x_inner_padding'];
 506    if ($this->parameter['x_axis_text']) $y += $this->calculated['x_axis']['boundary_box_max']['height']
 507                                            + $this->calculated['bottom_inner_padding'];
 508    $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
 509    $label = $this->calculated['x_label'];
 510    $coords = array('x' => $x, 'y' => $y, 'reference' => 'top-center');
 511    $this->update_boundaryBox($label['boundary_box'], $coords);
 512   $this->print_TTF($label);
 513  }
 514  
 515  function draw_zero_axis_left() {
 516    $colour = $this->parameter['zero_axis'];
 517    if ($colour == 'none') return;
 518    // draw zero axis on left hand side
 519    $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top']  + ($this->calculated['y_axis_left']['max'] * $this->calculated['y_axis_left']['factor']));
 520    ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
 521  }
 522  
 523  function draw_zero_axis_right() {
 524    $colour = $this->parameter['zero_axis'];
 525    if ($colour == 'none') return;
 526    // draw zero axis on right hand side
 527    $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top']  + ($this->calculated['y_axis_right']['max'] * $this->calculated['y_axis_right']['factor']));
 528    ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
 529  }
 530  
 531  function draw_x_axis() {
 532    $gridColour  = $this->colour[$this->parameter['grid_colour']];
 533    $tickColour  = $this->colour[$this->parameter['x_ticks_colour']];
 534    $axis_colour  = $this->parameter['axis_colour'];
 535    $xGrid       = $this->parameter['x_grid'];
 536    $gridTop     = $this->calculated['boundary_box']['top'];
 537    $gridBottom  = $this->calculated['boundary_box']['bottom'];
 538  
 539    if ($this->parameter['tick_length'] >= 0) {
 540      $tickTop     = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
 541      $tickBottom  = $this->calculated['boundary_box']['bottom'];
 542      $textBottom  = $tickBottom + $this->calculated['bottom_inner_padding'];
 543    } else {
 544      $tickTop     = $this->calculated['boundary_box']['bottom'];
 545      $tickBottom  = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
 546      $textBottom  = $tickBottom + $this->calculated['bottom_inner_padding'];
 547    }
 548  
 549    $axis_font    = $this->parameter['axis_font'];
 550    $axis_size    = $this->parameter['axis_size'];
 551    $axis_angle   = $this->parameter['x_axis_angle'];
 552  
 553    if ($axis_angle == 0)  $reference = 'top-center';
 554    if ($axis_angle > 0)   $reference = 'top-right';
 555    if ($axis_angle < 0)   $reference = 'top-left';
 556    if ($axis_angle == 90) $reference = 'top-center';
 557  
 558    //generic tag information. applies to all axis text.
 559    $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
 560  
 561    foreach ($this->calculated['x_axis']['tick_x'] as $set => $tickX) {
 562      // draw x grid if colour specified
 563      if ($xGrid != 'none') {
 564        switch ($xGrid) {
 565          case 'line':
 566            ImageLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
 567            break;
 568           case 'dash':
 569            ImageDashedLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
 570            break;
 571        }
 572      }
 573  
 574      if ($this->parameter['x_axis_text'] && !($set % $this->parameter['x_axis_text'])) { // test if tick should be displayed
 575        // draw tick
 576        if ($tickColour != 'none')
 577          ImageLine($this->image, round($tickX), round($tickTop), round($tickX), round($tickBottom), $tickColour);
 578  
 579        // draw axis text
 580        $coords = array('x' => $tickX, 'y' => $textBottom, 'reference' => $reference);
 581        $axisTag['text'] = $this->calculated['x_axis']['text'][$set];
 582        $axisTag['boundary_box'] = $this->calculated['x_axis']['boundary_box'][$set];
 583        $this->update_boundaryBox($axisTag['boundary_box'], $coords);
 584        $this->print_TTF($axisTag);
 585      }
 586    }
 587  }
 588  
 589  function draw_y_axis() {
 590    $gridColour  = $this->colour[$this->parameter['grid_colour']];
 591    $tickColour  = $this->colour[$this->parameter['y_ticks_colour']];
 592    $axis_colour  = $this->parameter['axis_colour'];
 593    $yGrid       = $this->parameter['y_grid'];
 594    $gridLeft    = $this->calculated['boundary_box']['left'];
 595    $gridRight   = $this->calculated['boundary_box']['right'];
 596  
 597    // axis font information
 598    $axis_font    = $this->parameter['axis_font'];
 599    $axis_size    = $this->parameter['axis_size'];
 600    $axis_angle   = $this->parameter['y_axis_angle'];
 601    $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
 602  
 603  
 604    if ($this->calculated['y_axis_left']['has_data']) {
 605      // LEFT HAND SIDE
 606      // left and right coords for ticks
 607      if ($this->parameter['tick_length'] >= 0) {
 608        $tickLeft     = $this->calculated['boundary_box']['left'];
 609        $tickRight    = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
 610      } else {
 611        $tickLeft     = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
 612        $tickRight    = $this->calculated['boundary_box']['left'];
 613      }
 614      $textRight      = $tickLeft - $this->calculated['left_inner_padding'];
 615  
 616      if ($axis_angle == 0)  $reference = 'right-center';
 617      if ($axis_angle > 0)   $reference = 'right-top';
 618      if ($axis_angle < 0)   $reference = 'right-bottom';
 619      if ($axis_angle == 90) $reference = 'right-center';
 620  
 621      foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
 622        // draw y grid if colour specified
 623        if ($yGrid != 'none') {
 624          switch ($yGrid) {
 625            case 'line':
 626              ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
 627              break;
 628             case 'dash':
 629              ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
 630              break;
 631          }
 632        }
 633  
 634        // y axis text
 635        if ($this->parameter['y_axis_text_left'] && !($set % $this->parameter['y_axis_text_left'])) { // test if tick should be displayed
 636          // draw tick
 637          if ($tickColour != 'none')
 638            ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
 639  
 640          // draw axis text...
 641          $coords = array('x' => $textRight, 'y' => $tickY, 'reference' => $reference);
 642          $axisTag['text'] = $this->calculated['y_axis_left']['text'][$set];
 643          $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
 644          $this->update_boundaryBox($axisTag['boundary_box'], $coords);
 645          $this->print_TTF($axisTag);
 646        }
 647      }
 648    }
 649  
 650    if ($this->calculated['y_axis_right']['has_data']) {
 651      // RIGHT HAND SIDE
 652      // left and right coords for ticks
 653      if ($this->parameter['tick_length'] >= 0) {
 654        $tickLeft     = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
 655        $tickRight    = $this->calculated['boundary_box']['right'];
 656      } else {
 657        $tickLeft     = $this->calculated['boundary_box']['right'];
 658        $tickRight    = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
 659      }
 660      $textLeft       = $tickRight+ $this->calculated['left_inner_padding'];
 661  
 662      if ($axis_angle == 0)  $reference = 'left-center';
 663      if ($axis_angle > 0)   $reference = 'left-bottom';
 664      if ($axis_angle < 0)   $reference = 'left-top';
 665      if ($axis_angle == 90) $reference = 'left-center';
 666  
 667      foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
 668        if (!$this->calculated['y_axis_left']['has_data'] && $yGrid != 'none') { // draw grid if not drawn already (above)
 669          switch ($yGrid) {
 670            case 'line':
 671              ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
 672              break;
 673             case 'dash':
 674              ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
 675              break;
 676          }
 677        }
 678  
 679        if ($this->parameter['y_axis_text_right'] && !($set % $this->parameter['y_axis_text_right'])) { // test if tick should be displayed
 680          // draw tick
 681          if ($tickColour != 'none')
 682            ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
 683  
 684          // draw axis text...
 685          $coords = array('x' => $textLeft, 'y' => $tickY, 'reference' => $reference);
 686          $axisTag['text'] = $this->calculated['y_axis_right']['text'][$set];
 687          $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
 688          $this->update_boundaryBox($axisTag['boundary_box'], $coords);
 689          $this->print_TTF($axisTag);
 690        }
 691      }
 692    }
 693  }
 694  
 695  function init_data() {
 696    $this->calculated['y_plot'] = array(); // array to hold pixel plotting coords for y axis
 697    $height = $this->calculated['boundary_box']['bottom'] - $this->calculated['boundary_box']['top'];
 698    $width  = $this->calculated['boundary_box']['right'] - $this->calculated['boundary_box']['left'];
 699  
 700    // calculate pixel steps between axis ticks.
 701    $this->calculated['y_axis']['step'] = $height / ($this->parameter['y_axis_gridlines'] - 1);
 702  
 703    // calculate x ticks spacing taking into account x offset for ticks.
 704    $extraTick  = 2 * $this->parameter['x_offset']; // extra tick to account for padding
 705    $numTicks = $this->calculated['x_axis']['num_ticks'] - 1;    // number of x ticks
 706  
 707    // Hack by rodger to avoid division by zero, see bug 1231
 708    if ($numTicks==0) $numTicks=1;
 709  
 710    $this->calculated['x_axis']['step'] = $width / ($numTicks + $extraTick);
 711    $widthPlot = $width - ($this->calculated['x_axis']['step'] * $extraTick);
 712    $this->calculated['x_axis']['step'] = $widthPlot / $numTicks;
 713  
 714    //calculate factor for transforming x,y physical coords to logical coords for right hand y_axis.
 715    $y_range = $this->calculated['y_axis_right']['max'] - $this->calculated['y_axis_right']['min'];
 716    $y_range = ($y_range ? $y_range : 1);
 717    $this->calculated['y_axis_right']['factor'] = $height / $y_range;
 718  
 719    //calculate factor for transforming x,y physical coords to logical coords for left hand axis.
 720    $yRange = $this->calculated['y_axis_left']['max'] - $this->calculated['y_axis_left']['min'];
 721    $yRange = ($yRange ? $yRange : 1);
 722    $this->calculated['y_axis_left']['factor'] = $height / $yRange;
 723    if ($this->parameter['x_axis_gridlines'] != 'auto') {
 724      $xRange = $this->calculated['x_axis']['max'] - $this->calculated['x_axis']['min'];
 725      $xRange = ($xRange ? $xRange : 1);
 726      $this->calculated['x_axis']['factor'] = $widthPlot / $xRange;
 727    }
 728  
 729    //expand_pre($this->calculated['boundary_box']);
 730    // cycle thru all data sets...
 731    $this->calculated['num_bars'] = 0;
 732    foreach ($this->y_order as $order => $set) {
 733      // determine how many bars there are
 734      if (isset($this->y_format[$set]['bar']) && ($this->y_format[$set]['bar'] != 'none')) {
 735        $this->calculated['bar_offset_index'][$set] = $this->calculated['num_bars']; // index to relate bar with data set.
 736        $this->calculated['num_bars']++;
 737      }
 738  
 739      // calculate y coords for plotting data
 740      foreach ($this->x_data as $index => $x) {
 741        $this->calculated['y_plot'][$set][$index] = $this->y_data[$set][$index];
 742  
 743        if ((string)$this->y_data[$set][$index] != 'none') {
 744  
 745          if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
 746            $this->calculated['y_plot'][$set][$index] =
 747              round(($this->y_data[$set][$index] - $this->calculated['y_axis_right']['min'])
 748                * $this->calculated['y_axis_right']['factor']);
 749          } else {
 750            //print "$set $index<br />";
 751            $this->calculated['y_plot'][$set][$index] =
 752              round(($this->y_data[$set][$index] - $this->calculated['y_axis_left']['min'])
 753                * $this->calculated['y_axis_left']['factor']);
 754          }
 755  
 756        }
 757      }
 758    }
 759    //print "factor ".$this->calculated['x_axis']['factor']."<br />";
 760    //expand_pre($this->calculated['x_plot']);
 761  
 762    // calculate bar parameters if bars are to be drawn.
 763    if ($this->calculated['num_bars']) {
 764      $xStep       = $this->calculated['x_axis']['step'];
 765      $totalWidth  = $this->calculated['x_axis']['step'] - $this->parameter['bar_spacing'];
 766      $barWidth    = $totalWidth / $this->calculated['num_bars'];
 767  
 768      $barX = ($barWidth - $totalWidth) / 2; // starting x offset
 769      for ($i=0; $i < $this->calculated['num_bars']; $i++) {
 770        $this->calculated['bar_offset_x'][$i] = $barX;
 771        $barX += $barWidth; // add width of bar to x offset.
 772      }
 773      $this->calculated['bar_width'] = $barWidth;
 774    }
 775  
 776  
 777  }
 778  
 779  function init_x_ticks() {
 780    // get coords for x axis ticks and data plots
 781    //$xGrid       = $this->parameter['x_grid'];
 782    $xStep       = $this->calculated['x_axis']['step'];
 783    $ticksOffset = $this->parameter['x_offset']; // where to start drawing ticks relative to y axis.
 784    $gridLeft    = $this->calculated['boundary_box']['left'] + ($xStep * $ticksOffset); // grid x start
 785    $tickX       = $gridLeft; // tick x coord
 786  
 787    foreach ($this->calculated['x_axis']['text'] as $set => $value) {
 788      //print "index: $set<br />";
 789      // x tick value
 790      $this->calculated['x_axis']['tick_x'][$set] = $tickX;
 791      // if num ticks is auto then x plot value is same as x  tick
 792      if ($this->parameter['x_axis_gridlines'] == 'auto') $this->calculated['x_plot'][$set] = round($tickX);
 793      //print $this->calculated['x_plot'][$set].'<br />';
 794      $tickX += $xStep;
 795    }
 796  
 797    //print "xStep: $xStep <br />";
 798    // if numeric x axis then calculate x coords for each data point. this is seperate from x ticks.
 799    $gridX = $gridLeft;
 800    if (empty($this->calculated['x_axis']['factor'])) {
 801        $this->calculated['x_axis']['factor'] = 0;
 802    }
 803    if (empty($this->calculated['x_axis']['min'])) {
 804        $this->calculated['x_axis']['min'] = 0;
 805    }
 806    $factor = $this->calculated['x_axis']['factor'];
 807    $min = $this->calculated['x_axis']['min'];
 808  
 809    if ($this->parameter['x_axis_gridlines'] != 'auto') {
 810      foreach ($this->x_data as $index => $x) {
 811        //print "index: $index, x: $x<br />";
 812        $offset = $x - $this->calculated['x_axis']['min'];
 813  
 814        //$gridX = ($offset * $this->calculated['x_axis']['factor']);
 815        //print "offset: $offset <br />";
 816        //$this->calculated['x_plot'][$set] = $gridLeft + ($offset * $this->calculated['x_axis']['factor']);
 817  
 818        $this->calculated['x_plot'][$index] = $gridLeft + ($x - $min) * $factor;
 819  
 820        //print $this->calculated['x_plot'][$set].'<br />';
 821      }
 822    }
 823    //expand_pre($this->calculated['boundary_box']);
 824    //print "factor ".$this->calculated['x_axis']['factor']."<br />";
 825    //expand_pre($this->calculated['x_plot']);
 826  }
 827  
 828  function init_y_ticks() {
 829    // get coords for y axis ticks
 830  
 831    $yStep      = $this->calculated['y_axis']['step'];
 832    $gridBottom = $this->calculated['boundary_box']['bottom'];
 833    $tickY      = $gridBottom; // tick y coord
 834  
 835    for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) {
 836      $this->calculated['y_axis']['tick_y'][$i] = $tickY;
 837      $tickY   -= $yStep;
 838    }
 839  
 840  }
 841  
 842  function init_labels() {
 843    if ($this->parameter['title']) {
 844      $size = $this->get_boundaryBox(
 845        array('points' => $this->parameter['title_size'],
 846              'angle'  => 0,
 847              'font'   => $this->parameter['title_font'],
 848              'text'   => $this->parameter['title']));
 849      $this->calculated['title']['boundary_box']  = $size;
 850      $this->calculated['title']['text']         = $this->parameter['title'];
 851      $this->calculated['title']['font']         = $this->parameter['title_font'];
 852      $this->calculated['title']['points']       = $this->parameter['title_size'];
 853      $this->calculated['title']['colour']       = $this->parameter['title_colour'];
 854      $this->calculated['title']['angle']        = 0;
 855  
 856      $this->calculated['boundary_box']['top'] += $size['height'] + $this->parameter['outer_padding'];
 857      //$this->calculated['boundary_box']['top'] += $size['height'];
 858  
 859    } else $this->calculated['title']['boundary_box'] = $this->get_null_size();
 860  
 861    if ($this->parameter['y_label_left']) {
 862      $this->calculated['y_label_left']['text']    = $this->parameter['y_label_left'];
 863      $this->calculated['y_label_left']['angle']   = $this->parameter['y_label_angle'];
 864      $this->calculated['y_label_left']['font']    = $this->parameter['label_font'];
 865      $this->calculated['y_label_left']['points']  = $this->parameter['label_size'];
 866      $this->calculated['y_label_left']['colour']  = $this->parameter['label_colour'];
 867  
 868      $size = $this->get_boundaryBox($this->calculated['y_label_left']);
 869      $this->calculated['y_label_left']['boundary_box']  = $size;
 870      //$this->calculated['boundary_box']['left'] += $size['width'] + $this->parameter['inner_padding'];
 871      $this->calculated['boundary_box']['left'] += $size['width'];
 872  
 873    } else $this->calculated['y_label_left']['boundary_box'] = $this->get_null_size();
 874  
 875    if ($this->parameter['y_label_right']) {
 876      $this->calculated['y_label_right']['text']    = $this->parameter['y_label_right'];
 877      $this->calculated['y_label_right']['angle']   = $this->parameter['y_label_angle'];
 878      $this->calculated['y_label_right']['font']    = $this->parameter['label_font'];
 879      $this->calculated['y_label_right']['points']  = $this->parameter['label_size'];
 880      $this->calculated['y_label_right']['colour']  = $this->parameter['label_colour'];
 881  
 882      $size = $this->get_boundaryBox($this->calculated['y_label_right']);
 883      $this->calculated['y_label_right']['boundary_box']  = $size;
 884      //$this->calculated['boundary_box']['right'] -= $size['width'] + $this->parameter['inner_padding'];
 885      $this->calculated['boundary_box']['right'] -= $size['width'];
 886  
 887    } else $this->calculated['y_label_right']['boundary_box'] = $this->get_null_size();
 888  
 889    if ($this->parameter['x_label']) {
 890      $this->calculated['x_label']['text']         = $this->parameter['x_label'];
 891      $this->calculated['x_label']['angle']        = $this->parameter['x_label_angle'];
 892      $this->calculated['x_label']['font']         = $this->parameter['label_font'];
 893      $this->calculated['x_label']['points']       = $this->parameter['label_size'];
 894      $this->calculated['x_label']['colour']       = $this->parameter['label_colour'];
 895  
 896      $size = $this->get_boundaryBox($this->calculated['x_label']);
 897      $this->calculated['x_label']['boundary_box']  = $size;
 898      //$this->calculated['boundary_box']['bottom'] -= $size['height'] + $this->parameter['inner_padding'];
 899      $this->calculated['boundary_box']['bottom'] -= $size['height'];
 900  
 901    } else $this->calculated['x_label']['boundary_box'] = $this->get_null_size();
 902  
 903  }
 904  
 905  
 906  function init_legend() {
 907    $this->calculated['legend'] = array(); // array to hold calculated values for legend.
 908    //$this->calculated['legend']['boundary_box_max'] = array('height' => 0, 'width' => 0);
 909    $this->calculated['legend']['boundary_box_max'] = $this->get_null_size();
 910    if ($this->parameter['legend'] == 'none') return;
 911  
 912    $position = $this->parameter['legend'];
 913    $numSets = 0; // number of data sets with legends.
 914    $sumTextHeight = 0; // total of height of all legend text items.
 915    $width = 0;
 916    $height = 0;
 917  
 918    foreach ($this->y_order as $set) {
 919     $text = isset($this->y_format[$set]['legend']) ? $this->y_format[$set]['legend'] : 'none';
 920     $size = $this->get_boundaryBox(
 921       array('points' => $this->parameter['legend_size'],
 922             'angle'  => 0,
 923             'font'   => $this->parameter['legend_font'],
 924             'text'   => $text));
 925  
 926     $this->calculated['legend']['boundary_box'][$set] = $size;
 927     $this->calculated['legend']['text'][$set]        = $text;
 928     //$this->calculated['legend']['font'][$set]        = $this->parameter['legend_font'];
 929     //$this->calculated['legend']['points'][$set]      = $this->parameter['legend_size'];
 930     //$this->calculated['legend']['angle'][$set]       = 0;
 931  
 932     if ($text && $text!='none') {
 933       $numSets++;
 934       $sumTextHeight += $size['height'];
 935     }
 936  
 937     if ($size['width'] > $this->calculated['legend']['boundary_box_max']['width'])
 938       $this->calculated['legend']['boundary_box_max'] = $size;
 939    }
 940  
 941    $offset  = $this->parameter['legend_offset'];  // offset in pixels of legend box from graph border.
 942    $padding = $this->parameter['legend_padding']; // padding in pixels around legend text.
 943    $textWidth = $this->calculated['legend']['boundary_box_max']['width']; // width of largest legend item.
 944    $textHeight = $this->calculated['legend']['boundary_box_max']['height']; // use height as size to use for colour square in legend.
 945    $width = $padding * 2 + $textWidth + $textHeight * 2;  // left and right padding + maximum text width + space for square
 946    $height = $padding * ($numSets + 1) + $sumTextHeight; // top and bottom padding + padding between text + text.
 947  
 948  
 949    $this->calculated['legend']['boundary_box_all'] = array('width'     => $width,
 950                                                          'height'    => $height,
 951                                                          'offset'    => $offset,
 952                                                          'reference' => $position);
 953  
 954    switch ($position) { // move in right or bottom if legend is outside data plotting area.
 955      case 'outside-top' :
 956        $this->calculated['boundary_box']['right']      -= $offset + $width; // move in right hand side
 957        break;
 958  
 959      case 'outside-bottom' :
 960        $this->calculated['boundary_box']['right']      -= $offset + $width; // move in right hand side
 961        break;
 962  
 963      case 'outside-left' :
 964        $this->calculated['boundary_box']['bottom']      -= $offset + $height; // move in right hand side
 965        break;
 966  
 967      case 'outside-right' :
 968        $this->calculated['boundary_box']['bottom']      -= $offset + $height; // move in right hand side
 969        break;
 970    }
 971  }
 972  
 973  function init_y_axis() {
 974    $this->calculated['y_axis_left'] = array(); // array to hold calculated values for y_axis on left.
 975    $this->calculated['y_axis_left']['boundary_box_max'] = $this->get_null_size();
 976    $this->calculated['y_axis_right'] = array(); // array to hold calculated values for y_axis on right.
 977    $this->calculated['y_axis_right']['boundary_box_max'] = $this->get_null_size();
 978  
 979    $axis_font       = $this->parameter['axis_font'];
 980    $axis_size       = $this->parameter['axis_size'];
 981    $axis_colour     = $this->parameter['axis_colour'];
 982    $axis_angle      = $this->parameter['y_axis_angle'];
 983    $y_tick_labels   = $this->y_tick_labels;
 984    
 985    $this->calculated['y_axis_left']['has_data'] = FALSE;
 986    $this->calculated['y_axis_right']['has_data'] = FALSE;
 987  
 988    // find min and max y values.
 989    $minLeft = $this->parameter['y_min_left'];
 990    $maxLeft = $this->parameter['y_max_left'];
 991    $minRight = $this->parameter['y_min_right'];
 992    $maxRight = $this->parameter['y_max_right'];
 993    $dataLeft = array();
 994    $dataRight = array();
 995    foreach ($this->y_order as $order => $set) {
 996      if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
 997        $this->calculated['y_axis_right']['has_data'] = TRUE;
 998        $dataRight = array_merge($dataRight, $this->y_data[$set]);
 999      } else {
1000        $this->calculated['y_axis_left']['has_data'] = TRUE;
1001        $dataLeft = array_merge($dataLeft, $this->y_data[$set]);
1002      }
1003    }
1004    $dataLeftRange = $this->find_range($dataLeft, $minLeft, $maxLeft, $this->parameter['y_resolution_left']);
1005    $dataRightRange = $this->find_range($dataRight, $minRight, $maxRight, $this->parameter['y_resolution_right']);
1006    $minLeft = $dataLeftRange['min'];
1007    $maxLeft = $dataLeftRange['max'];
1008    $minRight = $dataRightRange['min'];
1009    $maxRight = $dataRightRange['max'];
1010  
1011    $this->calculated['y_axis_left']['min']  = $minLeft;
1012    $this->calculated['y_axis_left']['max']  = $maxLeft;
1013    $this->calculated['y_axis_right']['min'] = $minRight;
1014    $this->calculated['y_axis_right']['max'] = $maxRight;
1015  
1016    $stepLeft = ($maxLeft - $minLeft) / ($this->parameter['y_axis_gridlines'] - 1);
1017    $startLeft = $minLeft;
1018    $step_right = ($maxRight - $minRight) / ($this->parameter['y_axis_gridlines'] - 1);
1019    $start_right = $minRight;
1020  
1021    if ($this->parameter['y_axis_text_left']) {
1022      for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1023        // left y axis
1024        if ($y_tick_labels) {
1025          $value = $y_tick_labels[$i];
1026        } else {
1027          $value = number_format($startLeft, $this->parameter['y_decimal_left'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1028        }
1029        $this->calculated['y_axis_left']['data'][$i]  = $startLeft;
1030        $this->calculated['y_axis_left']['text'][$i]  = $value; // text is formatted raw data
1031  
1032        $size = $this->get_boundaryBox(
1033          array('points' => $axis_size,
1034                'font'   => $axis_font,
1035                'angle'  => $axis_angle,
1036                'colour' => $axis_colour,
1037                'text'   => $value));
1038        $this->calculated['y_axis_left']['boundary_box'][$i] = $size;
1039  
1040        if ($size['height'] > $this->calculated['y_axis_left']['boundary_box_max']['height'])
1041          $this->calculated['y_axis_left']['boundary_box_max']['height'] = $size['height'];
1042        if ($size['width'] > $this->calculated['y_axis_left']['boundary_box_max']['width'])
1043          $this->calculated['y_axis_left']['boundary_box_max']['width'] = $size['width'];
1044  
1045        $startLeft += $stepLeft;
1046      }
1047      $this->calculated['boundary_box']['left'] += $this->calculated['y_axis_left']['boundary_box_max']['width']
1048                                                  + $this->parameter['y_inner_padding'];
1049    }
1050  
1051    if ($this->parameter['y_axis_text_right']) {
1052      for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
1053        // right y axis
1054        $value = number_format($start_right, $this->parameter['y_decimal_right'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1055        $this->calculated['y_axis_right']['data'][$i]  = $start_right;
1056        $this->calculated['y_axis_right']['text'][$i]  = $value; // text is formatted raw data
1057        $size = $this->get_boundaryBox(
1058          array('points' => $axis_size,
1059                'font'   => $axis_font,
1060                'angle'  => $axis_angle,
1061                'colour' => $axis_colour,
1062                'text'   => $value));
1063        $this->calculated['y_axis_right']['boundary_box'][$i] = $size;
1064  
1065        if ($size['height'] > $this->calculated['y_axis_right']['boundary_box_max']['height'])
1066          $this->calculated['y_axis_right']['boundary_box_max'] = $size;
1067        if ($size['width'] > $this->calculated['y_axis_right']['boundary_box_max']['width'])
1068          $this->calculated['y_axis_right']['boundary_box_max']['width'] = $size['width'];
1069  
1070        $start_right += $step_right;
1071      }
1072      $this->calculated['boundary_box']['right'] -= $this->calculated['y_axis_right']['boundary_box_max']['width']
1073                                                  + $this->parameter['y_inner_padding'];
1074    }
1075  }
1076  
1077  function init_x_axis() {
1078    $this->calculated['x_axis'] = array(); // array to hold calculated values for x_axis.
1079    $this->calculated['x_axis']['boundary_box_max'] = array('height' => 0, 'width' => 0);
1080  
1081    $axis_font       = $this->parameter['axis_font'];
1082    $axis_size       = $this->parameter['axis_size'];
1083    $axis_colour     = $this->parameter['axis_colour'];
1084    $axis_angle      = $this->parameter['x_axis_angle'];
1085  
1086    // check whether to treat x axis as numeric
1087    if ($this->parameter['x_axis_gridlines'] == 'auto') { // auto means text based x_axis, not numeric...
1088      $this->calculated['x_axis']['num_ticks'] = sizeof($this->x_data);
1089        $data = $this->x_data;
1090        for ($i=0; $i < $this->calculated['x_axis']['num_ticks']; $i++) {
1091          $value = array_shift($data); // grab value from begin of array
1092          $this->calculated['x_axis']['data'][$i]  = $value;
1093          $this->calculated['x_axis']['text'][$i]  = $value; // raw data and text are both the same in this case
1094          $size = $this->get_boundaryBox(
1095            array('points' => $axis_size,
1096                  'font'   => $axis_font,
1097                  'angle'  => $axis_angle,
1098                  'colour' => $axis_colour,
1099                  'text'   => $value));
1100          $this->calculated['x_axis']['boundary_box'][$i] = $size;
1101          if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1102            $this->calculated['x_axis']['boundary_box_max'] = $size;
1103        }
1104  
1105    } else { // x axis is numeric so find max min values...
1106      $this->calculated['x_axis']['num_ticks'] = $this->parameter['x_axis_gridlines'];
1107  
1108      $min = $this->parameter['x_min'];
1109      $max = $this->parameter['x_max'];
1110      $data = array();
1111      $data = $this->find_range($this->x_data, $min, $max, $this->parameter['x_resolution']);
1112      $min = $data['min'];
1113      $max = $data['max'];
1114      $this->calculated['x_axis']['min'] = $min;
1115      $this->calculated['x_axis']['max'] = $max;
1116  
1117      $step = ($max - $min) / ($this->calculated['x_axis']['num_ticks'] - 1);
1118      $start = $min;
1119  
1120      for ($i = 0; $i < $this->calculated['x_axis']['num_ticks']; $i++) { // calculate x axis text sizes
1121        $value = number_format($start, $this->parameter['xDecimal'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
1122        $this->calculated['x_axis']['data'][$i]  = $start;
1123        $this->calculated['x_axis']['text'][$i]  = $value; // text is formatted raw data
1124  
1125        $size = $this->get_boundaryBox(
1126          array('points' => $axis_size,
1127                'font'   => $axis_font,
1128                'angle'  => $axis_angle,
1129                'colour' => $axis_colour,
1130                'text'   => $value));
1131        $this->calculated['x_axis']['boundary_box'][$i] = $size;
1132  
1133        if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
1134          $this->calculated['x_axis']['boundary_box_max'] = $size;
1135  
1136        $start += $step;
1137      }
1138    }
1139    if ($this->parameter['x_axis_text'])
1140      $this->calculated['boundary_box']['bottom'] -= $this->calculated['x_axis']['boundary_box_max']['height']
1141                                                    + $this->parameter['x_inner_padding'];
1142  }
1143  
1144  // find max and min values for a data array given the resolution.
1145  function find_range($data, $min, $max, $resolution) {
1146    if (sizeof($data) == 0 ) return array('min' => 0, 'max' => 0);
1147    foreach ($data as $key => $value) {
1148      if ($value=='none') continue;
1149      if ($value > $max) $max = $value;
1150      if ($value < $min) $min = $value;
1151    }
1152  
1153    if ($max == 0) {
1154      $factor = 1;
1155    } else {
1156      if ($max < 0) $factor = - pow(10, (floor(log10(abs($max))) + $resolution) );
1157      else $factor = pow(10, (floor(log10(abs($max))) - $resolution) );
1158    }
1159    $factor = round($factor * 1000.0) / 1000.0; // To avoid some wierd rounding errors (Moodle)
1160  
1161    $max = $factor * @ceil($max / $factor);
1162    $min = $factor * @floor($min / $factor);
1163  
1164    //print "max=$max, min=$min<br />";
1165  
1166    return array('min' => $min, 'max' => $max);
1167  }
1168  
1169  function graph() {
1170    if (func_num_args() == 2) {
1171      $this->parameter['width']  = func_get_arg(0);
1172      $this->parameter['height'] = func_get_arg(1);
1173    }
1174    //$this->boundaryBox  = array(
1175    $this->calculated['boundary_box'] = array(
1176      'left'      =>  0,
1177      'top'       =>  0,
1178      'right'     =>  $this->parameter['width'] - 1,
1179      'bottom'    =>  $this->parameter['height'] - 1);
1180  
1181    $this->init_colours();
1182  
1183    //ImageColorTransparent($this->image, $this->colour['white']); // colour for transparency
1184  }
1185  
1186  function print_TTF($message) {
1187    $points    = $message['points'];
1188    $angle     = $message['angle'];
1189    $text      = $message['text'];
1190    $colour    = $this->colour[$message['colour']];
1191    $font      = $this->parameter['path_to_fonts'].$message['font'];
1192  
1193    $x         = $message['boundary_box']['x'];
1194    $y         = $message['boundary_box']['y'];
1195    $offsetX   = $message['boundary_box']['offsetX'];
1196    $offsetY   = $message['boundary_box']['offsetY'];
1197    $height    = $message['boundary_box']['height'];
1198    $width     = $message['boundary_box']['width'];
1199    $reference = $message['boundary_box']['reference'];
1200  
1201    switch ($reference) {
1202      case 'top-left':
1203      case 'left-top':
1204        $y += $height - $offsetY;
1205        //$y += $offsetY;
1206        $x += $offsetX;
1207        break;
1208      case 'left-center':
1209        $y += ($height / 2) - $offsetY;
1210        $x += $offsetX;
1211        break;
1212      case 'left-bottom':
1213        $y -= $offsetY;
1214        $x += $offsetX;
1215       break;
1216      case 'top-center':
1217        $y += $height - $offsetY;
1218        $x -= ($width / 2) - $offsetX;
1219       break;
1220      case 'top-right':
1221      case 'right-top':
1222        $y += $height - $offsetY;
1223        $x -= $width  - $offsetX;
1224        break;
1225      case 'right-center':
1226        $y += ($height / 2) - $offsetY;
1227        $x -= $width  - $offsetX;
1228        break;
1229      case 'right-bottom':
1230        $y -= $offsetY;
1231        $x -= $width  - $offsetX;
1232        break;
1233      case 'bottom-center':
1234        $y -= $offsetY;
1235        $x -= ($width / 2) - $offsetX;
1236       break;
1237      default:
1238        $y = 0;
1239        $x = 0;
1240        break;
1241    }
1242    // start of Moodle addition
1243    $textlib = textlib_get_instance();
1244    $text = $textlib->utf8_to_entities($text, true, true); //does not work with hex entities!
1245    // end of Moodle addition
1246    ImageTTFText($this->image, $points, $angle, $x, $y, $colour, $font, $text);
1247  }
1248  
1249  // move boundaryBox to coordinates specified
1250  function update_boundaryBox(&$boundaryBox, $coords) {
1251    $width      = $boundaryBox['width'];
1252    $height     = $boundaryBox['height'];
1253    $x          = $coords['x'];
1254    $y          = $coords['y'];
1255    $reference  = $coords['reference'];
1256    switch ($reference) {
1257      case 'top-left':
1258      case 'left-top':
1259        $top    = $y;
1260        $bottom = $y + $height;
1261        $left   = $x;
1262        $right  = $x + $width;
1263        break;
1264      case 'left-center':
1265        $top    = $y - ($height / 2);
1266        $bottom = $y + ($height / 2);
1267        $left   = $x;
1268        $right  = $x + $width;
1269        break;
1270      case 'left-bottom':
1271        $top    = $y - $height;
1272        $bottom = $y;
1273        $left   = $x;
1274        $right  = $x + $width;
1275        break;
1276      case 'top-center':
1277        $top    = $y;
1278        $bottom = $y + $height;
1279        $left   = $x - ($width / 2);
1280        $right  = $x + ($width / 2);
1281        break;
1282      case 'right-top':
1283      case 'top-right':
1284        $top    = $y;
1285        $bottom = $y + $height;
1286        $left   = $x - $width;
1287        $right  = $x;
1288        break;
1289      case 'right-center':
1290        $top    = $y - ($height / 2);
1291        $bottom = $y + ($height / 2);
1292        $left   = $x - $width;
1293        $right  = $x;
1294        break;
1295      case 'bottom=right':
1296      case 'right-bottom':
1297        $top    = $y - $height;
1298        $bottom = $y;
1299        $left   = $x - $width;
1300        $right  = $x;
1301        break;
1302      default:
1303        $top    = 0;
1304        $bottom = $height;
1305        $left   = 0;
1306        $right  = $width;
1307        break;
1308    }
1309  
1310    $boundaryBox = array_merge($boundaryBox, array('top'       => $top,
1311                                                   'bottom'    => $bottom,
1312                                                   'left'      => $left,
1313                                                   'right'     => $right,
1314                                                   'x'         => $x,
1315                                                   'y'         => $y,
1316                                                   'reference' => $reference));
1317  }
1318  
1319  function get_null_size() {
1320    return array('width'      => 0,
1321                 'height'     => 0,
1322                 'offsetX'    => 0,
1323                 'offsetY'    => 0,
1324                 //'fontHeight' => 0
1325                 );
1326  }
1327  
1328  function get_boundaryBox($message) {
1329    $points  = $message['points'];
1330    $angle   = $message['angle'];
1331    $font    = $this->parameter['path_to_fonts'].$message['font'];
1332    $text    = $message['text'];
1333  
1334    //print ('get_boundaryBox');
1335    //expandPre($message);
1336  
1337    // get font size
1338    $bounds = ImageTTFBBox($points, $angle, $font, "W");
1339    if ($angle < 0) {
1340      $fontHeight = abs($bounds[7]-$bounds[1]);
1341    } else if ($angle > 0) {
1342      $fontHeight = abs($bounds[1]-$bounds[7]);
1343    } else {
1344      $fontHeight = abs($bounds[7]-$bounds[1]);
1345    }
1346  
1347    // get boundary box and offsets for printing at an angle
1348    // start of Moodle addition
1349    $textlib = textlib_get_instance();
1350    $text = $textlib->utf8_to_entities($text, true, true); //gd does not work with hex entities!
1351    // end of Moodle addition
1352    $bounds = ImageTTFBBox($points, $angle, $font, $text);
1353  
1354    if ($angle < 0) {
1355      $width = abs($bounds[4]-$bounds[0]);
1356      $height = abs($bounds[3]-$bounds[7]);
1357      $offsetY = abs($bounds[3]-$bounds[1]);
1358      $offsetX = 0;
1359  
1360    } else if ($angle > 0) {
1361      $width = abs($bounds[2]-$bounds[6]);
1362      $height = abs($bounds[1]-$bounds[5]);
1363      $offsetY = 0;
1364      $offsetX = abs($bounds[0]-$bounds[6]);
1365  
1366    } else {
1367      $width = abs($bounds[4]-$bounds[6]);
1368      $height = abs($bounds[7]-$bounds[1]);
1369      $offsetY = 0;
1370      $offsetX = 0;
1371    }
1372  
1373    //return values
1374    return array('width'      => $width,
1375                 'height'     => $height,
1376                 'offsetX'    => $offsetX,
1377                 'offsetY'    => $offsetY,
1378                 //'fontHeight' => $fontHeight
1379                 );
1380  }
1381  
1382  function draw_rectangle($border, $colour, $type) {
1383    $colour = $this->colour[$colour];
1384    switch ($type) {
1385      case 'fill':    // fill the rectangle
1386        ImageFilledRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1387        break;
1388      case 'box':     // all sides
1389        ImageRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
1390        break;
1391      case 'axis':    // bottom x axis and left y axis
1392        ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1393        ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1394        break;
1395      case 'y':       // left y axis only
1396      case 'y-left':
1397        ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1398        break;
1399      case 'y-right': // right y axis only
1400        ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1401        break;
1402      case 'x':       // bottom x axis only
1403        ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1404        break;
1405      case 'u':       // u shaped. bottom x axis and both left and right y axis.
1406        ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
1407        ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
1408        ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
1409        break;
1410  
1411    }
1412  }
1413  
1414  function init_colours() {
1415    $this->image              = ImageCreate($this->parameter['width'], $this->parameter['height']);
1416    // standard colours
1417    $this->colour['white']    = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF); // first colour is background colour.
1418    $this->colour['black']    = ImageColorAllocate ($this->image, 0x00, 0x00, 0x00);
1419    $this->colour['maroon']   = ImageColorAllocate ($this->image, 0x80, 0x00, 0x00);
1420    $this->colour['green']    = ImageColorAllocate ($this->image, 0x00, 0x80, 0x00);
1421    $this->colour['ltgreen']  = ImageColorAllocate ($this->image, 0x52, 0xF1, 0x7F);
1422    $this->colour['ltltgreen']= ImageColorAllocate ($this->image, 0x99, 0xFF, 0x99);
1423    $this->colour['olive']    = ImageColorAllocate ($this->image, 0x80, 0x80, 0x00);
1424    $this->colour['navy']     = ImageColorAllocate ($this->image, 0x00, 0x00, 0x80);
1425    $this->colour['purple']   = ImageColorAllocate ($this->image, 0x80, 0x00, 0x80);
1426    $this->colour['gray']     = ImageColorAllocate ($this->image, 0x80, 0x80, 0x80);
1427    $this->colour['red']      = ImageColorAllocate ($this->image, 0xFF, 0x00, 0x00);
1428    $this->colour['ltred']    = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x99);
1429    $this->colour['ltltred']  = ImageColorAllocate ($this->image, 0xFF, 0xCC, 0xCC);
1430    $this->colour['orange']   = ImageColorAllocate ($this->image, 0xFF, 0x66, 0x00);
1431    $this->colour['ltorange']   = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x66);
1432    $this->colour['ltltorange'] = ImageColorAllocate ($this->image, 0xFF, 0xcc, 0x99);
1433    $this->colour['lime']     = ImageColorAllocate ($this->image, 0x00, 0xFF, 0x00);
1434    $this->colour['yellow']   = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0x00);