Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/graphlib.php
Go to the documentation of this file.
00001 <?php
00002 
00029 defined('MOODLE_INTERNAL') || die();
00030 
00031 /* This file contains modifications by Martin Dougiamas
00032  * as part of Moodle (http://moodle.com).  Modified lines
00033  * are marked with "Moodle".
00034  */
00035 
00039 class graph {
00040   var $image;
00041   var $debug             =   FALSE;        // be careful!!
00042   var $calculated        =   array();      // array of computed values for chart
00043   var $parameter         =   array(        // input parameters
00044     'width'              =>  320,          // default width of image
00045     'height'             =>  240,          // default height of image
00046     'file_name'          => 'none',        // name of file for file to be saved as.
00047                                            //  NOTE: no suffix required. this is determined from output_format below.
00048     'output_format'      => 'PNG',         // image output format. 'GIF', 'PNG', 'JPEG'. default 'PNG'.
00049 
00050     'seconds_to_live'    =>  0,            // expiry time in seconds (for HTTP header)
00051     'hours_to_live'      =>  0,            // expiry time in hours (for HTTP header)
00052     'path_to_fonts'      => 'fonts/',      // path to fonts folder. don't forget *trailing* slash!!
00053                                            //   for WINDOZE this may need to be the full path, not relative.
00054 
00055     'title'              => 'Graph Title', // text for graph title
00056     'title_font'         => 'default.ttf',   // title text font. don't forget to set 'path_to_fonts' above.
00057     'title_size'         =>  16,           // title text point size
00058     'title_colour'       => 'black',       // colour for title text
00059 
00060     'x_label'            => '',            // if this is set then this text is printed on bottom axis of graph.
00061     'y_label_left'       => '',            // if this is set then this text is printed on left axis of graph.
00062     'y_label_right'      => '',            // if this is set then this text is printed on right axis of graph.
00063 
00064     'label_size'         =>  8,           // label text point size
00065     'label_font'         => 'default.ttf', // label text font. don't forget to set 'path_to_fonts' above.
00066     'label_colour'       => 'gray33',      // label text colour
00067     'y_label_angle'      =>  90,           // rotation of y axis label
00068 
00069     'x_label_angle'      =>  90,            // rotation of y axis label
00070 
00071     'outer_padding'      =>  5,            // padding around outer text. i.e. title, y label, and x label.
00072     'inner_padding'      =>  0,            // padding beteen axis text and graph.
00073     'x_inner_padding'      =>  5,            // padding beteen axis text and graph.
00074     'y_inner_padding'      =>  6,            // padding beteen axis text and graph.
00075     'outer_border'       => 'none',        // colour of border aound image, or 'none'.
00076     'inner_border'       => 'black',       // colour of border around actual graph, or 'none'.
00077     'inner_border_type'  => 'box',         // 'box' for all four sides, 'axis' for x/y axis only,
00078                                            // 'y' or 'y-left' for y axis only, 'y-right' for right y axis only,
00079                                            // 'x' for x axis only, 'u' for both left and right y axis and x axis.
00080     'outer_background'   => 'none',        // background colour of entire image.
00081     'inner_background'   => 'none',        // background colour of plot area.
00082 
00083     'y_min_left'         =>  0,            // this will be reset to minimum value if there is a value lower than this.
00084     'y_max_left'         =>  0,            // this will be reset to maximum value if there is a value higher than this.
00085     'y_min_right'        =>  0,            // this will be reset to minimum value if there is a value lower than this.
00086     'y_max_right'        =>  0,            // this will be reset to maximum value if there is a value higher than this.
00087     'x_min'              =>  0,            // only used if x axis is numeric.
00088     'x_max'              =>  0,            // only used if x axis is numeric.
00089 
00090     'y_resolution_left'  =>  1,            // scaling for rounding of y axis max value.
00091                                            // if max y value is 8645 then
00092                                            // if y_resolution is 0, then y_max becomes 9000.
00093                                            // if y_resolution is 1, then y_max becomes 8700.
00094                                            // if y_resolution is 2, then y_max becomes 8650.
00095                                            // if y_resolution is 3, then y_max becomes 8645.
00096                                            // get it?
00097     'y_decimal_left'     =>  0,            // number of decimal places for y_axis text.
00098     'y_resolution_right' =>  2,            // ... same for right hand side
00099     'y_decimal_right'    =>  0,            // ... same for right hand side
00100     'x_resolution'       =>  2,            // only used if x axis is numeric.
00101     'x_decimal'          =>  0,            // only used if x axis is numeric.
00102 
00103     'point_size'         =>  4,            // default point size. use even number for diamond or triangle to get nice look.
00104     'brush_size'         =>  4,            // default brush size for brush line.
00105     'brush_type'         => 'circle',      // type of brush to use to draw line. choose from the following
00106                                            //   'circle', 'square', 'horizontal', 'vertical', 'slash', 'backslash'
00107     'bar_size'           =>  0.8,          // size of bar to draw. <1 bars won't touch
00108                                            //   1 is full width - i.e. bars will touch.
00109                                            //   >1 means bars will overlap.
00110     'bar_spacing'        =>  10,           // space in pixels between group of bars for each x value.
00111     'shadow_offset'      =>  3,            // draw shadow at this offset, unless overidden by data parameter.
00112     'shadow'             => 'grayCC',      // 'none' or colour of shadow.
00113     'shadow_below_axis'  => true,         // whether to draw shadows of bars and areas below the x/zero axis.
00114 
00115 
00116     'x_axis_gridlines'   => 'auto',        // if set to a number then x axis is treated as numeric.
00117     'y_axis_gridlines'   =>  6,            // number of gridlines on y axis.
00118     'zero_axis'          => 'none',        // colour to draw zero-axis, or 'none'.
00119 
00120 
00121     'axis_font'          => 'default.ttf', // axis text font. don't forget to set 'path_to_fonts' above.
00122     'axis_size'          =>  8,            // axis text font size in points
00123     'axis_colour'        => 'gray33',      // colour of axis text.
00124     'y_axis_angle'       =>  0,            // rotation of axis text.
00125     'x_axis_angle'       =>  0,            // rotation of axis text.
00126 
00127     'y_axis_text_left'   =>  1,            // whether to print left hand y axis text. if 0 no text, if 1 all ticks have text,
00128     'x_axis_text'        =>  1,            //   if 4 then print every 4th tick and text, etc...
00129     'y_axis_text_right'  =>  0,            // behaviour same as above for right hand y axis.
00130 
00131     'x_offset'           =>  0.5,          // x axis tick offset from y axis as fraction of tick spacing.
00132     'y_ticks_colour'     => 'black',       // colour to draw y ticks, or 'none'
00133     'x_ticks_colour'     => 'black',       // colour to draw x ticks, or 'none'
00134     'y_grid'             => 'line',        // grid lines. set to 'line' or 'dash'...
00135     'x_grid'             => 'line',        //   or if set to 'none' print nothing.
00136     'grid_colour'        => 'grayEE',      // default grid colour.
00137     'tick_length'        =>  4,            // length of ticks in pixels. can be negative. i.e. outside data drawing area.
00138 
00139     'legend'             => 'none',        // default. no legend.
00140                                           // otherwise: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
00141                                           //   'outside-top', 'outside-bottom', 'outside-left', or 'outside-right'.
00142     'legend_offset'      =>  10,           // offset in pixels from graph or outside border.
00143     'legend_padding'     =>  5,            // padding around legend text.
00144     'legend_font'        => 'default.ttf',   // legend text font. don't forget to set 'path_to_fonts' above.
00145     'legend_size'        =>  8,            // legend text point size.
00146     'legend_colour'      => 'black',       // legend text colour.
00147     'legend_border'      => 'none',        // legend border colour, or 'none'.
00148 
00149     'decimal_point'      => '.',           // symbol for decimal separation  '.' or ',' *european support.
00150     'thousand_sep'       => ',',           // symbol for thousand separation ',' or ''
00151 
00152   );
00153   var $y_tick_labels     =   null;         // array of text values for y-axis tick labels
00154   var $offset_relation   =   null;         // array of offsets for different sets of data
00155 
00156 
00157     // init all text - title, labels, and axis text.
00158     function init() {
00159 
00161 
00162       global $CFG;
00163 
00170 
00171       $currlang = current_language();
00172       if (file_exists("$CFG->dataroot/lang/".$currlang."_local/fonts/default.ttf")) {
00173           $fontpath = "$CFG->dataroot/lang/".$currlang."_local/fonts/";
00174       } else if (file_exists("$CFG->dataroot/lang/$currlang/fonts/default.ttf")) {
00175           $fontpath = "$CFG->dataroot/lang/$currlang/fonts/";
00176       } else if (file_exists("$CFG->dirroot/lang/$currlang/fonts/default.ttf")) {
00177           $fontpath = "$CFG->dirroot/lang/$currlang/fonts/";
00178       } else if (file_exists("$CFG->dataroot/lang/default.ttf")) {
00179           $fontpath = "$CFG->dataroot/lang/";
00180       } else {
00181           $fontpath = "$CFG->libdir/";
00182       }
00183 
00184       $this->parameter['path_to_fonts'] = $fontpath;
00185 
00187 
00188 
00189 
00190       $this->calculated['outer_border'] = $this->calculated['boundary_box'];
00191 
00192       // outer padding
00193       $this->calculated['boundary_box']['left']   += $this->parameter['outer_padding'];
00194       $this->calculated['boundary_box']['top']    += $this->parameter['outer_padding'];
00195       $this->calculated['boundary_box']['right']  -= $this->parameter['outer_padding'];
00196       $this->calculated['boundary_box']['bottom'] -= $this->parameter['outer_padding'];
00197 
00198       $this->init_x_axis();
00199       $this->init_y_axis();
00200       $this->init_legend();
00201       $this->init_labels();
00202 
00203       //  take into account tick lengths
00204       $this->calculated['bottom_inner_padding'] = $this->parameter['x_inner_padding'];
00205       if (($this->parameter['x_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
00206         $this->calculated['bottom_inner_padding'] -= $this->parameter['tick_length'];
00207       $this->calculated['boundary_box']['bottom'] -= $this->calculated['bottom_inner_padding'];
00208 
00209       $this->calculated['left_inner_padding'] = $this->parameter['y_inner_padding'];
00210       if ($this->parameter['y_axis_text_left']) {
00211         if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
00212           $this->calculated['left_inner_padding'] -= $this->parameter['tick_length'];
00213       }
00214       $this->calculated['boundary_box']['left'] += $this->calculated['left_inner_padding'];
00215 
00216       $this->calculated['right_inner_padding'] = $this->parameter['y_inner_padding'];
00217       if ($this->parameter['y_axis_text_right']) {
00218         if (($this->parameter['y_ticks_colour'] != 'none') && ($this->parameter['tick_length'] < 0))
00219           $this->calculated['right_inner_padding'] -= $this->parameter['tick_length'];
00220       }
00221       $this->calculated['boundary_box']['right'] -= $this->calculated['right_inner_padding'];
00222 
00223       // boundaryBox now has coords for plotting area.
00224       $this->calculated['inner_border'] = $this->calculated['boundary_box'];
00225 
00226       $this->init_data();
00227       $this->init_x_ticks();
00228       $this->init_y_ticks();
00229     }
00230 
00231     function draw_text() {
00232       $colour = $this->parameter['outer_background'];
00233       if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'fill'); // graph background
00234 
00235       // draw border around image
00236       $colour = $this->parameter['outer_border'];
00237       if ($colour != 'none') $this->draw_rectangle($this->calculated['outer_border'], $colour, 'box'); // graph border
00238 
00239       $this->draw_title();
00240       $this->draw_x_label();
00241       $this->draw_y_label_left();
00242       $this->draw_y_label_right();
00243       $this->draw_x_axis();
00244       $this->draw_y_axis();
00245       if      ($this->calculated['y_axis_left']['has_data'])  $this->draw_zero_axis_left();  // either draw zero axis on left
00246       else if ($this->calculated['y_axis_right']['has_data']) $this->draw_zero_axis_right(); // ... or right.
00247       $this->draw_legend();
00248 
00249       // draw border around plot area
00250       $colour = $this->parameter['inner_background'];
00251       if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, 'fill'); // graph background
00252 
00253       // draw border around image
00254       $colour = $this->parameter['inner_border'];
00255       if ($colour != 'none') $this->draw_rectangle($this->calculated['inner_border'], $colour, $this->parameter['inner_border_type']); // graph border
00256     }
00257 
00258     function draw_stack() {
00259       $this->init();
00260       $this->draw_text();
00261 
00262       $yOrder = $this->y_order; // save y_order data.
00263       // iterate over each data set. order is very important if you want to see data correctly. remember shadows!!
00264       foreach ($yOrder as $set) {
00265         $this->y_order = array($set);
00266         $this->init_data();
00267         $this->draw_data();
00268       }
00269       $this->y_order = $yOrder; // revert y_order data.
00270 
00271       $this->output();
00272     }
00273 
00274     function draw() {
00275       $this->init();
00276       $this->draw_text();
00277       $this->draw_data();
00278       $this->output();
00279     }
00280 
00281     // draw a data set
00282     function draw_set($order, $set, $offset) {
00283       if ($offset) @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
00284       else $colour  = $this->y_format[$set]['colour'];
00285       @$this->init_variable($point,      $this->y_format[$set]['point'],      'none');
00286       @$this->init_variable($pointSize,  $this->y_format[$set]['point_size'],  $this->parameter['point_size']);
00287       @$this->init_variable($line,       $this->y_format[$set]['line'],       'none');
00288       @$this->init_variable($brushType,  $this->y_format[$set]['brush_type'],  $this->parameter['brush_type']);
00289       @$this->init_variable($brushSize,  $this->y_format[$set]['brush_size'],  $this->parameter['brush_size']);
00290       @$this->init_variable($bar,        $this->y_format[$set]['bar'],        'none');
00291       @$this->init_variable($barSize,    $this->y_format[$set]['bar_size'],    $this->parameter['bar_size']);
00292       @$this->init_variable($area,       $this->y_format[$set]['area'],       'none');
00293 
00294       $lastX = 0;
00295       $lastY = 'none';
00296       $fromX = 0;
00297       $fromY = 'none';
00298 
00299       //print "set $set<br />";
00300       //expand_pre($this->calculated['y_plot']);
00301 
00302       foreach ($this->x_data as $index => $x) {
00303         //print "index $index<br />";
00304         $thisY = $this->calculated['y_plot'][$set][$index];
00305         $thisX = $this->calculated['x_plot'][$index];
00306 
00307         //print "$thisX, $thisY <br />";
00308 
00309         if (($bar!='none') && (string)$thisY != 'none') {
00310             if ($relatedset = $this->offset_relation[$set]) {                               // Moodle
00311                 $yoffset = $this->calculated['y_plot'][$relatedset][$index];                // Moodle
00312             } else {                                                                        // Moodle
00313                 $yoffset = 0;                                                               // Moodle
00314             }                                                                               // Moodle
00315             //$this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set);           // Moodle
00316             $this->bar($thisX, $thisY, $bar, $barSize, $colour, $offset, $set, $yoffset);   // Moodle
00317         }
00318 
00319         if (($area!='none') && (((string)$lastY != 'none') && ((string)$thisY != 'none')))
00320           $this->area($lastX, $lastY, $thisX, $thisY, $area, $colour, $offset);
00321 
00322         if (($point!='none') && (string)$thisY != 'none') $this->plot($thisX, $thisY, $point, $pointSize, $colour, $offset);
00323 
00324         if (($line!='none') && ((string)$thisY != 'none')) {
00325           if ((string)$fromY != 'none')
00326             $this->line($fromX, $fromY, $thisX, $thisY, $line, $brushType, $brushSize, $colour, $offset);
00327 
00328           $fromY = $thisY; // start next line from here
00329           $fromX = $thisX; // ...
00330         } else {
00331           $fromY = 'none';
00332           $fromX = 'none';
00333         }
00334 
00335         $lastX = $thisX;
00336         $lastY = $thisY;
00337       }
00338     }
00339 
00340     function draw_data() {
00341       // cycle thru y data to be plotted
00342       // first check for drop shadows...
00343       foreach ($this->y_order as $order => $set) {
00344         @$this->init_variable($offset, $this->y_format[$set]['shadow_offset'], $this->parameter['shadow_offset']);
00345         @$this->init_variable($colour, $this->y_format[$set]['shadow'], $this->parameter['shadow']);
00346         if ($colour != 'none') $this->draw_set($order, $set, $offset);
00347 
00348       }
00349 
00350       // then draw data
00351       foreach ($this->y_order as $order => $set) {
00352         $this->draw_set($order, $set, 0);
00353       }
00354     }
00355 
00356     function draw_legend() {
00357       $position      = $this->parameter['legend'];
00358       if ($position == 'none') return; // abort if no border
00359 
00360       $borderColour  = $this->parameter['legend_border'];
00361       $offset        = $this->parameter['legend_offset'];
00362       $padding       = $this->parameter['legend_padding'];
00363       $height        = $this->calculated['legend']['boundary_box_all']['height'];
00364       $width         = $this->calculated['legend']['boundary_box_all']['width'];
00365       $graphTop      = $this->calculated['boundary_box']['top'];
00366       $graphBottom   = $this->calculated['boundary_box']['bottom'];
00367       $graphLeft     = $this->calculated['boundary_box']['left'];
00368       $graphRight    = $this->calculated['boundary_box']['right'];
00369       $outsideRight  = $this->calculated['outer_border']['right'];
00370       $outsideBottom = $this->calculated['outer_border']['bottom'];
00371       switch ($position) {
00372         case 'top-left':
00373           $top    = $graphTop  + $offset;
00374           $bottom = $graphTop  + $height + $offset;
00375           $left   = $graphLeft + $offset;
00376           $right  = $graphLeft + $width + $offset;
00377 
00378           break;
00379         case 'top-right':
00380           $top    = $graphTop   + $offset;
00381           $bottom = $graphTop   + $height + $offset;
00382           $left   = $graphRight - $width - $offset;
00383           $right  = $graphRight - $offset;
00384 
00385           break;
00386         case 'bottom-left':
00387           $top    = $graphBottom - $height - $offset;
00388           $bottom = $graphBottom - $offset;
00389           $left   = $graphLeft   + $offset;
00390           $right  = $graphLeft   + $width + $offset;
00391 
00392           break;
00393         case 'bottom-right':
00394           $top    = $graphBottom - $height - $offset;
00395           $bottom = $graphBottom - $offset;
00396           $left   = $graphRight  - $width - $offset;
00397           $right  = $graphRight  - $offset;
00398           break;
00399 
00400         case 'outside-top' :
00401           $top    = $graphTop;
00402           $bottom = $graphTop     + $height;
00403           $left   = $outsideRight - $width - $offset;
00404           $right  = $outsideRight - $offset;
00405           break;
00406 
00407         case 'outside-bottom' :
00408           $top    = $graphBottom  - $height;
00409           $bottom = $graphBottom;
00410           $left   = $outsideRight - $width - $offset;
00411           $right  = $outsideRight - $offset;
00412          break;
00413 
00414         case 'outside-left' :
00415           $top    = $outsideBottom - $height - $offset;
00416           $bottom = $outsideBottom - $offset;
00417           $left   = $graphLeft;
00418           $right  = $graphLeft     + $width;
00419          break;
00420 
00421         case 'outside-right' :
00422           $top    = $outsideBottom - $height - $offset;
00423           $bottom = $outsideBottom - $offset;
00424           $left   = $graphRight    - $width;
00425           $right  = $graphRight;
00426           break;
00427         default: // default is top left. no particular reason.
00428           $top    = $this->calculated['boundary_box']['top'];
00429           $bottom = $this->calculated['boundary_box']['top'] + $this->calculated['legend']['boundary_box_all']['height'];
00430           $left   = $this->calculated['boundary_box']['left'];
00431           $right  = $this->calculated['boundary_box']['right'] + $this->calculated['legend']['boundary_box_all']['width'];
00432 
00433     }
00434       // legend border
00435       if($borderColour!='none') $this->draw_rectangle(array('top' => $top,
00436                                                             'left' => $left,
00437                                                             'bottom' => $bottom,
00438                                                             'right' => $right), $this->parameter['legend_border'], 'box');
00439 
00440       // legend text
00441       $legendText = array('points' => $this->parameter['legend_size'],
00442                           'angle'  => 0,
00443                           'font'   => $this->parameter['legend_font'],
00444                           'colour' => $this->parameter['legend_colour']);
00445 
00446       $box = $this->calculated['legend']['boundary_box_max']['height']; // use max height for legend square size.
00447       $x = $left + $padding;
00448       $x_text = $x + $box * 2;
00449       $y = $top + $padding;
00450 
00451       foreach ($this->y_order as $set) {
00452         $legendText['text'] = $this->calculated['legend']['text'][$set];
00453         if ($legendText['text'] != 'none') {
00454           // if text exists then draw box and text
00455           $boxColour = $this->colour[$this->y_format[$set]['colour']];
00456 
00457           // draw box
00458           ImageFilledRectangle($this->image, $x, $y, $x + $box, $y + $box, $boxColour);
00459 
00460           // draw text
00461           $coords = array('x' => $x + $box * 2, 'y' => $y, 'reference' => 'top-left');
00462           $legendText['boundary_box'] = $this->calculated['legend']['boundary_box'][$set];
00463           $this->update_boundaryBox($legendText['boundary_box'], $coords);
00464           $this->print_TTF($legendText);
00465           $y += $padding + $box;
00466         }
00467       }
00468 
00469     }
00470 
00471     function draw_y_label_right() {
00472       if (!$this->parameter['y_label_right']) return;
00473       $x = $this->calculated['boundary_box']['right'] + $this->parameter['y_inner_padding'];
00474       if ($this->parameter['y_axis_text_right']) $x += $this->calculated['y_axis_right']['boundary_box_max']['width']
00475                                                + $this->calculated['right_inner_padding'];
00476       $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
00477 
00478       $label = $this->calculated['y_label_right'];
00479       $coords = array('x' => $x, 'y' => $y, 'reference' => 'left-center');
00480       $this->update_boundaryBox($label['boundary_box'], $coords);
00481       $this->print_TTF($label);
00482     }
00483 
00484 
00485     function draw_y_label_left() {
00486       if (!$this->parameter['y_label_left']) return;
00487       $x = $this->calculated['boundary_box']['left'] - $this->parameter['y_inner_padding'];
00488       if ($this->parameter['y_axis_text_left']) $x -= $this->calculated['y_axis_left']['boundary_box_max']['width']
00489                                                + $this->calculated['left_inner_padding'];
00490       $y = ($this->calculated['boundary_box']['bottom'] + $this->calculated['boundary_box']['top']) / 2;
00491 
00492       $label = $this->calculated['y_label_left'];
00493       $coords = array('x' => $x, 'y' => $y, 'reference' => 'right-center');
00494       $this->update_boundaryBox($label['boundary_box'], $coords);
00495       $this->print_TTF($label);
00496     }
00497 
00498     function draw_title() {
00499       if (!$this->parameter['title']) return;
00500       //$y = $this->calculated['outside_border']['top'] + $this->parameter['outer_padding'];
00501       $y = $this->calculated['boundary_box']['top'] - $this->parameter['outer_padding'];
00502       $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
00503       $label = $this->calculated['title'];
00504       $coords = array('x' => $x, 'y' => $y, 'reference' => 'bottom-center');
00505       $this->update_boundaryBox($label['boundary_box'], $coords);
00506       $this->print_TTF($label);
00507     }
00508 
00509     function draw_x_label() {
00510       if (!$this->parameter['x_label']) return;
00511       $y = $this->calculated['boundary_box']['bottom'] + $this->parameter['x_inner_padding'];
00512       if ($this->parameter['x_axis_text']) $y += $this->calculated['x_axis']['boundary_box_max']['height']
00513                                               + $this->calculated['bottom_inner_padding'];
00514       $x = ($this->calculated['boundary_box']['right'] + $this->calculated['boundary_box']['left']) / 2;
00515       $label = $this->calculated['x_label'];
00516       $coords = array('x' => $x, 'y' => $y, 'reference' => 'top-center');
00517       $this->update_boundaryBox($label['boundary_box'], $coords);
00518       $this->print_TTF($label);
00519     }
00520 
00521     function draw_zero_axis_left() {
00522       $colour = $this->parameter['zero_axis'];
00523       if ($colour == 'none') return;
00524       // draw zero axis on left hand side
00525       $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top']  + ($this->calculated['y_axis_left']['max'] * $this->calculated['y_axis_left']['factor']));
00526       ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
00527     }
00528 
00529     function draw_zero_axis_right() {
00530       $colour = $this->parameter['zero_axis'];
00531       if ($colour == 'none') return;
00532       // draw zero axis on right hand side
00533       $this->calculated['zero_axis'] = round($this->calculated['boundary_box']['top']  + ($this->calculated['y_axis_right']['max'] * $this->calculated['y_axis_right']['factor']));
00534       ImageLine($this->image, $this->calculated['boundary_box']['left'], $this->calculated['zero_axis'], $this->calculated['boundary_box']['right'], $this->calculated['zero_axis'], $this->colour[$colour]);
00535     }
00536 
00537     function draw_x_axis() {
00538       $gridColour  = $this->colour[$this->parameter['grid_colour']];
00539       $tickColour  = $this->colour[$this->parameter['x_ticks_colour']];
00540       $axis_colour  = $this->parameter['axis_colour'];
00541       $xGrid       = $this->parameter['x_grid'];
00542       $gridTop     = $this->calculated['boundary_box']['top'];
00543       $gridBottom  = $this->calculated['boundary_box']['bottom'];
00544 
00545       if ($this->parameter['tick_length'] >= 0) {
00546         $tickTop     = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
00547         $tickBottom  = $this->calculated['boundary_box']['bottom'];
00548         $textBottom  = $tickBottom + $this->calculated['bottom_inner_padding'];
00549       } else {
00550         $tickTop     = $this->calculated['boundary_box']['bottom'];
00551         $tickBottom  = $this->calculated['boundary_box']['bottom'] - $this->parameter['tick_length'];
00552         $textBottom  = $tickBottom + $this->calculated['bottom_inner_padding'];
00553       }
00554 
00555       $axis_font    = $this->parameter['axis_font'];
00556       $axis_size    = $this->parameter['axis_size'];
00557       $axis_angle   = $this->parameter['x_axis_angle'];
00558 
00559       if ($axis_angle == 0)  $reference = 'top-center';
00560       if ($axis_angle > 0)   $reference = 'top-right';
00561       if ($axis_angle < 0)   $reference = 'top-left';
00562       if ($axis_angle == 90) $reference = 'top-center';
00563 
00564       //generic tag information. applies to all axis text.
00565       $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
00566 
00567       foreach ($this->calculated['x_axis']['tick_x'] as $set => $tickX) {
00568         // draw x grid if colour specified
00569         if ($xGrid != 'none') {
00570           switch ($xGrid) {
00571             case 'line':
00572               ImageLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
00573               break;
00574              case 'dash':
00575               ImageDashedLine($this->image, round($tickX), round($gridTop), round($tickX), round($gridBottom), $gridColour);
00576               break;
00577           }
00578         }
00579 
00580         if ($this->parameter['x_axis_text'] && !($set % $this->parameter['x_axis_text'])) { // test if tick should be displayed
00581           // draw tick
00582           if ($tickColour != 'none')
00583             ImageLine($this->image, round($tickX), round($tickTop), round($tickX), round($tickBottom), $tickColour);
00584 
00585           // draw axis text
00586           $coords = array('x' => $tickX, 'y' => $textBottom, 'reference' => $reference);
00587           $axisTag['text'] = $this->calculated['x_axis']['text'][$set];
00588           $axisTag['boundary_box'] = $this->calculated['x_axis']['boundary_box'][$set];
00589           $this->update_boundaryBox($axisTag['boundary_box'], $coords);
00590           $this->print_TTF($axisTag);
00591         }
00592       }
00593     }
00594 
00595     function draw_y_axis() {
00596       $gridColour  = $this->colour[$this->parameter['grid_colour']];
00597       $tickColour  = $this->colour[$this->parameter['y_ticks_colour']];
00598       $axis_colour  = $this->parameter['axis_colour'];
00599       $yGrid       = $this->parameter['y_grid'];
00600       $gridLeft    = $this->calculated['boundary_box']['left'];
00601       $gridRight   = $this->calculated['boundary_box']['right'];
00602 
00603       // axis font information
00604       $axis_font    = $this->parameter['axis_font'];
00605       $axis_size    = $this->parameter['axis_size'];
00606       $axis_angle   = $this->parameter['y_axis_angle'];
00607       $axisTag = array('points' => $axis_size, 'angle' => $axis_angle, 'font' => $axis_font, 'colour' => $axis_colour);
00608 
00609 
00610       if ($this->calculated['y_axis_left']['has_data']) {
00611         // LEFT HAND SIDE
00612         // left and right coords for ticks
00613         if ($this->parameter['tick_length'] >= 0) {
00614           $tickLeft     = $this->calculated['boundary_box']['left'];
00615           $tickRight    = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
00616         } else {
00617           $tickLeft     = $this->calculated['boundary_box']['left'] + $this->parameter['tick_length'];
00618           $tickRight    = $this->calculated['boundary_box']['left'];
00619         }
00620         $textRight      = $tickLeft - $this->calculated['left_inner_padding'];
00621 
00622         if ($axis_angle == 0)  $reference = 'right-center';
00623         if ($axis_angle > 0)   $reference = 'right-top';
00624         if ($axis_angle < 0)   $reference = 'right-bottom';
00625         if ($axis_angle == 90) $reference = 'right-center';
00626 
00627         foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
00628           // draw y grid if colour specified
00629           if ($yGrid != 'none') {
00630             switch ($yGrid) {
00631               case 'line':
00632                 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
00633                 break;
00634                case 'dash':
00635                 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
00636                 break;
00637             }
00638           }
00639 
00640           // y axis text
00641           if ($this->parameter['y_axis_text_left'] && !($set % $this->parameter['y_axis_text_left'])) { // test if tick should be displayed
00642             // draw tick
00643             if ($tickColour != 'none')
00644               ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
00645 
00646             // draw axis text...
00647             $coords = array('x' => $textRight, 'y' => $tickY, 'reference' => $reference);
00648             $axisTag['text'] = $this->calculated['y_axis_left']['text'][$set];
00649             $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
00650             $this->update_boundaryBox($axisTag['boundary_box'], $coords);
00651             $this->print_TTF($axisTag);
00652           }
00653         }
00654       }
00655 
00656       if ($this->calculated['y_axis_right']['has_data']) {
00657         // RIGHT HAND SIDE
00658         // left and right coords for ticks
00659         if ($this->parameter['tick_length'] >= 0) {
00660           $tickLeft     = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
00661           $tickRight    = $this->calculated['boundary_box']['right'];
00662         } else {
00663           $tickLeft     = $this->calculated['boundary_box']['right'];
00664           $tickRight    = $this->calculated['boundary_box']['right'] - $this->parameter['tick_length'];
00665         }
00666         $textLeft       = $tickRight+ $this->calculated['left_inner_padding'];
00667 
00668         if ($axis_angle == 0)  $reference = 'left-center';
00669         if ($axis_angle > 0)   $reference = 'left-bottom';
00670         if ($axis_angle < 0)   $reference = 'left-top';
00671         if ($axis_angle == 90) $reference = 'left-center';
00672 
00673         foreach ($this->calculated['y_axis']['tick_y'] as $set => $tickY) {
00674           if (!$this->calculated['y_axis_left']['has_data'] && $yGrid != 'none') { // draw grid if not drawn already (above)
00675             switch ($yGrid) {
00676               case 'line':
00677                 ImageLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
00678                 break;
00679                case 'dash':
00680                 ImageDashedLine($this->image, round($gridLeft), round($tickY), round($gridRight), round($tickY), $gridColour);
00681                 break;
00682             }
00683           }
00684 
00685           if ($this->parameter['y_axis_text_right'] && !($set % $this->parameter['y_axis_text_right'])) { // test if tick should be displayed
00686             // draw tick
00687             if ($tickColour != 'none')
00688               ImageLine($this->image, round($tickLeft), round($tickY), round($tickRight), round($tickY), $tickColour);
00689 
00690             // draw axis text...
00691             $coords = array('x' => $textLeft, 'y' => $tickY, 'reference' => $reference);
00692             $axisTag['text'] = $this->calculated['y_axis_right']['text'][$set];
00693             $axisTag['boundary_box'] = $this->calculated['y_axis_left']['boundary_box'][$set];
00694             $this->update_boundaryBox($axisTag['boundary_box'], $coords);
00695             $this->print_TTF($axisTag);
00696           }
00697         }
00698       }
00699     }
00700 
00701     function init_data() {
00702       $this->calculated['y_plot'] = array(); // array to hold pixel plotting coords for y axis
00703       $height = $this->calculated['boundary_box']['bottom'] - $this->calculated['boundary_box']['top'];
00704       $width  = $this->calculated['boundary_box']['right'] - $this->calculated['boundary_box']['left'];
00705 
00706       // calculate pixel steps between axis ticks.
00707       $this->calculated['y_axis']['step'] = $height / ($this->parameter['y_axis_gridlines'] - 1);
00708 
00709       // calculate x ticks spacing taking into account x offset for ticks.
00710       $extraTick  = 2 * $this->parameter['x_offset']; // extra tick to account for padding
00711       $numTicks = $this->calculated['x_axis']['num_ticks'] - 1;    // number of x ticks
00712 
00713       // Hack by rodger to avoid division by zero, see bug 1231
00714       if ($numTicks==0) $numTicks=1;
00715 
00716       $this->calculated['x_axis']['step'] = $width / ($numTicks + $extraTick);
00717       $widthPlot = $width - ($this->calculated['x_axis']['step'] * $extraTick);
00718       $this->calculated['x_axis']['step'] = $widthPlot / $numTicks;
00719 
00720       //calculate factor for transforming x,y physical coords to logical coords for right hand y_axis.
00721       $y_range = $this->calculated['y_axis_right']['max'] - $this->calculated['y_axis_right']['min'];
00722       $y_range = ($y_range ? $y_range : 1);
00723       $this->calculated['y_axis_right']['factor'] = $height / $y_range;
00724 
00725       //calculate factor for transforming x,y physical coords to logical coords for left hand axis.
00726       $yRange = $this->calculated['y_axis_left']['max'] - $this->calculated['y_axis_left']['min'];
00727       $yRange = ($yRange ? $yRange : 1);
00728       $this->calculated['y_axis_left']['factor'] = $height / $yRange;
00729       if ($this->parameter['x_axis_gridlines'] != 'auto') {
00730         $xRange = $this->calculated['x_axis']['max'] - $this->calculated['x_axis']['min'];
00731         $xRange = ($xRange ? $xRange : 1);
00732         $this->calculated['x_axis']['factor'] = $widthPlot / $xRange;
00733       }
00734 
00735       //expand_pre($this->calculated['boundary_box']);
00736       // cycle thru all data sets...
00737       $this->calculated['num_bars'] = 0;
00738       foreach ($this->y_order as $order => $set) {
00739         // determine how many bars there are
00740         if (isset($this->y_format[$set]['bar']) && ($this->y_format[$set]['bar'] != 'none')) {
00741           $this->calculated['bar_offset_index'][$set] = $this->calculated['num_bars']; // index to relate bar with data set.
00742           $this->calculated['num_bars']++;
00743         }
00744 
00745         // calculate y coords for plotting data
00746         foreach ($this->x_data as $index => $x) {
00747           $this->calculated['y_plot'][$set][$index] = $this->y_data[$set][$index];
00748 
00749           if ((string)$this->y_data[$set][$index] != 'none') {
00750 
00751             if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
00752               $this->calculated['y_plot'][$set][$index] =
00753                 round(($this->y_data[$set][$index] - $this->calculated['y_axis_right']['min'])
00754                   * $this->calculated['y_axis_right']['factor']);
00755             } else {
00756               //print "$set $index<br />";
00757               $this->calculated['y_plot'][$set][$index] =
00758                 round(($this->y_data[$set][$index] - $this->calculated['y_axis_left']['min'])
00759                   * $this->calculated['y_axis_left']['factor']);
00760             }
00761 
00762           }
00763         }
00764       }
00765       //print "factor ".$this->calculated['x_axis']['factor']."<br />";
00766       //expand_pre($this->calculated['x_plot']);
00767 
00768       // calculate bar parameters if bars are to be drawn.
00769       if ($this->calculated['num_bars']) {
00770         $xStep       = $this->calculated['x_axis']['step'];
00771         $totalWidth  = $this->calculated['x_axis']['step'] - $this->parameter['bar_spacing'];
00772         $barWidth    = $totalWidth / $this->calculated['num_bars'];
00773 
00774         $barX = ($barWidth - $totalWidth) / 2; // starting x offset
00775         for ($i=0; $i < $this->calculated['num_bars']; $i++) {
00776           $this->calculated['bar_offset_x'][$i] = $barX;
00777           $barX += $barWidth; // add width of bar to x offset.
00778         }
00779         $this->calculated['bar_width'] = $barWidth;
00780       }
00781 
00782 
00783     }
00784 
00785     function init_x_ticks() {
00786       // get coords for x axis ticks and data plots
00787       //$xGrid       = $this->parameter['x_grid'];
00788       $xStep       = $this->calculated['x_axis']['step'];
00789       $ticksOffset = $this->parameter['x_offset']; // where to start drawing ticks relative to y axis.
00790       $gridLeft    = $this->calculated['boundary_box']['left'] + ($xStep * $ticksOffset); // grid x start
00791       $tickX       = $gridLeft; // tick x coord
00792 
00793       foreach ($this->calculated['x_axis']['text'] as $set => $value) {
00794         //print "index: $set<br />";
00795         // x tick value
00796         $this->calculated['x_axis']['tick_x'][$set] = $tickX;
00797         // if num ticks is auto then x plot value is same as x  tick
00798         if ($this->parameter['x_axis_gridlines'] == 'auto') $this->calculated['x_plot'][$set] = round($tickX);
00799         //print $this->calculated['x_plot'][$set].'<br />';
00800         $tickX += $xStep;
00801       }
00802 
00803       //print "xStep: $xStep <br />";
00804       // if numeric x axis then calculate x coords for each data point. this is seperate from x ticks.
00805       $gridX = $gridLeft;
00806       if (empty($this->calculated['x_axis']['factor'])) {
00807           $this->calculated['x_axis']['factor'] = 0;
00808       }
00809       if (empty($this->calculated['x_axis']['min'])) {
00810           $this->calculated['x_axis']['min'] = 0;
00811       }
00812       $factor = $this->calculated['x_axis']['factor'];
00813       $min = $this->calculated['x_axis']['min'];
00814 
00815       if ($this->parameter['x_axis_gridlines'] != 'auto') {
00816         foreach ($this->x_data as $index => $x) {
00817           //print "index: $index, x: $x<br />";
00818           $offset = $x - $this->calculated['x_axis']['min'];
00819 
00820           //$gridX = ($offset * $this->calculated['x_axis']['factor']);
00821           //print "offset: $offset <br />";
00822           //$this->calculated['x_plot'][$set] = $gridLeft + ($offset * $this->calculated['x_axis']['factor']);
00823 
00824           $this->calculated['x_plot'][$index] = $gridLeft + ($x - $min) * $factor;
00825 
00826           //print $this->calculated['x_plot'][$set].'<br />';
00827         }
00828       }
00829       //expand_pre($this->calculated['boundary_box']);
00830       //print "factor ".$this->calculated['x_axis']['factor']."<br />";
00831       //expand_pre($this->calculated['x_plot']);
00832     }
00833 
00834     function init_y_ticks() {
00835       // get coords for y axis ticks
00836 
00837       $yStep      = $this->calculated['y_axis']['step'];
00838       $gridBottom = $this->calculated['boundary_box']['bottom'];
00839       $tickY      = $gridBottom; // tick y coord
00840 
00841       for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) {
00842         $this->calculated['y_axis']['tick_y'][$i] = $tickY;
00843         $tickY   -= $yStep;
00844       }
00845 
00846     }
00847 
00848     function init_labels() {
00849       if ($this->parameter['title']) {
00850         $size = $this->get_boundaryBox(
00851           array('points' => $this->parameter['title_size'],
00852                 'angle'  => 0,
00853                 'font'   => $this->parameter['title_font'],
00854                 'text'   => $this->parameter['title']));
00855         $this->calculated['title']['boundary_box']  = $size;
00856         $this->calculated['title']['text']         = $this->parameter['title'];
00857         $this->calculated['title']['font']         = $this->parameter['title_font'];
00858         $this->calculated['title']['points']       = $this->parameter['title_size'];
00859         $this->calculated['title']['colour']       = $this->parameter['title_colour'];
00860         $this->calculated['title']['angle']        = 0;
00861 
00862         $this->calculated['boundary_box']['top'] += $size['height'] + $this->parameter['outer_padding'];
00863         //$this->calculated['boundary_box']['top'] += $size['height'];
00864 
00865       } else $this->calculated['title']['boundary_box'] = $this->get_null_size();
00866 
00867       if ($this->parameter['y_label_left']) {
00868         $this->calculated['y_label_left']['text']    = $this->parameter['y_label_left'];
00869         $this->calculated['y_label_left']['angle']   = $this->parameter['y_label_angle'];
00870         $this->calculated['y_label_left']['font']    = $this->parameter['label_font'];
00871         $this->calculated['y_label_left']['points']  = $this->parameter['label_size'];
00872         $this->calculated['y_label_left']['colour']  = $this->parameter['label_colour'];
00873 
00874         $size = $this->get_boundaryBox($this->calculated['y_label_left']);
00875         $this->calculated['y_label_left']['boundary_box']  = $size;
00876         //$this->calculated['boundary_box']['left'] += $size['width'] + $this->parameter['inner_padding'];
00877         $this->calculated['boundary_box']['left'] += $size['width'];
00878 
00879       } else $this->calculated['y_label_left']['boundary_box'] = $this->get_null_size();
00880 
00881       if ($this->parameter['y_label_right']) {
00882         $this->calculated['y_label_right']['text']    = $this->parameter['y_label_right'];
00883         $this->calculated['y_label_right']['angle']   = $this->parameter['y_label_angle'];
00884         $this->calculated['y_label_right']['font']    = $this->parameter['label_font'];
00885         $this->calculated['y_label_right']['points']  = $this->parameter['label_size'];
00886         $this->calculated['y_label_right']['colour']  = $this->parameter['label_colour'];
00887 
00888         $size = $this->get_boundaryBox($this->calculated['y_label_right']);
00889         $this->calculated['y_label_right']['boundary_box']  = $size;
00890         //$this->calculated['boundary_box']['right'] -= $size['width'] + $this->parameter['inner_padding'];
00891         $this->calculated['boundary_box']['right'] -= $size['width'];
00892 
00893       } else $this->calculated['y_label_right']['boundary_box'] = $this->get_null_size();
00894 
00895       if ($this->parameter['x_label']) {
00896         $this->calculated['x_label']['text']         = $this->parameter['x_label'];
00897         $this->calculated['x_label']['angle']        = $this->parameter['x_label_angle'];
00898         $this->calculated['x_label']['font']         = $this->parameter['label_font'];
00899         $this->calculated['x_label']['points']       = $this->parameter['label_size'];
00900         $this->calculated['x_label']['colour']       = $this->parameter['label_colour'];
00901 
00902         $size = $this->get_boundaryBox($this->calculated['x_label']);
00903         $this->calculated['x_label']['boundary_box']  = $size;
00904         //$this->calculated['boundary_box']['bottom'] -= $size['height'] + $this->parameter['inner_padding'];
00905         $this->calculated['boundary_box']['bottom'] -= $size['height'];
00906 
00907       } else $this->calculated['x_label']['boundary_box'] = $this->get_null_size();
00908 
00909     }
00910 
00911 
00912     function init_legend() {
00913       $this->calculated['legend'] = array(); // array to hold calculated values for legend.
00914       //$this->calculated['legend']['boundary_box_max'] = array('height' => 0, 'width' => 0);
00915       $this->calculated['legend']['boundary_box_max'] = $this->get_null_size();
00916       if ($this->parameter['legend'] == 'none') return;
00917 
00918       $position = $this->parameter['legend'];
00919       $numSets = 0; // number of data sets with legends.
00920       $sumTextHeight = 0; // total of height of all legend text items.
00921       $width = 0;
00922       $height = 0;
00923 
00924       foreach ($this->y_order as $set) {
00925        $text = isset($this->y_format[$set]['legend']) ? $this->y_format[$set]['legend'] : 'none';
00926        $size = $this->get_boundaryBox(
00927          array('points' => $this->parameter['legend_size'],
00928                'angle'  => 0,
00929                'font'   => $this->parameter['legend_font'],
00930                'text'   => $text));
00931 
00932        $this->calculated['legend']['boundary_box'][$set] = $size;
00933        $this->calculated['legend']['text'][$set]        = $text;
00934        //$this->calculated['legend']['font'][$set]        = $this->parameter['legend_font'];
00935        //$this->calculated['legend']['points'][$set]      = $this->parameter['legend_size'];
00936        //$this->calculated['legend']['angle'][$set]       = 0;
00937 
00938        if ($text && $text!='none') {
00939          $numSets++;
00940          $sumTextHeight += $size['height'];
00941        }
00942 
00943        if ($size['width'] > $this->calculated['legend']['boundary_box_max']['width'])
00944          $this->calculated['legend']['boundary_box_max'] = $size;
00945       }
00946 
00947       $offset  = $this->parameter['legend_offset'];  // offset in pixels of legend box from graph border.
00948       $padding = $this->parameter['legend_padding']; // padding in pixels around legend text.
00949       $textWidth = $this->calculated['legend']['boundary_box_max']['width']; // width of largest legend item.
00950       $textHeight = $this->calculated['legend']['boundary_box_max']['height']; // use height as size to use for colour square in legend.
00951       $width = $padding * 2 + $textWidth + $textHeight * 2;  // left and right padding + maximum text width + space for square
00952       $height = ($padding + $textHeight) * $numSets + $padding; // top and bottom padding + padding between text + text.
00953 
00954       $this->calculated['legend']['boundary_box_all'] = array('width'     => $width,
00955                                                             'height'    => $height,
00956                                                             'offset'    => $offset,
00957                                                             'reference' => $position);
00958 
00959       switch ($position) { // move in right or bottom if legend is outside data plotting area.
00960         case 'outside-top' :
00961           $this->calculated['boundary_box']['right']      -= $offset + $width; // move in right hand side
00962           break;
00963 
00964         case 'outside-bottom' :
00965           $this->calculated['boundary_box']['right']      -= $offset + $width; // move in right hand side
00966           break;
00967 
00968         case 'outside-left' :
00969           $this->calculated['boundary_box']['bottom']      -= $offset + $height; // move in right hand side
00970           break;
00971 
00972         case 'outside-right' :
00973           $this->calculated['boundary_box']['bottom']      -= $offset + $height; // move in right hand side
00974           break;
00975       }
00976     }
00977 
00978     function init_y_axis() {
00979       $this->calculated['y_axis_left'] = array(); // array to hold calculated values for y_axis on left.
00980       $this->calculated['y_axis_left']['boundary_box_max'] = $this->get_null_size();
00981       $this->calculated['y_axis_right'] = array(); // array to hold calculated values for y_axis on right.
00982       $this->calculated['y_axis_right']['boundary_box_max'] = $this->get_null_size();
00983 
00984       $axis_font       = $this->parameter['axis_font'];
00985       $axis_size       = $this->parameter['axis_size'];
00986       $axis_colour     = $this->parameter['axis_colour'];
00987       $axis_angle      = $this->parameter['y_axis_angle'];
00988       $y_tick_labels   = $this->y_tick_labels;
00989 
00990       $this->calculated['y_axis_left']['has_data'] = FALSE;
00991       $this->calculated['y_axis_right']['has_data'] = FALSE;
00992 
00993       // find min and max y values.
00994       $minLeft = $this->parameter['y_min_left'];
00995       $maxLeft = $this->parameter['y_max_left'];
00996       $minRight = $this->parameter['y_min_right'];
00997       $maxRight = $this->parameter['y_max_right'];
00998       $dataLeft = array();
00999       $dataRight = array();
01000       foreach ($this->y_order as $order => $set) {
01001         if (isset($this->y_format[$set]['y_axis']) && $this->y_format[$set]['y_axis'] == 'right') {
01002           $this->calculated['y_axis_right']['has_data'] = TRUE;
01003           $dataRight = array_merge($dataRight, $this->y_data[$set]);
01004         } else {
01005           $this->calculated['y_axis_left']['has_data'] = TRUE;
01006           $dataLeft = array_merge($dataLeft, $this->y_data[$set]);
01007         }
01008       }
01009       $dataLeftRange = $this->find_range($dataLeft, $minLeft, $maxLeft, $this->parameter['y_resolution_left']);
01010       $dataRightRange = $this->find_range($dataRight, $minRight, $maxRight, $this->parameter['y_resolution_right']);
01011       $minLeft = $dataLeftRange['min'];
01012       $maxLeft = $dataLeftRange['max'];
01013       $minRight = $dataRightRange['min'];
01014       $maxRight = $dataRightRange['max'];
01015 
01016       $this->calculated['y_axis_left']['min']  = $minLeft;
01017       $this->calculated['y_axis_left']['max']  = $maxLeft;
01018       $this->calculated['y_axis_right']['min'] = $minRight;
01019       $this->calculated['y_axis_right']['max'] = $maxRight;
01020 
01021       $stepLeft = ($maxLeft - $minLeft) / ($this->parameter['y_axis_gridlines'] - 1);
01022       $startLeft = $minLeft;
01023       $step_right = ($maxRight - $minRight) / ($this->parameter['y_axis_gridlines'] - 1);
01024       $start_right = $minRight;
01025 
01026       if ($this->parameter['y_axis_text_left']) {
01027         for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
01028           // left y axis
01029           if ($y_tick_labels) {
01030             $value = $y_tick_labels[$i];
01031           } else {
01032             $value = number_format($startLeft, $this->parameter['y_decimal_left'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
01033           }
01034           $this->calculated['y_axis_left']['data'][$i]  = $startLeft;
01035           $this->calculated['y_axis_left']['text'][$i]  = $value; // text is formatted raw data
01036 
01037           $size = $this->get_boundaryBox(
01038             array('points' => $axis_size,
01039                   'font'   => $axis_font,
01040                   'angle'  => $axis_angle,
01041                   'colour' => $axis_colour,
01042                   'text'   => $value));
01043           $this->calculated['y_axis_left']['boundary_box'][$i] = $size;
01044 
01045           if ($size['height'] > $this->calculated['y_axis_left']['boundary_box_max']['height'])
01046             $this->calculated['y_axis_left']['boundary_box_max']['height'] = $size['height'];
01047           if ($size['width'] > $this->calculated['y_axis_left']['boundary_box_max']['width'])
01048             $this->calculated['y_axis_left']['boundary_box_max']['width'] = $size['width'];
01049 
01050           $startLeft += $stepLeft;
01051         }
01052         $this->calculated['boundary_box']['left'] += $this->calculated['y_axis_left']['boundary_box_max']['width']
01053                                                     + $this->parameter['y_inner_padding'];
01054       }
01055 
01056       if ($this->parameter['y_axis_text_right']) {
01057         for ($i = 0; $i < $this->parameter['y_axis_gridlines']; $i++) { // calculate y axis text sizes
01058           // right y axis
01059           $value = number_format($start_right, $this->parameter['y_decimal_right'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
01060           $this->calculated['y_axis_right']['data'][$i]  = $start_right;
01061           $this->calculated['y_axis_right']['text'][$i]  = $value; // text is formatted raw data
01062           $size = $this->get_boundaryBox(
01063             array('points' => $axis_size,
01064                   'font'   => $axis_font,
01065                   'angle'  => $axis_angle,
01066                   'colour' => $axis_colour,
01067                   'text'   => $value));
01068           $this->calculated['y_axis_right']['boundary_box'][$i] = $size;
01069 
01070           if ($size['height'] > $this->calculated['y_axis_right']['boundary_box_max']['height'])
01071             $this->calculated['y_axis_right']['boundary_box_max'] = $size;
01072           if ($size['width'] > $this->calculated['y_axis_right']['boundary_box_max']['width'])
01073             $this->calculated['y_axis_right']['boundary_box_max']['width'] = $size['width'];
01074 
01075           $start_right += $step_right;
01076         }
01077         $this->calculated['boundary_box']['right'] -= $this->calculated['y_axis_right']['boundary_box_max']['width']
01078                                                     + $this->parameter['y_inner_padding'];
01079       }
01080     }
01081 
01082     function init_x_axis() {
01083       $this->calculated['x_axis'] = array(); // array to hold calculated values for x_axis.
01084       $this->calculated['x_axis']['boundary_box_max'] = array('height' => 0, 'width' => 0);
01085 
01086       $axis_font       = $this->parameter['axis_font'];
01087       $axis_size       = $this->parameter['axis_size'];
01088       $axis_colour     = $this->parameter['axis_colour'];
01089       $axis_angle      = $this->parameter['x_axis_angle'];
01090 
01091       // check whether to treat x axis as numeric
01092       if ($this->parameter['x_axis_gridlines'] == 'auto') { // auto means text based x_axis, not numeric...
01093         $this->calculated['x_axis']['num_ticks'] = sizeof($this->x_data);
01094           $data = $this->x_data;
01095           for ($i=0; $i < $this->calculated['x_axis']['num_ticks']; $i++) {
01096             $value = array_shift($data); // grab value from begin of array
01097             $this->calculated['x_axis']['data'][$i]  = $value;
01098             $this->calculated['x_axis']['text'][$i]  = $value; // raw data and text are both the same in this case
01099             $size = $this->get_boundaryBox(
01100               array('points' => $axis_size,
01101                     'font'   => $axis_font,
01102                     'angle'  => $axis_angle,
01103                     'colour' => $axis_colour,
01104                     'text'   => $value));
01105             $this->calculated['x_axis']['boundary_box'][$i] = $size;
01106             if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
01107               $this->calculated['x_axis']['boundary_box_max'] = $size;
01108           }
01109 
01110       } else { // x axis is numeric so find max min values...
01111         $this->calculated['x_axis']['num_ticks'] = $this->parameter['x_axis_gridlines'];
01112 
01113         $min = $this->parameter['x_min'];
01114         $max = $this->parameter['x_max'];
01115         $data = array();
01116         $data = $this->find_range($this->x_data, $min, $max, $this->parameter['x_resolution']);
01117         $min = $data['min'];
01118         $max = $data['max'];
01119         $this->calculated['x_axis']['min'] = $min;
01120         $this->calculated['x_axis']['max'] = $max;
01121 
01122         $step = ($max - $min) / ($this->calculated['x_axis']['num_ticks'] - 1);
01123         $start = $min;
01124 
01125         for ($i = 0; $i < $this->calculated['x_axis']['num_ticks']; $i++) { // calculate x axis text sizes
01126           $value = number_format($start, $this->parameter['xDecimal'], $this->parameter['decimal_point'], $this->parameter['thousand_sep']);
01127           $this->calculated['x_axis']['data'][$i]  = $start;
01128           $this->calculated['x_axis']['text'][$i]  = $value; // text is formatted raw data
01129 
01130           $size = $this->get_boundaryBox(
01131             array('points' => $axis_size,
01132                   'font'   => $axis_font,
01133                   'angle'  => $axis_angle,
01134                   'colour' => $axis_colour,
01135                   'text'   => $value));
01136           $this->calculated['x_axis']['boundary_box'][$i] = $size;
01137 
01138           if ($size['height'] > $this->calculated['x_axis']['boundary_box_max']['height'])
01139             $this->calculated['x_axis']['boundary_box_max'] = $size;
01140 
01141           $start += $step;
01142         }
01143       }
01144       if ($this->parameter['x_axis_text'])
01145         $this->calculated['boundary_box']['bottom'] -= $this->calculated['x_axis']['boundary_box_max']['height']
01146                                                       + $this->parameter['x_inner_padding'];
01147     }
01148 
01149     // find max and min values for a data array given the resolution.
01150     function find_range($data, $min, $max, $resolution) {
01151       if (sizeof($data) == 0 ) return array('min' => 0, 'max' => 0);
01152       foreach ($data as $key => $value) {
01153         if ($value=='none') continue;
01154         if ($value > $max) $max = $value;
01155         if ($value < $min) $min = $value;
01156       }
01157 
01158       if ($max == 0) {
01159         $factor = 1;
01160       } else {
01161         if ($max < 0) $factor = - pow(10, (floor(log10(abs($max))) + $resolution) );
01162         else $factor = pow(10, (floor(log10(abs($max))) - $resolution) );
01163       }
01164       if ($factor > 0.1) { // To avoid some wierd rounding errors (Moodle)
01165         $factor = round($factor * 1000.0) / 1000.0; // To avoid some wierd rounding errors (Moodle)
01166       } // To avoid some wierd rounding errors (Moodle)
01167 
01168       $max = $factor * @ceil($max / $factor);
01169       $min = $factor * @floor($min / $factor);
01170 
01171       //print "max=$max, min=$min<br />";
01172 
01173       return array('min' => $min, 'max' => $max);
01174     }
01175 
01176     function graph() {
01177       if (func_num_args() == 2) {
01178         $this->parameter['width']  = func_get_arg(0);
01179         $this->parameter['height'] = func_get_arg(1);
01180       }
01181       //$this->boundaryBox  = array(
01182       $this->calculated['boundary_box'] = array(
01183         'left'      =>  0,
01184         'top'       =>  0,
01185         'right'     =>  $this->parameter['width'] - 1,
01186         'bottom'    =>  $this->parameter['height'] - 1);
01187 
01188       $this->init_colours();
01189 
01190       //ImageColorTransparent($this->image, $this->colour['white']); // colour for transparency
01191     }
01192 
01193     function print_TTF($message) {
01194       $points    = $message['points'];
01195       $angle     = $message['angle'];
01196       $text      = $message['text'];
01197       $colour    = $this->colour[$message['colour']];
01198       $font      = $this->parameter['path_to_fonts'].$message['font'];
01199 
01200       $x         = $message['boundary_box']['x'];
01201       $y         = $message['boundary_box']['y'];
01202       $offsetX   = $message['boundary_box']['offsetX'];
01203       $offsetY   = $message['boundary_box']['offsetY'];
01204       $height    = $message['boundary_box']['height'];
01205       $width     = $message['boundary_box']['width'];
01206       $reference = $message['boundary_box']['reference'];
01207 
01208       switch ($reference) {
01209         case 'top-left':
01210         case 'left-top':
01211           $y += $height - $offsetY;
01212           //$y += $offsetY;
01213           $x += $offsetX;
01214           break;
01215         case 'left-center':
01216           $y += ($height / 2) - $offsetY;
01217           $x += $offsetX;
01218           break;
01219         case 'left-bottom':
01220           $y -= $offsetY;
01221           $x += $offsetX;
01222          break;
01223         case 'top-center':
01224           $y += $height - $offsetY;
01225           $x -= ($width / 2) - $offsetX;
01226          break;
01227         case 'top-right':
01228         case 'right-top':
01229           $y += $height - $offsetY;
01230           $x -= $width  - $offsetX;
01231           break;
01232         case 'right-center':
01233           $y += ($height / 2) - $offsetY;
01234           $x -= $width  - $offsetX;
01235           break;
01236         case 'right-bottom':
01237           $y -= $offsetY;
01238           $x -= $width  - $offsetX;
01239           break;
01240         case 'bottom-center':
01241           $y -= $offsetY;
01242           $x -= ($width / 2) - $offsetX;
01243          break;
01244         default:
01245           $y = 0;
01246           $x = 0;
01247           break;
01248       }
01249       // start of Moodle addition
01250       $textlib = textlib_get_instance();
01251       $text = $textlib->utf8_to_entities($text, true, true); //does not work with hex entities!
01252       // end of Moodle addition
01253       ImageTTFText($this->image, $points, $angle, $x, $y, $colour, $font, $text);
01254     }
01255 
01256     // move boundaryBox to coordinates specified
01257     function update_boundaryBox(&$boundaryBox, $coords) {
01258       $width      = $boundaryBox['width'];
01259       $height     = $boundaryBox['height'];
01260       $x          = $coords['x'];
01261       $y          = $coords['y'];
01262       $reference  = $coords['reference'];
01263       switch ($reference) {
01264         case 'top-left':
01265         case 'left-top':
01266           $top    = $y;
01267           $bottom = $y + $height;
01268           $left   = $x;
01269           $right  = $x + $width;
01270           break;
01271         case 'left-center':
01272           $top    = $y - ($height / 2);
01273           $bottom = $y + ($height / 2);
01274           $left   = $x;
01275           $right  = $x + $width;
01276           break;
01277         case 'left-bottom':
01278           $top    = $y - $height;
01279           $bottom = $y;
01280           $left   = $x;
01281           $right  = $x + $width;
01282           break;
01283         case 'top-center':
01284           $top    = $y;
01285           $bottom = $y + $height;
01286           $left   = $x - ($width / 2);
01287           $right  = $x + ($width / 2);
01288           break;
01289         case 'right-top':
01290         case 'top-right':
01291           $top    = $y;
01292           $bottom = $y + $height;
01293           $left   = $x - $width;
01294           $right  = $x;
01295           break;
01296         case 'right-center':
01297           $top    = $y - ($height / 2);
01298           $bottom = $y + ($height / 2);
01299           $left   = $x - $width;
01300           $right  = $x;
01301           break;
01302         case 'bottom=right':
01303         case 'right-bottom':
01304           $top    = $y - $height;
01305           $bottom = $y;
01306           $left   = $x - $width;
01307           $right  = $x;
01308           break;
01309         default:
01310           $top    = 0;
01311           $bottom = $height;
01312           $left   = 0;
01313           $right  = $width;
01314           break;
01315       }
01316 
01317       $boundaryBox = array_merge($boundaryBox, array('top'       => $top,
01318                                                      'bottom'    => $bottom,
01319                                                      'left'      => $left,
01320                                                      'right'     => $right,
01321                                                      'x'         => $x,
01322                                                      'y'         => $y,
01323                                                      'reference' => $reference));
01324     }
01325 
01326     function get_null_size() {
01327       return array('width'      => 0,
01328                    'height'     => 0,
01329                    'offsetX'    => 0,
01330                    'offsetY'    => 0,
01331                    //'fontHeight' => 0
01332                    );
01333     }
01334 
01335     function get_boundaryBox($message) {
01336       $points  = $message['points'];
01337       $angle   = $message['angle'];
01338       $font    = $this->parameter['path_to_fonts'].$message['font'];
01339       $text    = $message['text'];
01340 
01341       //print ('get_boundaryBox');
01342       //expandPre($message);
01343 
01344       // get font size
01345       $bounds = ImageTTFBBox($points, $angle, $font, "W");
01346       if ($angle < 0) {
01347         $fontHeight = abs($bounds[7]-$bounds[1]);
01348       } else if ($angle > 0) {
01349         $fontHeight = abs($bounds[1]-$bounds[7]);
01350       } else {
01351         $fontHeight = abs($bounds[7]-$bounds[1]);
01352       }
01353 
01354       // get boundary box and offsets for printing at an angle
01355       // start of Moodle addition
01356       $textlib = textlib_get_instance();
01357       $text = $textlib->utf8_to_entities($text, true, true); //gd does not work with hex entities!
01358       // end of Moodle addition
01359       $bounds = ImageTTFBBox($points, $angle, $font, $text);
01360 
01361       if ($angle < 0) {
01362         $width = abs($bounds[4]-$bounds[0]);
01363         $height = abs($bounds[3]-$bounds[7]);
01364         $offsetY = abs($bounds[3]-$bounds[1]);
01365         $offsetX = 0;
01366 
01367       } else if ($angle > 0) {
01368         $width = abs($bounds[2]-$bounds[6]);
01369         $height = abs($bounds[1]-$bounds[5]);
01370         $offsetY = 0;
01371         $offsetX = abs($bounds[0]-$bounds[6]);
01372 
01373       } else {
01374         $width = abs($bounds[4]-$bounds[6]);
01375         $height = abs($bounds[7]-$bounds[1]);
01376         $offsetY = $bounds[1];
01377         $offsetX = 0;
01378       }
01379 
01380       //return values
01381       return array('width'      => $width,
01382                    'height'     => $height,
01383                    'offsetX'    => $offsetX,
01384                    'offsetY'    => $offsetY,
01385                    //'fontHeight' => $fontHeight
01386                    );
01387     }
01388 
01389     function draw_rectangle($border, $colour, $type) {
01390       $colour = $this->colour[$colour];
01391       switch ($type) {
01392         case 'fill':    // fill the rectangle
01393           ImageFilledRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
01394           break;
01395         case 'box':     // all sides
01396           ImageRectangle($this->image, $border['left'], $border['top'], $border['right'], $border['bottom'], $colour);
01397           break;
01398         case 'axis':    // bottom x axis and left y axis
01399           ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
01400           ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
01401           break;
01402         case 'y':       // left y axis only
01403         case 'y-left':
01404           ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
01405           break;
01406         case 'y-right': // right y axis only
01407           ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
01408           break;
01409         case 'x':       // bottom x axis only
01410           ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
01411           break;
01412         case 'u':       // u shaped. bottom x axis and both left and right y axis.
01413           ImageLine($this->image, $border['left'], $border['top'], $border['left'], $border['bottom'], $colour);
01414           ImageLine($this->image, $border['right'], $border['top'], $border['right'], $border['bottom'], $colour);
01415           ImageLine($this->image, $border['left'], $border['bottom'], $border['right'], $border['bottom'], $colour);
01416           break;
01417 
01418       }
01419     }
01420 
01421     function init_colours() {
01422       $this->image              = ImageCreate($this->parameter['width'], $this->parameter['height']);
01423       // standard colours
01424       $this->colour['white']    = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF); // first colour is background colour.
01425       $this->colour['black']    = ImageColorAllocate ($this->image, 0x00, 0x00, 0x00);
01426       $this->colour['maroon']   = ImageColorAllocate ($this->image, 0x80, 0x00, 0x00);
01427       $this->colour['green']    = ImageColorAllocate ($this->image, 0x00, 0x80, 0x00);
01428       $this->colour['ltgreen']  = ImageColorAllocate ($this->image, 0x52, 0xF1, 0x7F);
01429       $this->colour['ltltgreen']= ImageColorAllocate ($this->image, 0x99, 0xFF, 0x99);
01430       $this->colour['olive']    = ImageColorAllocate ($this->image, 0x80, 0x80, 0x00);
01431       $this->colour['navy']     = ImageColorAllocate ($this->image, 0x00, 0x00, 0x80);
01432       $this->colour['purple']   = ImageColorAllocate ($this->image, 0x80, 0x00, 0x80);
01433       $this->colour['gray']     = ImageColorAllocate ($this->image, 0x80, 0x80, 0x80);
01434       $this->colour['red']      = ImageColorAllocate ($this->image, 0xFF, 0x00, 0x00);
01435       $this->colour['ltred']    = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x99);
01436       $this->colour['ltltred']  = ImageColorAllocate ($this->image, 0xFF, 0xCC, 0xCC);
01437       $this->colour['orange']   = ImageColorAllocate ($this->image, 0xFF, 0x66, 0x00);
01438       $this->colour['ltorange']   = ImageColorAllocate ($this->image, 0xFF, 0x99, 0x66);
01439       $this->colour['ltltorange'] = ImageColorAllocate ($this->image, 0xFF, 0xcc, 0x99);
01440       $this->colour['lime']     = ImageColorAllocate ($this->image, 0x00, 0xFF, 0x00);
01441       $this->colour['yellow']   = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0x00);
01442       $this->colour['blue']     = ImageColorAllocate ($this->image, 0x00, 0x00, 0xFF);
01443       $this->colour['ltblue']   = ImageColorAllocate ($this->image, 0x00, 0xCC, 0xFF);
01444       $this->colour['ltltblue'] = ImageColorAllocate ($this->image, 0x99, 0xFF, 0xFF);
01445       $this->colour['fuchsia']  = ImageColorAllocate ($this->image, 0xFF, 0x00, 0xFF);
01446       $this->colour['aqua']     = ImageColorAllocate ($this->image, 0x00, 0xFF, 0xFF);
01447       //$this->colour['white']    = ImageColorAllocate ($this->image, 0xFF, 0xFF, 0xFF);
01448       // shades of gray
01449       $this->colour['grayF0']   = ImageColorAllocate ($this->image, 0xF0, 0xF0, 0xF0);
01450       $this->colour['grayEE']   = ImageColorAllocate ($this->image, 0xEE, 0xEE, 0xEE);
01451       $this->colour['grayDD']   = ImageColorAllocate ($this->image, 0xDD, 0xDD, 0xDD);
01452       $this->colour['grayCC']   = ImageColorAllocate ($this->image, 0xCC, 0xCC, 0xCC);
01453       $this->colour['gray33']   = ImageColorAllocate ($this->image, 0x33, 0x33, 0x33);
01454       $this->colour['gray66']   = ImageColorAllocate ($this->image, 0x66, 0x66, 0x66);
01455       $this->colour['gray99']   = ImageColorAllocate ($this->image, 0x99, 0x99, 0x99);
01456 
01457       $this->colour['none']   = 'none';
01458       return true;
01459     }
01460 
01461     function output() {
01462       if ($this->debug) { // for debugging purposes.
01463         //expandPre($this->graph);
01464         //expandPre($this->y_data);
01465         //expandPre($this->x_data);
01466         //expandPre($this->parameter);
01467       } else {
01468 
01469         $expiresSeconds = $this->parameter['seconds_to_live'];
01470         $expiresHours = $this->parameter['hours_to_live'];
01471 
01472         if ($expiresHours || $expiresSeconds) {
01473           $now = mktime (date("H"),date("i"),date("s"),date("m"),date("d"),date("Y"));
01474           $expires = mktime (date("H")+$expiresHours,date("i"),date("s")+$expiresSeconds,date("m"),date("d"),date("Y"));
01475           $expiresGMT = gmdate('D, d M Y H:i:s', $expires).' GMT';
01476           $lastModifiedGMT  = gmdate('D, d M Y H:i:s', $now).' GMT';
01477 
01478           Header('Last-modified: '.$lastModifiedGMT);
01479           Header('Expires: '.$expiresGMT);
01480         }
01481 
01482         if ($this->parameter['file_name'] == 'none') {
01483           switch ($this->parameter['output_format']) {
01484             case 'GIF':
01485               Header("Content-type: image/gif");  // GIF??. switch to PNG guys!!
01486               ImageGIF($this->image);
01487               break;
01488             case 'JPEG':
01489               Header("Content-type: image/jpeg"); // JPEG for line art??. included for completeness.
01490               ImageJPEG($this->image);
01491               break;
01492            default:
01493               Header("Content-type: image/png");  // preferred output format
01494               ImagePNG($this->image);
01495               break;
01496           }
01497         } else {
01498            switch ($this->parameter['output_format']) {
01499             case 'GIF':
01500               ImageGIF($this->image, $this->parameter['file_name'].'.gif');
01501               break;
01502             case 'JPEG':
01503               ImageJPEG($this->image, $this->parameter['file_name'].'.jpg');
01504               break;
01505            default:
01506               ImagePNG($this->image, $this->parameter['file_name'].'.png');
01507               break;
01508           }
01509         }
01510 
01511         ImageDestroy($this->image);
01512       }
01513     } // function output
01514 
01515     function init_variable(&$variable, $value, $default) {
01516       if (!empty($value)) $variable = $value;
01517       else if (isset($default)) $variable = $default;
01518       else unset($variable);
01519     }
01520 
01521     // plot a point. options include square, circle, diamond, triangle, and dot. offset is used for drawing shadows.
01522     // for diamonds and triangles the size should be an even number to get nice look. if odd the points are crooked.
01523     function plot($x, $y, $type, $size, $colour, $offset) {
01524       //print("drawing point of type: $type, at offset: $offset");
01525       $u = $x + $offset;
01526       $v = $this->calculated['inner_border']['bottom'] - $y + $offset;
01527       $half = $size / 2;
01528 
01529       switch ($type) {
01530         case 'square':
01531           ImageFilledRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
01532           break;
01533         case 'square-open':
01534           ImageRectangle($this->image, $u-$half, $v-$half, $u+$half, $v+$half, $this->colour[$colour]);
01535           break;
01536         case 'circle':
01537           ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
01538           ImageFillToBorder($this->image, $u, $v, $this->colour[$colour], $this->colour[$colour]);
01539           break;
01540         case 'circle-open':
01541           ImageArc($this->image, $u, $v, $size, $size, 0, 360, $this->colour[$colour]);
01542           break;
01543         case 'diamond':
01544           ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
01545           break;
01546         case 'diamond-open':
01547           ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v, $u, $v+$half, $u-$half, $v), 4, $this->colour[$colour]);
01548           break;
01549         case 'triangle':
01550           ImageFilledPolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
01551           break;
01552         case 'triangle-open':
01553           ImagePolygon($this->image, array($u, $v-$half, $u+$half, $v+$half, $u-$half, $v+$half), 3, $this->colour[$colour]);
01554           break;
01555         case 'dot':
01556           ImageSetPixel($this->image, $u, $v, $this->colour[$colour]);
01557           break;
01558       }
01559     }
01560 
01561     function bar($x, $y, $type, $size, $colour, $offset, $index, $yoffset) {
01562       $index_offset = $this->calculated['bar_offset_index'][$index];
01563       if ( $yoffset ) {
01564         $bar_offsetx = 0;
01565       } else {
01566         $bar_offsetx = $this->calculated['bar_offset_x'][$index_offset];
01567       }
01568       //$this->dbug("drawing bar at offset = $offset : index = $index: bar_offsetx = $bar_offsetx");
01569 
01570       $span = ($this->calculated['bar_width'] * $size) / 2;
01571       $x_left  = $x + $bar_offsetx - $span;
01572       $x_right = $x + $bar_offsetx + $span;
01573 
01574       if ($this->parameter['zero_axis'] != 'none') {
01575         $zero = $this->calculated['zero_axis'];
01576         if ($this->parameter['shadow_below_axis'] ) $zero  += $offset;
01577         $u_left  = $x_left + $offset;
01578         $u_right = $x_right + $offset - 1;
01579         $v       = $this->calculated['boundary_box']['bottom'] - $y + $offset;
01580 
01581         if ($v > $zero) {
01582           $top = $zero +1;
01583           $bottom = $v;
01584         } else {
01585           $top = $v;
01586           $bottom = $zero - 1;
01587         }
01588 
01589         switch ($type) {
01590           case 'open':
01591             //ImageRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
01592             if ($v > $zero)
01593               ImageRectangle($this->image, round($u_left), $bottom, round($u_right), $bottom, $this->colour[$colour]);
01594             else
01595               ImageRectangle($this->image, round($u_left), $top, round($u_right), $top, $this->colour[$colour]);
01596             ImageRectangle($this->image, round($u_left), $top, round($u_left), $bottom, $this->colour[$colour]);
01597             ImageRectangle($this->image, round($u_right), $top, round($u_right), $bottom, $this->colour[$colour]);
01598             break;
01599           case 'fill':
01600             ImageFilledRectangle($this->image, round($u_left), $top, round($u_right), $bottom, $this->colour[$colour]);
01601             break;
01602         }
01603 
01604       } else {
01605 
01606         $bottom = $this->calculated['boundary_box']['bottom'];
01607         if ($this->parameter['shadow_below_axis'] ) $bottom  += $offset;
01608         if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
01609         $u_left  = $x_left + $offset;
01610         $u_right = $x_right + $offset - 1;
01611         $v       = $this->calculated['boundary_box']['bottom'] - $y + $offset;
01612 
01613         // Moodle addition, plus the function parameter yoffset
01614         if ($yoffset) {                                           // Moodle
01615             $yoffset = $yoffset - round(($bottom - $v) / 2.0);    // Moodle
01616             $bottom -= $yoffset;                                  // Moodle
01617             $v      -= $yoffset;                                  // Moodle
01618         }                                                         // Moodle
01619 
01620         switch ($type) {
01621           case 'open':
01622             ImageRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
01623             break;
01624           case 'fill':
01625             ImageFilledRectangle($this->image, round($u_left), $v, round($u_right), $bottom, $this->colour[$colour]);
01626             break;
01627         }
01628       }
01629     }
01630 
01631     function area($x_start, $y_start, $x_end, $y_end, $type, $colour, $offset) {
01632       //dbug("drawing area type: $type, at offset: $offset");
01633       if ($this->parameter['zero_axis'] != 'none') {
01634         $bottom = $this->calculated['boundary_box']['bottom'];
01635         $zero   = $this->calculated['zero_axis'];
01636         if ($this->parameter['shadow_below_axis'] ) $zero  += $offset;
01637         $u_start = $x_start + $offset;
01638         $u_end   = $x_end + $offset;
01639         $v_start = $bottom - $y_start + $offset;
01640         $v_end   = $bottom - $y_end + $offset;
01641         switch ($type) {
01642           case 'fill':
01643             // draw it this way 'cos the FilledPolygon routine seems a bit buggy.
01644             ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
01645             ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
01646            break;
01647           case 'open':
01648             //ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $zero, $u_start, $zero), 4, $this->colour[$colour]);
01649             ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
01650             ImageLine($this->image, $u_start, $v_start, $u_start, $zero, $this->colour[$colour]);
01651             ImageLine($this->image, $u_end, $v_end, $u_end, $zero, $this->colour[$colour]);
01652            break;
01653         }
01654       } else {
01655         $bottom = $this->calculated['boundary_box']['bottom'];
01656         $u_start = $x_start + $offset;
01657         $u_end   = $x_end + $offset;
01658         $v_start = $bottom - $y_start + $offset;
01659         $v_end   = $bottom - $y_end + $offset;
01660 
01661         if ($this->parameter['shadow_below_axis'] ) $bottom  += $offset;
01662         if ($this->parameter['inner_border'] != 'none') $bottom -= 1; // 1 pixel above bottom if border is to be drawn.
01663         switch ($type) {
01664           case 'fill':
01665             ImageFilledPolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
01666            break;
01667           case 'open':
01668             ImagePolygon($this->image, array($u_start, $v_start, $u_end, $v_end, $u_end, $bottom, $u_start, $bottom), 4, $this->colour[$colour]);
01669            break;
01670         }
01671       }
01672     }
01673 
01674     function line($x_start, $y_start, $x_end, $y_end, $type, $brush_type, $brush_size, $colour, $offset) {
01675       //dbug("drawing line of type: $type, at offset: $offset");
01676       $u_start = $x_start + $offset;
01677       $v_start = $this->calculated['boundary_box']['bottom'] - $y_start + $offset;
01678       $u_end   = $x_end + $offset;
01679       $v_end   = $this->calculated['boundary_box']['bottom'] - $y_end + $offset;
01680 
01681       switch ($type) {
01682         case 'brush':
01683           $this->draw_brush_line($u_start, $v_start, $u_end, $v_end, $brush_size, $brush_type, $colour);
01684          break;
01685         case 'line' :
01686           ImageLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
01687           break;
01688         case 'dash':
01689           ImageDashedLine($this->image, $u_start, $v_start, $u_end, $v_end, $this->colour[$colour]);
01690           break;
01691       }
01692     }
01693 
01694     // function to draw line. would prefer to use gdBrush but this is not supported yet.
01695     function draw_brush_line($x0, $y0, $x1, $y1, $size, $type, $colour) {
01696       //$this->dbug("line: $x0, $y0, $x1, $y1");
01697       $dy = $y1 - $y0;
01698       $dx = $x1 - $x0;
01699       $t = 0;
01700       $watchdog = 1024; // precaution to prevent infinite loops.
01701 
01702       $this->draw_brush($x0, $y0, $size, $type, $colour);
01703       if (abs($dx) > abs($dy)) { // slope < 1
01704         //$this->dbug("slope < 1");
01705         $m = $dy / $dx; // compute slope
01706         $t += $y0;
01707         $dx = ($dx < 0) ? -1 : 1;
01708         $m *= $dx;
01709         while (round($x0) != round($x1)) {
01710           if (!$watchdog--) break;
01711           $x0 += $dx; // step to next x value
01712           $t += $m;   // add slope to y value
01713           $y = round($t);
01714           //$this->dbug("x0=$x0, x1=$x1, y=$y watchdog=$watchdog");
01715           $this->draw_brush($x0, $y, $size, $type, $colour);
01716 
01717         }
01718       } else { // slope >= 1
01719         //$this->dbug("slope >= 1");
01720         $m = $dx / $dy; // compute slope
01721         $t += $x0;
01722         $dy = ($dy < 0) ? -1 : 1;
01723         $m *= $dy;
01724         while (round($y0) != round($y1)) {
01725           if (!$watchdog--) break;
01726           $y0 += $dy; // step to next y value
01727           $t += $m;   // add slope to x value
01728           $x = round($t);
01729           //$this->dbug("x=$x, y0=$y0, y1=$y1 watchdog=$watchdog");
01730           $this->draw_brush($x, $y0, $size, $type, $colour);
01731 
01732         }
01733       }
01734     }
01735 
01736     function draw_brush($x, $y, $size, $type, $colour) {
01737       $x = round($x);
01738       $y = round($y);
01739       $half = round($size / 2);
01740       switch ($type) {
01741         case 'circle':
01742           ImageArc($this->image, $x, $y, $size, $size, 0, 360, $this->colour[$colour]);
01743           ImageFillToBorder($this->image, $x, $y, $this->colour[$colour], $this->colour[$colour]);
01744           break;
01745         case 'square':
01746           ImageFilledRectangle($this->image, $x-$half, $y-$half, $x+$half, $y+$half, $this->colour[$colour]);
01747           break;
01748         case 'vertical':
01749           ImageFilledRectangle($this->image, $x, $y-$half, $x+1, $y+$half, $this->colour[$colour]);
01750           break;
01751         case 'horizontal':
01752           ImageFilledRectangle($this->image, $x-$half, $y, $x+$half, $y+1, $this->colour[$colour]);
01753           break;
01754         case 'slash':
01755           ImageFilledPolygon($this->image, array($x+$half, $y-$half,
01756                                                  $x+$half+1, $y-$half,
01757                                                  $x-$half+1, $y+$half,
01758                                                  $x-$half, $y+$half
01759                                                  ), 4, $this->colour[$colour]);
01760           break;
01761         case 'backslash':
01762           ImageFilledPolygon($this->image, array($x-$half, $y-$half,
01763                                                  $x-$half+1, $y-$half,
01764                                                  $x+$half+1, $y+$half,
01765                                                  $x+$half, $y+$half
01766                                                  ), 4, $this->colour[$colour]);
01767           break;
01768         default:
01769           @eval($type); // user can create own brush script.
01770       }
01771     }
01772 
01773 } // class graph
 All Data Structures Namespaces Files Functions Variables Enumerations