Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
   4  
   5  use PhpOffice\PhpSpreadsheet\Chart\Chart;
   6  use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
   7  
   8  require_once  __DIR__ . '/Polyfill.php';
   9  
  10  class JpGraph implements IRenderer
  11  {
  12      private static $width = 640;
  13  
  14      private static $height = 480;
  15  
  16      private static $colourSet = [
  17          'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1',
  18          'darkmagenta', 'coral', 'dodgerblue3', 'eggplant',
  19          'mediumblue', 'magenta', 'sandybrown', 'cyan',
  20          'firebrick1', 'forestgreen', 'deeppink4', 'darkolivegreen',
  21          'goldenrod2',
  22      ];
  23  
  24      private static $markSet;
  25  
  26      private $chart;
  27  
  28      private $graph;
  29  
  30      private static $plotColour = 0;
  31  
  32      private static $plotMark = 0;
  33  
  34      /**
  35       * Create a new jpgraph.
  36       *
  37       * @param Chart $chart
  38       */
  39      public function __construct(Chart $chart)
  40      {
  41          self::init();
  42          $this->graph = null;
  43          $this->chart = $chart;
  44      }
  45  
  46      private static function init()
  47      {
  48          static $loaded = false;
  49          if ($loaded) {
  50              return;
  51          }
  52  
  53          \JpGraph\JpGraph::load();
  54          \JpGraph\JpGraph::module('bar');
  55          \JpGraph\JpGraph::module('contour');
  56          \JpGraph\JpGraph::module('line');
  57          \JpGraph\JpGraph::module('pie');
  58          \JpGraph\JpGraph::module('pie3d');
  59          \JpGraph\JpGraph::module('radar');
  60          \JpGraph\JpGraph::module('regstat');
  61          \JpGraph\JpGraph::module('scatter');
  62          \JpGraph\JpGraph::module('stock');
  63  
  64          self::$markSet = [
  65              'diamond' => MARK_DIAMOND,
  66              'square' => MARK_SQUARE,
  67              'triangle' => MARK_UTRIANGLE,
  68              'x' => MARK_X,
  69              'star' => MARK_STAR,
  70              'dot' => MARK_FILLEDCIRCLE,
  71              'dash' => MARK_DTRIANGLE,
  72              'circle' => MARK_CIRCLE,
  73              'plus' => MARK_CROSS,
  74          ];
  75  
  76          $loaded = true;
  77      }
  78  
  79      private function formatPointMarker($seriesPlot, $markerID)
  80      {
  81          $plotMarkKeys = array_keys(self::$markSet);
  82          if ($markerID === null) {
  83              //    Use default plot marker (next marker in the series)
  84              self::$plotMark %= count(self::$markSet);
  85              $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
  86          } elseif ($markerID !== 'none') {
  87              //    Use specified plot marker (if it exists)
  88              if (isset(self::$markSet[$markerID])) {
  89                  $seriesPlot->mark->SetType(self::$markSet[$markerID]);
  90              } else {
  91                  //    If the specified plot marker doesn't exist, use default plot marker (next marker in the series)
  92                  self::$plotMark %= count(self::$markSet);
  93                  $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
  94              }
  95          } else {
  96              //    Hide plot marker
  97              $seriesPlot->mark->Hide();
  98          }
  99          $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]);
 100          $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]);
 101          $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
 102  
 103          return $seriesPlot;
 104      }
 105  
 106      private function formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation = '')
 107      {
 108          $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode();
 109          if ($datasetLabelFormatCode !== null) {
 110              //    Retrieve any label formatting code
 111              $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode);
 112          }
 113  
 114          $testCurrentIndex = 0;
 115          foreach ($datasetLabels as $i => $datasetLabel) {
 116              if (is_array($datasetLabel)) {
 117                  if ($rotation == 'bar') {
 118                      $datasetLabels[$i] = implode(' ', $datasetLabel);
 119                  } else {
 120                      $datasetLabel = array_reverse($datasetLabel);
 121                      $datasetLabels[$i] = implode("\n", $datasetLabel);
 122                  }
 123              } else {
 124                  //    Format labels according to any formatting code
 125                  if ($datasetLabelFormatCode !== null) {
 126                      $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode);
 127                  }
 128              }
 129              ++$testCurrentIndex;
 130          }
 131  
 132          return $datasetLabels;
 133      }
 134  
 135      private function percentageSumCalculation($groupID, $seriesCount)
 136      {
 137          $sumValues = [];
 138          //    Adjust our values to a percentage value across all series in the group
 139          for ($i = 0; $i < $seriesCount; ++$i) {
 140              if ($i == 0) {
 141                  $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
 142              } else {
 143                  $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
 144                  foreach ($nextValues as $k => $value) {
 145                      if (isset($sumValues[$k])) {
 146                          $sumValues[$k] += $value;
 147                      } else {
 148                          $sumValues[$k] = $value;
 149                      }
 150                  }
 151              }
 152          }
 153  
 154          return $sumValues;
 155      }
 156  
 157      private function percentageAdjustValues($dataValues, $sumValues)
 158      {
 159          foreach ($dataValues as $k => $dataValue) {
 160              $dataValues[$k] = $dataValue / $sumValues[$k] * 100;
 161          }
 162  
 163          return $dataValues;
 164      }
 165  
 166      private function getCaption($captionElement)
 167      {
 168          //    Read any caption
 169          $caption = ($captionElement !== null) ? $captionElement->getCaption() : null;
 170          //    Test if we have a title caption to display
 171          if ($caption !== null) {
 172              //    If we do, it could be a plain string or an array
 173              if (is_array($caption)) {
 174                  //    Implode an array to a plain string
 175                  $caption = implode('', $caption);
 176              }
 177          }
 178  
 179          return $caption;
 180      }
 181  
 182      private function renderTitle()
 183      {
 184          $title = $this->getCaption($this->chart->getTitle());
 185          if ($title !== null) {
 186              $this->graph->title->Set($title);
 187          }
 188      }
 189  
 190      private function renderLegend()
 191      {
 192          $legend = $this->chart->getLegend();
 193          if ($legend !== null) {
 194              $legendPosition = $legend->getPosition();
 195              switch ($legendPosition) {
 196                  case 'r':
 197                      $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); //    right
 198                      $this->graph->legend->SetColumns(1);
 199  
 200                      break;
 201                  case 'l':
 202                      $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); //    left
 203                      $this->graph->legend->SetColumns(1);
 204  
 205                      break;
 206                  case 't':
 207                      $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); //    top
 208                      break;
 209                  case 'b':
 210                      $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); //    bottom
 211                      break;
 212                  default:
 213                      $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); //    top-right
 214                      $this->graph->legend->SetColumns(1);
 215  
 216                      break;
 217              }
 218          } else {
 219              $this->graph->legend->Hide();
 220          }
 221      }
 222  
 223      private function renderCartesianPlotArea($type = 'textlin')
 224      {
 225          $this->graph = new \Graph(self::$width, self::$height);
 226          $this->graph->SetScale($type);
 227  
 228          $this->renderTitle();
 229  
 230          //    Rotate for bar rather than column chart
 231          $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection();
 232          $reverse = $rotation == 'bar';
 233  
 234          $xAxisLabel = $this->chart->getXAxisLabel();
 235          if ($xAxisLabel !== null) {
 236              $title = $this->getCaption($xAxisLabel);
 237              if ($title !== null) {
 238                  $this->graph->xaxis->SetTitle($title, 'center');
 239                  $this->graph->xaxis->title->SetMargin(35);
 240                  if ($reverse) {
 241                      $this->graph->xaxis->title->SetAngle(90);
 242                      $this->graph->xaxis->title->SetMargin(90);
 243                  }
 244              }
 245          }
 246  
 247          $yAxisLabel = $this->chart->getYAxisLabel();
 248          if ($yAxisLabel !== null) {
 249              $title = $this->getCaption($yAxisLabel);
 250              if ($title !== null) {
 251                  $this->graph->yaxis->SetTitle($title, 'center');
 252                  if ($reverse) {
 253                      $this->graph->yaxis->title->SetAngle(0);
 254                      $this->graph->yaxis->title->SetMargin(-55);
 255                  }
 256              }
 257          }
 258      }
 259  
 260      private function renderPiePlotArea()
 261      {
 262          $this->graph = new \PieGraph(self::$width, self::$height);
 263  
 264          $this->renderTitle();
 265      }
 266  
 267      private function renderRadarPlotArea()
 268      {
 269          $this->graph = new \RadarGraph(self::$width, self::$height);
 270          $this->graph->SetScale('lin');
 271  
 272          $this->renderTitle();
 273      }
 274  
 275      private function renderPlotLine($groupID, $filled = false, $combination = false, $dimensions = '2d')
 276      {
 277          $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
 278  
 279          $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
 280          if ($labelCount > 0) {
 281              $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
 282              $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
 283              $this->graph->xaxis->SetTickLabels($datasetLabels);
 284          }
 285  
 286          $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
 287          $seriesPlots = [];
 288          if ($grouping == 'percentStacked') {
 289              $sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
 290          }
 291  
 292          //    Loop through each data series in turn
 293          for ($i = 0; $i < $seriesCount; ++$i) {
 294              $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
 295              $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
 296  
 297              if ($grouping == 'percentStacked') {
 298                  $dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
 299              }
 300  
 301              //    Fill in any missing values in the $dataValues array
 302              $testCurrentIndex = 0;
 303              foreach ($dataValues as $k => $dataValue) {
 304                  while ($k != $testCurrentIndex) {
 305                      $dataValues[$testCurrentIndex] = null;
 306                      ++$testCurrentIndex;
 307                  }
 308                  ++$testCurrentIndex;
 309              }
 310  
 311              $seriesPlot = new \LinePlot($dataValues);
 312              if ($combination) {
 313                  $seriesPlot->SetBarCenter();
 314              }
 315  
 316              if ($filled) {
 317                  $seriesPlot->SetFilled(true);
 318                  $seriesPlot->SetColor('black');
 319                  $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
 320              } else {
 321                  //    Set the appropriate plot marker
 322                  $this->formatPointMarker($seriesPlot, $marker);
 323              }
 324              $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
 325              $seriesPlot->SetLegend($dataLabel);
 326  
 327              $seriesPlots[] = $seriesPlot;
 328          }
 329  
 330          if ($grouping == 'standard') {
 331              $groupPlot = $seriesPlots;
 332          } else {
 333              $groupPlot = new \AccLinePlot($seriesPlots);
 334          }
 335          $this->graph->Add($groupPlot);
 336      }
 337  
 338      private function renderPlotBar($groupID, $dimensions = '2d')
 339      {
 340          $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection();
 341          //    Rotate for bar rather than column chart
 342          if (($groupID == 0) && ($rotation == 'bar')) {
 343              $this->graph->Set90AndMargin();
 344          }
 345          $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
 346  
 347          $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
 348          if ($labelCount > 0) {
 349              $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
 350              $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation);
 351              //    Rotate for bar rather than column chart
 352              if ($rotation == 'bar') {
 353                  $datasetLabels = array_reverse($datasetLabels);
 354                  $this->graph->yaxis->SetPos('max');
 355                  $this->graph->yaxis->SetLabelAlign('center', 'top');
 356                  $this->graph->yaxis->SetLabelSide(SIDE_RIGHT);
 357              }
 358              $this->graph->xaxis->SetTickLabels($datasetLabels);
 359          }
 360  
 361          $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
 362          $seriesPlots = [];
 363          if ($grouping == 'percentStacked') {
 364              $sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
 365          }
 366  
 367          //    Loop through each data series in turn
 368          for ($j = 0; $j < $seriesCount; ++$j) {
 369              $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
 370              if ($grouping == 'percentStacked') {
 371                  $dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
 372              }
 373  
 374              //    Fill in any missing values in the $dataValues array
 375              $testCurrentIndex = 0;
 376              foreach ($dataValues as $k => $dataValue) {
 377                  while ($k != $testCurrentIndex) {
 378                      $dataValues[$testCurrentIndex] = null;
 379                      ++$testCurrentIndex;
 380                  }
 381                  ++$testCurrentIndex;
 382              }
 383  
 384              //    Reverse the $dataValues order for bar rather than column chart
 385              if ($rotation == 'bar') {
 386                  $dataValues = array_reverse($dataValues);
 387              }
 388              $seriesPlot = new \BarPlot($dataValues);
 389              $seriesPlot->SetColor('black');
 390              $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
 391              if ($dimensions == '3d') {
 392                  $seriesPlot->SetShadow();
 393              }
 394              if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) {
 395                  $dataLabel = '';
 396              } else {
 397                  $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue();
 398              }
 399              $seriesPlot->SetLegend($dataLabel);
 400  
 401              $seriesPlots[] = $seriesPlot;
 402          }
 403          //    Reverse the plot order for bar rather than column chart
 404          if (($rotation == 'bar') && ($grouping != 'percentStacked')) {
 405              $seriesPlots = array_reverse($seriesPlots);
 406          }
 407  
 408          if ($grouping == 'clustered') {
 409              $groupPlot = new \GroupBarPlot($seriesPlots);
 410          } elseif ($grouping == 'standard') {
 411              $groupPlot = new \GroupBarPlot($seriesPlots);
 412          } else {
 413              $groupPlot = new \AccBarPlot($seriesPlots);
 414              if ($dimensions == '3d') {
 415                  $groupPlot->SetShadow();
 416              }
 417          }
 418  
 419          $this->graph->Add($groupPlot);
 420      }
 421  
 422      private function renderPlotScatter($groupID, $bubble)
 423      {
 424          $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
 425          $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
 426  
 427          $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
 428          $seriesPlots = [];
 429  
 430          //    Loop through each data series in turn
 431          for ($i = 0; $i < $seriesCount; ++$i) {
 432              $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
 433              $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
 434  
 435              foreach ($dataValuesY as $k => $dataValueY) {
 436                  $dataValuesY[$k] = $k;
 437              }
 438  
 439              $seriesPlot = new \ScatterPlot($dataValuesX, $dataValuesY);
 440              if ($scatterStyle == 'lineMarker') {
 441                  $seriesPlot->SetLinkPoints();
 442                  $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
 443              } elseif ($scatterStyle == 'smoothMarker') {
 444                  $spline = new \Spline($dataValuesY, $dataValuesX);
 445                  [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20);
 446                  $lplot = new \LinePlot($splineDataX, $splineDataY);
 447                  $lplot->SetColor(self::$colourSet[self::$plotColour]);
 448  
 449                  $this->graph->Add($lplot);
 450              }
 451  
 452              if ($bubble) {
 453                  $this->formatPointMarker($seriesPlot, 'dot');
 454                  $seriesPlot->mark->SetColor('black');
 455                  $seriesPlot->mark->SetSize($bubbleSize);
 456              } else {
 457                  $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
 458                  $this->formatPointMarker($seriesPlot, $marker);
 459              }
 460              $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
 461              $seriesPlot->SetLegend($dataLabel);
 462  
 463              $this->graph->Add($seriesPlot);
 464          }
 465      }
 466  
 467      private function renderPlotRadar($groupID)
 468      {
 469          $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
 470  
 471          $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
 472          $seriesPlots = [];
 473  
 474          //    Loop through each data series in turn
 475          for ($i = 0; $i < $seriesCount; ++$i) {
 476              $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
 477              $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
 478              $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
 479  
 480              $dataValues = [];
 481              foreach ($dataValuesY as $k => $dataValueY) {
 482                  $dataValues[$k] = implode(' ', array_reverse($dataValueY));
 483              }
 484              $tmp = array_shift($dataValues);
 485              $dataValues[] = $tmp;
 486              $tmp = array_shift($dataValuesX);
 487              $dataValuesX[] = $tmp;
 488  
 489              $this->graph->SetTitles(array_reverse($dataValues));
 490  
 491              $seriesPlot = new \RadarPlot(array_reverse($dataValuesX));
 492  
 493              $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
 494              $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
 495              if ($radarStyle == 'filled') {
 496                  $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]);
 497              }
 498              $this->formatPointMarker($seriesPlot, $marker);
 499              $seriesPlot->SetLegend($dataLabel);
 500  
 501              $this->graph->Add($seriesPlot);
 502          }
 503      }
 504  
 505      private function renderPlotContour($groupID)
 506      {
 507          $contourStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
 508  
 509          $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
 510          $seriesPlots = [];
 511  
 512          $dataValues = [];
 513          //    Loop through each data series in turn
 514          for ($i = 0; $i < $seriesCount; ++$i) {
 515              $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
 516              $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
 517  
 518              $dataValues[$i] = $dataValuesX;
 519          }
 520          $seriesPlot = new \ContourPlot($dataValues);
 521  
 522          $this->graph->Add($seriesPlot);
 523      }
 524  
 525      private function renderPlotStock($groupID)
 526      {
 527          $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
 528          $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder();
 529  
 530          $dataValues = [];
 531          //    Loop through each data series in turn and build the plot arrays
 532          foreach ($plotOrder as $i => $v) {
 533              $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues();
 534              foreach ($dataValuesX as $j => $dataValueX) {
 535                  $dataValues[$plotOrder[$i]][$j] = $dataValueX;
 536              }
 537          }
 538          if (empty($dataValues)) {
 539              return;
 540          }
 541  
 542          $dataValuesPlot = [];
 543          // Flatten the plot arrays to a single dimensional array to work with jpgraph
 544          $jMax = count($dataValues[0]);
 545          for ($j = 0; $j < $jMax; ++$j) {
 546              for ($i = 0; $i < $seriesCount; ++$i) {
 547                  $dataValuesPlot[] = $dataValues[$i][$j];
 548              }
 549          }
 550  
 551          // Set the x-axis labels
 552          $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
 553          if ($labelCount > 0) {
 554              $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
 555              $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
 556              $this->graph->xaxis->SetTickLabels($datasetLabels);
 557          }
 558  
 559          $seriesPlot = new \StockPlot($dataValuesPlot);
 560          $seriesPlot->SetWidth(20);
 561  
 562          $this->graph->Add($seriesPlot);
 563      }
 564  
 565      private function renderAreaChart($groupCount, $dimensions = '2d')
 566      {
 567          $this->renderCartesianPlotArea();
 568  
 569          for ($i = 0; $i < $groupCount; ++$i) {
 570              $this->renderPlotLine($i, true, false, $dimensions);
 571          }
 572      }
 573  
 574      private function renderLineChart($groupCount, $dimensions = '2d')
 575      {
 576          $this->renderCartesianPlotArea();
 577  
 578          for ($i = 0; $i < $groupCount; ++$i) {
 579              $this->renderPlotLine($i, false, false, $dimensions);
 580          }
 581      }
 582  
 583      private function renderBarChart($groupCount, $dimensions = '2d')
 584      {
 585          $this->renderCartesianPlotArea();
 586  
 587          for ($i = 0; $i < $groupCount; ++$i) {
 588              $this->renderPlotBar($i, $dimensions);
 589          }
 590      }
 591  
 592      private function renderScatterChart($groupCount)
 593      {
 594          $this->renderCartesianPlotArea('linlin');
 595  
 596          for ($i = 0; $i < $groupCount; ++$i) {
 597              $this->renderPlotScatter($i, false);
 598          }
 599      }
 600  
 601      private function renderBubbleChart($groupCount)
 602      {
 603          $this->renderCartesianPlotArea('linlin');
 604  
 605          for ($i = 0; $i < $groupCount; ++$i) {
 606              $this->renderPlotScatter($i, true);
 607          }
 608      }
 609  
 610      private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false)
 611      {
 612          $this->renderPiePlotArea();
 613  
 614          $iLimit = ($multiplePlots) ? $groupCount : 1;
 615          for ($groupID = 0; $groupID < $iLimit; ++$groupID) {
 616              $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
 617              $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
 618              $datasetLabels = [];
 619              if ($groupID == 0) {
 620                  $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
 621                  if ($labelCount > 0) {
 622                      $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
 623                      $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
 624                  }
 625              }
 626  
 627              $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
 628              $seriesPlots = [];
 629              //    For pie charts, we only display the first series: doughnut charts generally display all series
 630              $jLimit = ($multiplePlots) ? $seriesCount : 1;
 631              //    Loop through each data series in turn
 632              for ($j = 0; $j < $jLimit; ++$j) {
 633                  $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
 634  
 635                  //    Fill in any missing values in the $dataValues array
 636                  $testCurrentIndex = 0;
 637                  foreach ($dataValues as $k => $dataValue) {
 638                      while ($k != $testCurrentIndex) {
 639                          $dataValues[$testCurrentIndex] = null;
 640                          ++$testCurrentIndex;
 641                      }
 642                      ++$testCurrentIndex;
 643                  }
 644  
 645                  if ($dimensions == '3d') {
 646                      $seriesPlot = new \PiePlot3D($dataValues);
 647                  } else {
 648                      if ($doughnut) {
 649                          $seriesPlot = new \PiePlotC($dataValues);
 650                      } else {
 651                          $seriesPlot = new \PiePlot($dataValues);
 652                      }
 653                  }
 654  
 655                  if ($multiplePlots) {
 656                      $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4));
 657                  }
 658  
 659                  if ($doughnut) {
 660                      $seriesPlot->SetMidColor('white');
 661                  }
 662  
 663                  $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
 664                  if (count($datasetLabels) > 0) {
 665                      $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), ''));
 666                  }
 667                  if ($dimensions != '3d') {
 668                      $seriesPlot->SetGuideLines(false);
 669                  }
 670                  if ($j == 0) {
 671                      if ($exploded) {
 672                          $seriesPlot->ExplodeAll();
 673                      }
 674                      $seriesPlot->SetLegends($datasetLabels);
 675                  }
 676  
 677                  $this->graph->Add($seriesPlot);
 678              }
 679          }
 680      }
 681  
 682      private function renderRadarChart($groupCount)
 683      {
 684          $this->renderRadarPlotArea();
 685  
 686          for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
 687              $this->renderPlotRadar($groupID);
 688          }
 689      }
 690  
 691      private function renderStockChart($groupCount)
 692      {
 693          $this->renderCartesianPlotArea('intint');
 694  
 695          for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
 696              $this->renderPlotStock($groupID);
 697          }
 698      }
 699  
 700      private function renderContourChart($groupCount, $dimensions)
 701      {
 702          $this->renderCartesianPlotArea('intint');
 703  
 704          for ($i = 0; $i < $groupCount; ++$i) {
 705              $this->renderPlotContour($i);
 706          }
 707      }
 708  
 709      private function renderCombinationChart($groupCount, $dimensions, $outputDestination)
 710      {
 711          $this->renderCartesianPlotArea();
 712  
 713          for ($i = 0; $i < $groupCount; ++$i) {
 714              $dimensions = null;
 715              $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
 716              switch ($chartType) {
 717                  case 'area3DChart':
 718                      $dimensions = '3d';
 719                  // no break
 720                  case 'areaChart':
 721                      $this->renderPlotLine($i, true, true, $dimensions);
 722  
 723                      break;
 724                  case 'bar3DChart':
 725                      $dimensions = '3d';
 726                  // no break
 727                  case 'barChart':
 728                      $this->renderPlotBar($i, $dimensions);
 729  
 730                      break;
 731                  case 'line3DChart':
 732                      $dimensions = '3d';
 733                  // no break
 734                  case 'lineChart':
 735                      $this->renderPlotLine($i, false, true, $dimensions);
 736  
 737                      break;
 738                  case 'scatterChart':
 739                      $this->renderPlotScatter($i, false);
 740  
 741                      break;
 742                  case 'bubbleChart':
 743                      $this->renderPlotScatter($i, true);
 744  
 745                      break;
 746                  default:
 747                      $this->graph = null;
 748  
 749                      return false;
 750              }
 751          }
 752  
 753          $this->renderLegend();
 754  
 755          $this->graph->Stroke($outputDestination);
 756  
 757          return true;
 758      }
 759  
 760      public function render($outputDestination)
 761      {
 762          self::$plotColour = 0;
 763  
 764          $groupCount = $this->chart->getPlotArea()->getPlotGroupCount();
 765  
 766          $dimensions = null;
 767          if ($groupCount == 1) {
 768              $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType();
 769          } else {
 770              $chartTypes = [];
 771              for ($i = 0; $i < $groupCount; ++$i) {
 772                  $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
 773              }
 774              $chartTypes = array_unique($chartTypes);
 775              if (count($chartTypes) == 1) {
 776                  $chartType = array_pop($chartTypes);
 777              } elseif (count($chartTypes) == 0) {
 778                  echo 'Chart is not yet implemented<br />';
 779  
 780                  return false;
 781              } else {
 782                  return $this->renderCombinationChart($groupCount, $dimensions, $outputDestination);
 783              }
 784          }
 785  
 786          switch ($chartType) {
 787              case 'area3DChart':
 788                  $dimensions = '3d';
 789              // no break
 790              case 'areaChart':
 791                  $this->renderAreaChart($groupCount, $dimensions);
 792  
 793                  break;
 794              case 'bar3DChart':
 795                  $dimensions = '3d';
 796              // no break
 797              case 'barChart':
 798                  $this->renderBarChart($groupCount, $dimensions);
 799  
 800                  break;
 801              case 'line3DChart':
 802                  $dimensions = '3d';
 803              // no break
 804              case 'lineChart':
 805                  $this->renderLineChart($groupCount, $dimensions);
 806  
 807                  break;
 808              case 'pie3DChart':
 809                  $dimensions = '3d';
 810              // no break
 811              case 'pieChart':
 812                  $this->renderPieChart($groupCount, $dimensions, false, false);
 813  
 814                  break;
 815              case 'doughnut3DChart':
 816                  $dimensions = '3d';
 817              // no break
 818              case 'doughnutChart':
 819                  $this->renderPieChart($groupCount, $dimensions, true, true);
 820  
 821                  break;
 822              case 'scatterChart':
 823                  $this->renderScatterChart($groupCount);
 824  
 825                  break;
 826              case 'bubbleChart':
 827                  $this->renderBubbleChart($groupCount);
 828  
 829                  break;
 830              case 'radarChart':
 831                  $this->renderRadarChart($groupCount);
 832  
 833                  break;
 834              case 'surface3DChart':
 835                  $dimensions = '3d';
 836              // no break
 837              case 'surfaceChart':
 838                  $this->renderContourChart($groupCount, $dimensions);
 839  
 840                  break;
 841              case 'stockChart':
 842                  $this->renderStockChart($groupCount);
 843  
 844                  break;
 845              default:
 846                  echo $chartType . ' is not yet implemented<br />';
 847  
 848                  return false;
 849          }
 850          $this->renderLegend();
 851  
 852          $this->graph->Stroke($outputDestination);
 853  
 854          return true;
 855      }
 856  }