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\Writer\Xlsx;
   4  
   5  use PhpOffice\PhpSpreadsheet\Chart\Axis;
   6  use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
   7  use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
   8  use PhpOffice\PhpSpreadsheet\Chart\GridLines;
   9  use PhpOffice\PhpSpreadsheet\Chart\Layout;
  10  use PhpOffice\PhpSpreadsheet\Chart\Legend;
  11  use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
  12  use PhpOffice\PhpSpreadsheet\Chart\Title;
  13  use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
  14  use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
  15  use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
  16  
  17  class Chart extends WriterPart
  18  {
  19      protected $calculateCellValues;
  20  
  21      /**
  22       * @var int
  23       */
  24      private $seriesIndex;
  25  
  26      /**
  27       * Write charts to XML format.
  28       *
  29       * @param \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart
  30       * @param mixed $calculateCellValues
  31       *
  32       * @throws WriterException
  33       *
  34       * @return string XML Output
  35       */
  36      public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $calculateCellValues = true)
  37      {
  38          $this->calculateCellValues = $calculateCellValues;
  39  
  40          // Create XML writer
  41          $objWriter = null;
  42          if ($this->getParentWriter()->getUseDiskCaching()) {
  43              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  44          } else {
  45              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  46          }
  47          //    Ensure that data series values are up-to-date before we save
  48          if ($this->calculateCellValues) {
  49              $pChart->refresh();
  50          }
  51  
  52          // XML header
  53          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  54  
  55          // c:chartSpace
  56          $objWriter->startElement('c:chartSpace');
  57          $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
  58          $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
  59          $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
  60  
  61          $objWriter->startElement('c:date1904');
  62          $objWriter->writeAttribute('val', 0);
  63          $objWriter->endElement();
  64          $objWriter->startElement('c:lang');
  65          $objWriter->writeAttribute('val', 'en-GB');
  66          $objWriter->endElement();
  67          $objWriter->startElement('c:roundedCorners');
  68          $objWriter->writeAttribute('val', 0);
  69          $objWriter->endElement();
  70  
  71          $this->writeAlternateContent($objWriter);
  72  
  73          $objWriter->startElement('c:chart');
  74  
  75          $this->writeTitle($objWriter, $pChart->getTitle());
  76  
  77          $objWriter->startElement('c:autoTitleDeleted');
  78          $objWriter->writeAttribute('val', 0);
  79          $objWriter->endElement();
  80  
  81          $this->writePlotArea($objWriter, $pChart->getWorksheet(), $pChart->getPlotArea(), $pChart->getXAxisLabel(), $pChart->getYAxisLabel(), $pChart->getChartAxisX(), $pChart->getChartAxisY(), $pChart->getMajorGridlines(), $pChart->getMinorGridlines());
  82  
  83          $this->writeLegend($objWriter, $pChart->getLegend());
  84  
  85          $objWriter->startElement('c:plotVisOnly');
  86          $objWriter->writeAttribute('val', (int) $pChart->getPlotVisibleOnly());
  87          $objWriter->endElement();
  88  
  89          $objWriter->startElement('c:dispBlanksAs');
  90          $objWriter->writeAttribute('val', $pChart->getDisplayBlanksAs());
  91          $objWriter->endElement();
  92  
  93          $objWriter->startElement('c:showDLblsOverMax');
  94          $objWriter->writeAttribute('val', 0);
  95          $objWriter->endElement();
  96  
  97          $objWriter->endElement();
  98  
  99          $this->writePrintSettings($objWriter);
 100  
 101          $objWriter->endElement();
 102  
 103          // Return
 104          return $objWriter->getData();
 105      }
 106  
 107      /**
 108       * Write Chart Title.
 109       *
 110       * @param XMLWriter $objWriter XML Writer
 111       * @param Title $title
 112       *
 113       * @throws WriterException
 114       */
 115      private function writeTitle(XMLWriter $objWriter, Title $title = null)
 116      {
 117          if ($title === null) {
 118              return;
 119          }
 120  
 121          $objWriter->startElement('c:title');
 122          $objWriter->startElement('c:tx');
 123          $objWriter->startElement('c:rich');
 124  
 125          $objWriter->startElement('a:bodyPr');
 126          $objWriter->endElement();
 127  
 128          $objWriter->startElement('a:lstStyle');
 129          $objWriter->endElement();
 130  
 131          $objWriter->startElement('a:p');
 132  
 133          $caption = $title->getCaption();
 134          if ((is_array($caption)) && (count($caption) > 0)) {
 135              $caption = $caption[0];
 136          }
 137          $this->getParentWriter()->getWriterPart('stringtable')->writeRichTextForCharts($objWriter, $caption, 'a');
 138  
 139          $objWriter->endElement();
 140          $objWriter->endElement();
 141          $objWriter->endElement();
 142  
 143          $this->writeLayout($objWriter, $title->getLayout());
 144  
 145          $objWriter->startElement('c:overlay');
 146          $objWriter->writeAttribute('val', 0);
 147          $objWriter->endElement();
 148  
 149          $objWriter->endElement();
 150      }
 151  
 152      /**
 153       * Write Chart Legend.
 154       *
 155       * @param XMLWriter $objWriter XML Writer
 156       * @param Legend $legend
 157       *
 158       * @throws WriterException
 159       */
 160      private function writeLegend(XMLWriter $objWriter, Legend $legend = null)
 161      {
 162          if ($legend === null) {
 163              return;
 164          }
 165  
 166          $objWriter->startElement('c:legend');
 167  
 168          $objWriter->startElement('c:legendPos');
 169          $objWriter->writeAttribute('val', $legend->getPosition());
 170          $objWriter->endElement();
 171  
 172          $this->writeLayout($objWriter, $legend->getLayout());
 173  
 174          $objWriter->startElement('c:overlay');
 175          $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0');
 176          $objWriter->endElement();
 177  
 178          $objWriter->startElement('c:txPr');
 179          $objWriter->startElement('a:bodyPr');
 180          $objWriter->endElement();
 181  
 182          $objWriter->startElement('a:lstStyle');
 183          $objWriter->endElement();
 184  
 185          $objWriter->startElement('a:p');
 186          $objWriter->startElement('a:pPr');
 187          $objWriter->writeAttribute('rtl', 0);
 188  
 189          $objWriter->startElement('a:defRPr');
 190          $objWriter->endElement();
 191          $objWriter->endElement();
 192  
 193          $objWriter->startElement('a:endParaRPr');
 194          $objWriter->writeAttribute('lang', 'en-US');
 195          $objWriter->endElement();
 196  
 197          $objWriter->endElement();
 198          $objWriter->endElement();
 199  
 200          $objWriter->endElement();
 201      }
 202  
 203      /**
 204       * Write Chart Plot Area.
 205       *
 206       * @param XMLWriter $objWriter XML Writer
 207       * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pSheet
 208       * @param PlotArea $plotArea
 209       * @param Title $xAxisLabel
 210       * @param Title $yAxisLabel
 211       * @param Axis $xAxis
 212       * @param Axis $yAxis
 213       * @param null|GridLines $majorGridlines
 214       * @param null|GridLines $minorGridlines
 215       *
 216       * @throws WriterException
 217       */
 218      private function writePlotArea(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pSheet, PlotArea $plotArea, Title $xAxisLabel = null, Title $yAxisLabel = null, Axis $xAxis = null, Axis $yAxis = null, GridLines $majorGridlines = null, GridLines $minorGridlines = null)
 219      {
 220          if ($plotArea === null) {
 221              return;
 222          }
 223  
 224          $id1 = $id2 = 0;
 225          $this->seriesIndex = 0;
 226          $objWriter->startElement('c:plotArea');
 227  
 228          $layout = $plotArea->getLayout();
 229  
 230          $this->writeLayout($objWriter, $layout);
 231  
 232          $chartTypes = self::getChartType($plotArea);
 233          $catIsMultiLevelSeries = $valIsMultiLevelSeries = false;
 234          $plotGroupingType = '';
 235          foreach ($chartTypes as $chartType) {
 236              $objWriter->startElement('c:' . $chartType);
 237  
 238              $groupCount = $plotArea->getPlotGroupCount();
 239              for ($i = 0; $i < $groupCount; ++$i) {
 240                  $plotGroup = $plotArea->getPlotGroupByIndex($i);
 241                  $groupType = $plotGroup->getPlotType();
 242                  if ($groupType == $chartType) {
 243                      $plotStyle = $plotGroup->getPlotStyle();
 244                      if ($groupType === DataSeries::TYPE_RADARCHART) {
 245                          $objWriter->startElement('c:radarStyle');
 246                          $objWriter->writeAttribute('val', $plotStyle);
 247                          $objWriter->endElement();
 248                      } elseif ($groupType === DataSeries::TYPE_SCATTERCHART) {
 249                          $objWriter->startElement('c:scatterStyle');
 250                          $objWriter->writeAttribute('val', $plotStyle);
 251                          $objWriter->endElement();
 252                      }
 253  
 254                      $this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType);
 255                  }
 256              }
 257  
 258              $this->writeDataLabels($objWriter, $layout);
 259  
 260              if ($chartType === DataSeries::TYPE_LINECHART) {
 261                  //    Line only, Line3D can't be smoothed
 262                  $objWriter->startElement('c:smooth');
 263                  $objWriter->writeAttribute('val', (int) $plotGroup->getSmoothLine());
 264                  $objWriter->endElement();
 265              } elseif (($chartType === DataSeries::TYPE_BARCHART) || ($chartType === DataSeries::TYPE_BARCHART_3D)) {
 266                  $objWriter->startElement('c:gapWidth');
 267                  $objWriter->writeAttribute('val', 150);
 268                  $objWriter->endElement();
 269  
 270                  if ($plotGroupingType == 'percentStacked' || $plotGroupingType == 'stacked') {
 271                      $objWriter->startElement('c:overlap');
 272                      $objWriter->writeAttribute('val', 100);
 273                      $objWriter->endElement();
 274                  }
 275              } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) {
 276                  $objWriter->startElement('c:bubbleScale');
 277                  $objWriter->writeAttribute('val', 25);
 278                  $objWriter->endElement();
 279  
 280                  $objWriter->startElement('c:showNegBubbles');
 281                  $objWriter->writeAttribute('val', 0);
 282                  $objWriter->endElement();
 283              } elseif ($chartType === DataSeries::TYPE_STOCKCHART) {
 284                  $objWriter->startElement('c:hiLowLines');
 285                  $objWriter->endElement();
 286  
 287                  $objWriter->startElement('c:upDownBars');
 288  
 289                  $objWriter->startElement('c:gapWidth');
 290                  $objWriter->writeAttribute('val', 300);
 291                  $objWriter->endElement();
 292  
 293                  $objWriter->startElement('c:upBars');
 294                  $objWriter->endElement();
 295  
 296                  $objWriter->startElement('c:downBars');
 297                  $objWriter->endElement();
 298  
 299                  $objWriter->endElement();
 300              }
 301  
 302              //    Generate 2 unique numbers to use for axId values
 303              $id1 = '75091328';
 304              $id2 = '75089408';
 305  
 306              if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
 307                  $objWriter->startElement('c:axId');
 308                  $objWriter->writeAttribute('val', $id1);
 309                  $objWriter->endElement();
 310                  $objWriter->startElement('c:axId');
 311                  $objWriter->writeAttribute('val', $id2);
 312                  $objWriter->endElement();
 313              } else {
 314                  $objWriter->startElement('c:firstSliceAng');
 315                  $objWriter->writeAttribute('val', 0);
 316                  $objWriter->endElement();
 317  
 318                  if ($chartType === DataSeries::TYPE_DONUTCHART) {
 319                      $objWriter->startElement('c:holeSize');
 320                      $objWriter->writeAttribute('val', 50);
 321                      $objWriter->endElement();
 322                  }
 323              }
 324  
 325              $objWriter->endElement();
 326          }
 327  
 328          if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
 329              if ($chartType === DataSeries::TYPE_BUBBLECHART) {
 330                  $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id1, $id2, $catIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
 331              } else {
 332                  $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $yAxis);
 333              }
 334  
 335              $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
 336          }
 337  
 338          $objWriter->endElement();
 339      }
 340  
 341      /**
 342       * Write Data Labels.
 343       *
 344       * @param XMLWriter $objWriter XML Writer
 345       * @param \PhpOffice\PhpSpreadsheet\Chart\Layout $chartLayout Chart layout
 346       */
 347      private function writeDataLabels(XMLWriter $objWriter, Layout $chartLayout = null)
 348      {
 349          $objWriter->startElement('c:dLbls');
 350  
 351          $objWriter->startElement('c:showLegendKey');
 352          $showLegendKey = (empty($chartLayout)) ? 0 : $chartLayout->getShowLegendKey();
 353          $objWriter->writeAttribute('val', ((empty($showLegendKey)) ? 0 : 1));
 354          $objWriter->endElement();
 355  
 356          $objWriter->startElement('c:showVal');
 357          $showVal = (empty($chartLayout)) ? 0 : $chartLayout->getShowVal();
 358          $objWriter->writeAttribute('val', ((empty($showVal)) ? 0 : 1));
 359          $objWriter->endElement();
 360  
 361          $objWriter->startElement('c:showCatName');
 362          $showCatName = (empty($chartLayout)) ? 0 : $chartLayout->getShowCatName();
 363          $objWriter->writeAttribute('val', ((empty($showCatName)) ? 0 : 1));
 364          $objWriter->endElement();
 365  
 366          $objWriter->startElement('c:showSerName');
 367          $showSerName = (empty($chartLayout)) ? 0 : $chartLayout->getShowSerName();
 368          $objWriter->writeAttribute('val', ((empty($showSerName)) ? 0 : 1));
 369          $objWriter->endElement();
 370  
 371          $objWriter->startElement('c:showPercent');
 372          $showPercent = (empty($chartLayout)) ? 0 : $chartLayout->getShowPercent();
 373          $objWriter->writeAttribute('val', ((empty($showPercent)) ? 0 : 1));
 374          $objWriter->endElement();
 375  
 376          $objWriter->startElement('c:showBubbleSize');
 377          $showBubbleSize = (empty($chartLayout)) ? 0 : $chartLayout->getShowBubbleSize();
 378          $objWriter->writeAttribute('val', ((empty($showBubbleSize)) ? 0 : 1));
 379          $objWriter->endElement();
 380  
 381          $objWriter->startElement('c:showLeaderLines');
 382          $showLeaderLines = (empty($chartLayout)) ? 1 : $chartLayout->getShowLeaderLines();
 383          $objWriter->writeAttribute('val', ((empty($showLeaderLines)) ? 0 : 1));
 384          $objWriter->endElement();
 385  
 386          $objWriter->endElement();
 387      }
 388  
 389      /**
 390       * Write Category Axis.
 391       *
 392       * @param XMLWriter $objWriter XML Writer
 393       * @param Title $xAxisLabel
 394       * @param string $id1
 395       * @param string $id2
 396       * @param bool $isMultiLevelSeries
 397       * @param Axis $yAxis
 398       *
 399       * @throws WriterException
 400       */
 401      private function writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis)
 402      {
 403          $objWriter->startElement('c:catAx');
 404  
 405          if ($id1 > 0) {
 406              $objWriter->startElement('c:axId');
 407              $objWriter->writeAttribute('val', $id1);
 408              $objWriter->endElement();
 409          }
 410  
 411          $objWriter->startElement('c:scaling');
 412          $objWriter->startElement('c:orientation');
 413          $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation'));
 414          $objWriter->endElement();
 415          $objWriter->endElement();
 416  
 417          $objWriter->startElement('c:delete');
 418          $objWriter->writeAttribute('val', 0);
 419          $objWriter->endElement();
 420  
 421          $objWriter->startElement('c:axPos');
 422          $objWriter->writeAttribute('val', 'b');
 423          $objWriter->endElement();
 424  
 425          if ($xAxisLabel !== null) {
 426              $objWriter->startElement('c:title');
 427              $objWriter->startElement('c:tx');
 428              $objWriter->startElement('c:rich');
 429  
 430              $objWriter->startElement('a:bodyPr');
 431              $objWriter->endElement();
 432  
 433              $objWriter->startElement('a:lstStyle');
 434              $objWriter->endElement();
 435  
 436              $objWriter->startElement('a:p');
 437              $objWriter->startElement('a:r');
 438  
 439              $caption = $xAxisLabel->getCaption();
 440              if (is_array($caption)) {
 441                  $caption = $caption[0];
 442              }
 443              $objWriter->startElement('a:t');
 444              $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
 445              $objWriter->endElement();
 446  
 447              $objWriter->endElement();
 448              $objWriter->endElement();
 449              $objWriter->endElement();
 450              $objWriter->endElement();
 451  
 452              $layout = $xAxisLabel->getLayout();
 453              $this->writeLayout($objWriter, $layout);
 454  
 455              $objWriter->startElement('c:overlay');
 456              $objWriter->writeAttribute('val', 0);
 457              $objWriter->endElement();
 458  
 459              $objWriter->endElement();
 460          }
 461  
 462          $objWriter->startElement('c:numFmt');
 463          $objWriter->writeAttribute('formatCode', $yAxis->getAxisNumberFormat());
 464          $objWriter->writeAttribute('sourceLinked', $yAxis->getAxisNumberSourceLinked());
 465          $objWriter->endElement();
 466  
 467          $objWriter->startElement('c:majorTickMark');
 468          $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark'));
 469          $objWriter->endElement();
 470  
 471          $objWriter->startElement('c:minorTickMark');
 472          $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark'));
 473          $objWriter->endElement();
 474  
 475          $objWriter->startElement('c:tickLblPos');
 476          $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels'));
 477          $objWriter->endElement();
 478  
 479          if ($id2 > 0) {
 480              $objWriter->startElement('c:crossAx');
 481              $objWriter->writeAttribute('val', $id2);
 482              $objWriter->endElement();
 483  
 484              $objWriter->startElement('c:crosses');
 485              $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses'));
 486              $objWriter->endElement();
 487          }
 488  
 489          $objWriter->startElement('c:auto');
 490          $objWriter->writeAttribute('val', 1);
 491          $objWriter->endElement();
 492  
 493          $objWriter->startElement('c:lblAlgn');
 494          $objWriter->writeAttribute('val', 'ctr');
 495          $objWriter->endElement();
 496  
 497          $objWriter->startElement('c:lblOffset');
 498          $objWriter->writeAttribute('val', 100);
 499          $objWriter->endElement();
 500  
 501          if ($isMultiLevelSeries) {
 502              $objWriter->startElement('c:noMultiLvlLbl');
 503              $objWriter->writeAttribute('val', 0);
 504              $objWriter->endElement();
 505          }
 506          $objWriter->endElement();
 507      }
 508  
 509      /**
 510       * Write Value Axis.
 511       *
 512       * @param XMLWriter $objWriter XML Writer
 513       * @param Title $yAxisLabel
 514       * @param string $groupType Chart type
 515       * @param string $id1
 516       * @param string $id2
 517       * @param bool $isMultiLevelSeries
 518       * @param Axis $xAxis
 519       * @param GridLines $majorGridlines
 520       * @param GridLines $minorGridlines
 521       *
 522       * @throws WriterException
 523       */
 524      private function writeValueAxis($objWriter, $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis, GridLines $majorGridlines, GridLines $minorGridlines)
 525      {
 526          $objWriter->startElement('c:valAx');
 527  
 528          if ($id2 > 0) {
 529              $objWriter->startElement('c:axId');
 530              $objWriter->writeAttribute('val', $id2);
 531              $objWriter->endElement();
 532          }
 533  
 534          $objWriter->startElement('c:scaling');
 535  
 536          if ($xAxis->getAxisOptionsProperty('maximum') !== null) {
 537              $objWriter->startElement('c:max');
 538              $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('maximum'));
 539              $objWriter->endElement();
 540          }
 541  
 542          if ($xAxis->getAxisOptionsProperty('minimum') !== null) {
 543              $objWriter->startElement('c:min');
 544              $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minimum'));
 545              $objWriter->endElement();
 546          }
 547  
 548          $objWriter->startElement('c:orientation');
 549          $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation'));
 550  
 551          $objWriter->endElement();
 552          $objWriter->endElement();
 553  
 554          $objWriter->startElement('c:delete');
 555          $objWriter->writeAttribute('val', 0);
 556          $objWriter->endElement();
 557  
 558          $objWriter->startElement('c:axPos');
 559          $objWriter->writeAttribute('val', 'l');
 560          $objWriter->endElement();
 561  
 562          $objWriter->startElement('c:majorGridlines');
 563          $objWriter->startElement('c:spPr');
 564  
 565          if ($majorGridlines->getLineColorProperty('value') !== null) {
 566              $objWriter->startElement('a:ln');
 567              $objWriter->writeAttribute('w', $majorGridlines->getLineStyleProperty('width'));
 568              $objWriter->startElement('a:solidFill');
 569              $objWriter->startElement("a:{$majorGridlines->getLineColorProperty('type')}");
 570              $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('value'));
 571              $objWriter->startElement('a:alpha');
 572              $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('alpha'));
 573              $objWriter->endElement(); //end alpha
 574              $objWriter->endElement(); //end srgbClr
 575              $objWriter->endElement(); //end solidFill
 576  
 577              $objWriter->startElement('a:prstDash');
 578              $objWriter->writeAttribute('val', $majorGridlines->getLineStyleProperty('dash'));
 579              $objWriter->endElement();
 580  
 581              if ($majorGridlines->getLineStyleProperty('join') == 'miter') {
 582                  $objWriter->startElement('a:miter');
 583                  $objWriter->writeAttribute('lim', '800000');
 584                  $objWriter->endElement();
 585              } else {
 586                  $objWriter->startElement('a:bevel');
 587                  $objWriter->endElement();
 588              }
 589  
 590              if ($majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
 591                  $objWriter->startElement('a:headEnd');
 592                  $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']));
 593                  $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('head', 'w'));
 594                  $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('head', 'len'));
 595                  $objWriter->endElement();
 596              }
 597  
 598              if ($majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
 599                  $objWriter->startElement('a:tailEnd');
 600                  $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']));
 601                  $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('end', 'w'));
 602                  $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('end', 'len'));
 603                  $objWriter->endElement();
 604              }
 605              $objWriter->endElement(); //end ln
 606          }
 607          $objWriter->startElement('a:effectLst');
 608  
 609          if ($majorGridlines->getGlowSize() !== null) {
 610              $objWriter->startElement('a:glow');
 611              $objWriter->writeAttribute('rad', $majorGridlines->getGlowSize());
 612              $objWriter->startElement("a:{$majorGridlines->getGlowColor('type')}");
 613              $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('value'));
 614              $objWriter->startElement('a:alpha');
 615              $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('alpha'));
 616              $objWriter->endElement(); //end alpha
 617              $objWriter->endElement(); //end schemeClr
 618              $objWriter->endElement(); //end glow
 619          }
 620  
 621          if ($majorGridlines->getShadowProperty('presets') !== null) {
 622              $objWriter->startElement("a:{$majorGridlines->getShadowProperty('effect')}");
 623              if ($majorGridlines->getShadowProperty('blur') !== null) {
 624                  $objWriter->writeAttribute('blurRad', $majorGridlines->getShadowProperty('blur'));
 625              }
 626              if ($majorGridlines->getShadowProperty('distance') !== null) {
 627                  $objWriter->writeAttribute('dist', $majorGridlines->getShadowProperty('distance'));
 628              }
 629              if ($majorGridlines->getShadowProperty('direction') !== null) {
 630                  $objWriter->writeAttribute('dir', $majorGridlines->getShadowProperty('direction'));
 631              }
 632              if ($majorGridlines->getShadowProperty('algn') !== null) {
 633                  $objWriter->writeAttribute('algn', $majorGridlines->getShadowProperty('algn'));
 634              }
 635              if ($majorGridlines->getShadowProperty(['size', 'sx']) !== null) {
 636                  $objWriter->writeAttribute('sx', $majorGridlines->getShadowProperty(['size', 'sx']));
 637              }
 638              if ($majorGridlines->getShadowProperty(['size', 'sy']) !== null) {
 639                  $objWriter->writeAttribute('sy', $majorGridlines->getShadowProperty(['size', 'sy']));
 640              }
 641              if ($majorGridlines->getShadowProperty(['size', 'kx']) !== null) {
 642                  $objWriter->writeAttribute('kx', $majorGridlines->getShadowProperty(['size', 'kx']));
 643              }
 644              if ($majorGridlines->getShadowProperty('rotWithShape') !== null) {
 645                  $objWriter->writeAttribute('rotWithShape', $majorGridlines->getShadowProperty('rotWithShape'));
 646              }
 647              $objWriter->startElement("a:{$majorGridlines->getShadowProperty(['color', 'type'])}");
 648              $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'value']));
 649  
 650              $objWriter->startElement('a:alpha');
 651              $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'alpha']));
 652              $objWriter->endElement(); //end alpha
 653  
 654              $objWriter->endElement(); //end color:type
 655              $objWriter->endElement(); //end shadow
 656          }
 657  
 658          if ($majorGridlines->getSoftEdgesSize() !== null) {
 659              $objWriter->startElement('a:softEdge');
 660              $objWriter->writeAttribute('rad', $majorGridlines->getSoftEdgesSize());
 661              $objWriter->endElement(); //end softEdge
 662          }
 663  
 664          $objWriter->endElement(); //end effectLst
 665          $objWriter->endElement(); //end spPr
 666          $objWriter->endElement(); //end majorGridLines
 667  
 668          if ($minorGridlines->getObjectState()) {
 669              $objWriter->startElement('c:minorGridlines');
 670              $objWriter->startElement('c:spPr');
 671  
 672              if ($minorGridlines->getLineColorProperty('value') !== null) {
 673                  $objWriter->startElement('a:ln');
 674                  $objWriter->writeAttribute('w', $minorGridlines->getLineStyleProperty('width'));
 675                  $objWriter->startElement('a:solidFill');
 676                  $objWriter->startElement("a:{$minorGridlines->getLineColorProperty('type')}");
 677                  $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('value'));
 678                  $objWriter->startElement('a:alpha');
 679                  $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('alpha'));
 680                  $objWriter->endElement(); //end alpha
 681                  $objWriter->endElement(); //end srgbClr
 682                  $objWriter->endElement(); //end solidFill
 683  
 684                  $objWriter->startElement('a:prstDash');
 685                  $objWriter->writeAttribute('val', $minorGridlines->getLineStyleProperty('dash'));
 686                  $objWriter->endElement();
 687  
 688                  if ($minorGridlines->getLineStyleProperty('join') == 'miter') {
 689                      $objWriter->startElement('a:miter');
 690                      $objWriter->writeAttribute('lim', '800000');
 691                      $objWriter->endElement();
 692                  } else {
 693                      $objWriter->startElement('a:bevel');
 694                      $objWriter->endElement();
 695                  }
 696  
 697                  if ($minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
 698                      $objWriter->startElement('a:headEnd');
 699                      $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']));
 700                      $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('head', 'w'));
 701                      $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('head', 'len'));
 702                      $objWriter->endElement();
 703                  }
 704  
 705                  if ($minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
 706                      $objWriter->startElement('a:tailEnd');
 707                      $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']));
 708                      $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('end', 'w'));
 709                      $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('end', 'len'));
 710                      $objWriter->endElement();
 711                  }
 712                  $objWriter->endElement(); //end ln
 713              }
 714  
 715              $objWriter->startElement('a:effectLst');
 716  
 717              if ($minorGridlines->getGlowSize() !== null) {
 718                  $objWriter->startElement('a:glow');
 719                  $objWriter->writeAttribute('rad', $minorGridlines->getGlowSize());
 720                  $objWriter->startElement("a:{$minorGridlines->getGlowColor('type')}");
 721                  $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('value'));
 722                  $objWriter->startElement('a:alpha');
 723                  $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('alpha'));
 724                  $objWriter->endElement(); //end alpha
 725                  $objWriter->endElement(); //end schemeClr
 726                  $objWriter->endElement(); //end glow
 727              }
 728  
 729              if ($minorGridlines->getShadowProperty('presets') !== null) {
 730                  $objWriter->startElement("a:{$minorGridlines->getShadowProperty('effect')}");
 731                  if ($minorGridlines->getShadowProperty('blur') !== null) {
 732                      $objWriter->writeAttribute('blurRad', $minorGridlines->getShadowProperty('blur'));
 733                  }
 734                  if ($minorGridlines->getShadowProperty('distance') !== null) {
 735                      $objWriter->writeAttribute('dist', $minorGridlines->getShadowProperty('distance'));
 736                  }
 737                  if ($minorGridlines->getShadowProperty('direction') !== null) {
 738                      $objWriter->writeAttribute('dir', $minorGridlines->getShadowProperty('direction'));
 739                  }
 740                  if ($minorGridlines->getShadowProperty('algn') !== null) {
 741                      $objWriter->writeAttribute('algn', $minorGridlines->getShadowProperty('algn'));
 742                  }
 743                  if ($minorGridlines->getShadowProperty(['size', 'sx']) !== null) {
 744                      $objWriter->writeAttribute('sx', $minorGridlines->getShadowProperty(['size', 'sx']));
 745                  }
 746                  if ($minorGridlines->getShadowProperty(['size', 'sy']) !== null) {
 747                      $objWriter->writeAttribute('sy', $minorGridlines->getShadowProperty(['size', 'sy']));
 748                  }
 749                  if ($minorGridlines->getShadowProperty(['size', 'kx']) !== null) {
 750                      $objWriter->writeAttribute('kx', $minorGridlines->getShadowProperty(['size', 'kx']));
 751                  }
 752                  if ($minorGridlines->getShadowProperty('rotWithShape') !== null) {
 753                      $objWriter->writeAttribute('rotWithShape', $minorGridlines->getShadowProperty('rotWithShape'));
 754                  }
 755                  $objWriter->startElement("a:{$minorGridlines->getShadowProperty(['color', 'type'])}");
 756                  $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'value']));
 757                  $objWriter->startElement('a:alpha');
 758                  $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'alpha']));
 759                  $objWriter->endElement(); //end alpha
 760                  $objWriter->endElement(); //end color:type
 761                  $objWriter->endElement(); //end shadow
 762              }
 763  
 764              if ($minorGridlines->getSoftEdgesSize() !== null) {
 765                  $objWriter->startElement('a:softEdge');
 766                  $objWriter->writeAttribute('rad', $minorGridlines->getSoftEdgesSize());
 767                  $objWriter->endElement(); //end softEdge
 768              }
 769  
 770              $objWriter->endElement(); //end effectLst
 771              $objWriter->endElement(); //end spPr
 772              $objWriter->endElement(); //end minorGridLines
 773          }
 774  
 775          if ($yAxisLabel !== null) {
 776              $objWriter->startElement('c:title');
 777              $objWriter->startElement('c:tx');
 778              $objWriter->startElement('c:rich');
 779  
 780              $objWriter->startElement('a:bodyPr');
 781              $objWriter->endElement();
 782  
 783              $objWriter->startElement('a:lstStyle');
 784              $objWriter->endElement();
 785  
 786              $objWriter->startElement('a:p');
 787              $objWriter->startElement('a:r');
 788  
 789              $caption = $yAxisLabel->getCaption();
 790              if (is_array($caption)) {
 791                  $caption = $caption[0];
 792              }
 793  
 794              $objWriter->startElement('a:t');
 795              $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
 796              $objWriter->endElement();
 797  
 798              $objWriter->endElement();
 799              $objWriter->endElement();
 800              $objWriter->endElement();
 801              $objWriter->endElement();
 802  
 803              if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
 804                  $layout = $yAxisLabel->getLayout();
 805                  $this->writeLayout($objWriter, $layout);
 806              }
 807  
 808              $objWriter->startElement('c:overlay');
 809              $objWriter->writeAttribute('val', 0);
 810              $objWriter->endElement();
 811  
 812              $objWriter->endElement();
 813          }
 814  
 815          $objWriter->startElement('c:numFmt');
 816          $objWriter->writeAttribute('formatCode', $xAxis->getAxisNumberFormat());
 817          $objWriter->writeAttribute('sourceLinked', $xAxis->getAxisNumberSourceLinked());
 818          $objWriter->endElement();
 819  
 820          $objWriter->startElement('c:majorTickMark');
 821          $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark'));
 822          $objWriter->endElement();
 823  
 824          $objWriter->startElement('c:minorTickMark');
 825          $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark'));
 826          $objWriter->endElement();
 827  
 828          $objWriter->startElement('c:tickLblPos');
 829          $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels'));
 830          $objWriter->endElement();
 831  
 832          $objWriter->startElement('c:spPr');
 833  
 834          if ($xAxis->getFillProperty('value') !== null) {
 835              $objWriter->startElement('a:solidFill');
 836              $objWriter->startElement('a:' . $xAxis->getFillProperty('type'));
 837              $objWriter->writeAttribute('val', $xAxis->getFillProperty('value'));
 838              $objWriter->startElement('a:alpha');
 839              $objWriter->writeAttribute('val', $xAxis->getFillProperty('alpha'));
 840              $objWriter->endElement();
 841              $objWriter->endElement();
 842              $objWriter->endElement();
 843          }
 844  
 845          $objWriter->startElement('a:ln');
 846  
 847          $objWriter->writeAttribute('w', $xAxis->getLineStyleProperty('width'));
 848          $objWriter->writeAttribute('cap', $xAxis->getLineStyleProperty('cap'));
 849          $objWriter->writeAttribute('cmpd', $xAxis->getLineStyleProperty('compound'));
 850  
 851          if ($xAxis->getLineProperty('value') !== null) {
 852              $objWriter->startElement('a:solidFill');
 853              $objWriter->startElement('a:' . $xAxis->getLineProperty('type'));
 854              $objWriter->writeAttribute('val', $xAxis->getLineProperty('value'));
 855              $objWriter->startElement('a:alpha');
 856              $objWriter->writeAttribute('val', $xAxis->getLineProperty('alpha'));
 857              $objWriter->endElement();
 858              $objWriter->endElement();
 859              $objWriter->endElement();
 860          }
 861  
 862          $objWriter->startElement('a:prstDash');
 863          $objWriter->writeAttribute('val', $xAxis->getLineStyleProperty('dash'));
 864          $objWriter->endElement();
 865  
 866          if ($xAxis->getLineStyleProperty('join') == 'miter') {
 867              $objWriter->startElement('a:miter');
 868              $objWriter->writeAttribute('lim', '800000');
 869              $objWriter->endElement();
 870          } else {
 871              $objWriter->startElement('a:bevel');
 872              $objWriter->endElement();
 873          }
 874  
 875          if ($xAxis->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
 876              $objWriter->startElement('a:headEnd');
 877              $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'head', 'type']));
 878              $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('head'));
 879              $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('head'));
 880              $objWriter->endElement();
 881          }
 882  
 883          if ($xAxis->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
 884              $objWriter->startElement('a:tailEnd');
 885              $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'end', 'type']));
 886              $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('end'));
 887              $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('end'));
 888              $objWriter->endElement();
 889          }
 890  
 891          $objWriter->endElement();
 892  
 893          $objWriter->startElement('a:effectLst');
 894  
 895          if ($xAxis->getGlowProperty('size') !== null) {
 896              $objWriter->startElement('a:glow');
 897              $objWriter->writeAttribute('rad', $xAxis->getGlowProperty('size'));
 898              $objWriter->startElement("a:{$xAxis->getGlowProperty(['color', 'type'])}");
 899              $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'value']));
 900              $objWriter->startElement('a:alpha');
 901              $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'alpha']));
 902              $objWriter->endElement();
 903              $objWriter->endElement();
 904              $objWriter->endElement();
 905          }
 906  
 907          if ($xAxis->getShadowProperty('presets') !== null) {
 908              $objWriter->startElement("a:{$xAxis->getShadowProperty('effect')}");
 909  
 910              if ($xAxis->getShadowProperty('blur') !== null) {
 911                  $objWriter->writeAttribute('blurRad', $xAxis->getShadowProperty('blur'));
 912              }
 913              if ($xAxis->getShadowProperty('distance') !== null) {
 914                  $objWriter->writeAttribute('dist', $xAxis->getShadowProperty('distance'));
 915              }
 916              if ($xAxis->getShadowProperty('direction') !== null) {
 917                  $objWriter->writeAttribute('dir', $xAxis->getShadowProperty('direction'));
 918              }
 919              if ($xAxis->getShadowProperty('algn') !== null) {
 920                  $objWriter->writeAttribute('algn', $xAxis->getShadowProperty('algn'));
 921              }
 922              if ($xAxis->getShadowProperty(['size', 'sx']) !== null) {
 923                  $objWriter->writeAttribute('sx', $xAxis->getShadowProperty(['size', 'sx']));
 924              }
 925              if ($xAxis->getShadowProperty(['size', 'sy']) !== null) {
 926                  $objWriter->writeAttribute('sy', $xAxis->getShadowProperty(['size', 'sy']));
 927              }
 928              if ($xAxis->getShadowProperty(['size', 'kx']) !== null) {
 929                  $objWriter->writeAttribute('kx', $xAxis->getShadowProperty(['size', 'kx']));
 930              }
 931              if ($xAxis->getShadowProperty('rotWithShape') !== null) {
 932                  $objWriter->writeAttribute('rotWithShape', $xAxis->getShadowProperty('rotWithShape'));
 933              }
 934  
 935              $objWriter->startElement("a:{$xAxis->getShadowProperty(['color', 'type'])}");
 936              $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'value']));
 937              $objWriter->startElement('a:alpha');
 938              $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'alpha']));
 939              $objWriter->endElement();
 940              $objWriter->endElement();
 941  
 942              $objWriter->endElement();
 943          }
 944  
 945          if ($xAxis->getSoftEdgesSize() !== null) {
 946              $objWriter->startElement('a:softEdge');
 947              $objWriter->writeAttribute('rad', $xAxis->getSoftEdgesSize());
 948              $objWriter->endElement();
 949          }
 950  
 951          $objWriter->endElement(); //effectList
 952          $objWriter->endElement(); //end spPr
 953  
 954          if ($id1 > 0) {
 955              $objWriter->startElement('c:crossAx');
 956              $objWriter->writeAttribute('val', $id2);
 957              $objWriter->endElement();
 958  
 959              if ($xAxis->getAxisOptionsProperty('horizontal_crosses_value') !== null) {
 960                  $objWriter->startElement('c:crossesAt');
 961                  $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses_value'));
 962                  $objWriter->endElement();
 963              } else {
 964                  $objWriter->startElement('c:crosses');
 965                  $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses'));
 966                  $objWriter->endElement();
 967              }
 968  
 969              $objWriter->startElement('c:crossBetween');
 970              $objWriter->writeAttribute('val', 'midCat');
 971              $objWriter->endElement();
 972  
 973              if ($xAxis->getAxisOptionsProperty('major_unit') !== null) {
 974                  $objWriter->startElement('c:majorUnit');
 975                  $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_unit'));
 976                  $objWriter->endElement();
 977              }
 978  
 979              if ($xAxis->getAxisOptionsProperty('minor_unit') !== null) {
 980                  $objWriter->startElement('c:minorUnit');
 981                  $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_unit'));
 982                  $objWriter->endElement();
 983              }
 984          }
 985  
 986          if ($isMultiLevelSeries) {
 987              if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
 988                  $objWriter->startElement('c:noMultiLvlLbl');
 989                  $objWriter->writeAttribute('val', 0);
 990                  $objWriter->endElement();
 991              }
 992          }
 993  
 994          $objWriter->endElement();
 995      }
 996  
 997      /**
 998       * Get the data series type(s) for a chart plot series.
 999       *
1000       * @param PlotArea $plotArea
1001       *
1002       * @throws WriterException
1003       *
1004       * @return array|string
1005       */
1006      private static function getChartType($plotArea)
1007      {
1008          $groupCount = $plotArea->getPlotGroupCount();
1009  
1010          if ($groupCount == 1) {
1011              $chartType = [$plotArea->getPlotGroupByIndex(0)->getPlotType()];
1012          } else {
1013              $chartTypes = [];
1014              for ($i = 0; $i < $groupCount; ++$i) {
1015                  $chartTypes[] = $plotArea->getPlotGroupByIndex($i)->getPlotType();
1016              }
1017              $chartType = array_unique($chartTypes);
1018              if (count($chartTypes) == 0) {
1019                  throw new WriterException('Chart is not yet implemented');
1020              }
1021          }
1022  
1023          return $chartType;
1024      }
1025  
1026      /**
1027       * Method writing plot series values.
1028       *
1029       * @param XMLWriter $objWriter XML Writer
1030       * @param int       $val       value for idx (default: 3)
1031       * @param string    $fillColor hex color (default: FF9900)
1032       *
1033       * @return XMLWriter XML Writer
1034       */
1035      private function writePlotSeriesValuesElement($objWriter, $val = 3, $fillColor = 'FF9900')
1036      {
1037          $objWriter->startElement('c:dPt');
1038          $objWriter->startElement('c:idx');
1039          $objWriter->writeAttribute('val', $val);
1040          $objWriter->endElement();
1041  
1042          $objWriter->startElement('c:bubble3D');
1043          $objWriter->writeAttribute('val', 0);
1044          $objWriter->endElement();
1045  
1046          $objWriter->startElement('c:spPr');
1047          $objWriter->startElement('a:solidFill');
1048          $objWriter->startElement('a:srgbClr');
1049          $objWriter->writeAttribute('val', $fillColor);
1050          $objWriter->endElement();
1051          $objWriter->endElement();
1052          $objWriter->endElement();
1053          $objWriter->endElement();
1054  
1055          return $objWriter;
1056      }
1057  
1058      /**
1059       * Write Plot Group (series of related plots).
1060       *
1061       * @param DataSeries $plotGroup
1062       * @param string $groupType Type of plot for dataseries
1063       * @param XMLWriter $objWriter XML Writer
1064       * @param bool &$catIsMultiLevelSeries Is category a multi-series category
1065       * @param bool &$valIsMultiLevelSeries Is value set a multi-series set
1066       * @param string &$plotGroupingType Type of grouping for multi-series values
1067       *
1068       * @throws WriterException
1069       */
1070      private function writePlotGroup($plotGroup, $groupType, $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType)
1071      {
1072          if ($plotGroup === null) {
1073              return;
1074          }
1075  
1076          if (($groupType == DataSeries::TYPE_BARCHART) || ($groupType == DataSeries::TYPE_BARCHART_3D)) {
1077              $objWriter->startElement('c:barDir');
1078              $objWriter->writeAttribute('val', $plotGroup->getPlotDirection());
1079              $objWriter->endElement();
1080          }
1081  
1082          if ($plotGroup->getPlotGrouping() !== null) {
1083              $plotGroupingType = $plotGroup->getPlotGrouping();
1084              $objWriter->startElement('c:grouping');
1085              $objWriter->writeAttribute('val', $plotGroupingType);
1086              $objWriter->endElement();
1087          }
1088  
1089          //    Get these details before the loop, because we can use the count to check for varyColors
1090          $plotSeriesOrder = $plotGroup->getPlotOrder();
1091          $plotSeriesCount = count($plotSeriesOrder);
1092  
1093          if (($groupType !== DataSeries::TYPE_RADARCHART) && ($groupType !== DataSeries::TYPE_STOCKCHART)) {
1094              if ($groupType !== DataSeries::TYPE_LINECHART) {
1095                  if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART) || ($plotSeriesCount > 1)) {
1096                      $objWriter->startElement('c:varyColors');
1097                      $objWriter->writeAttribute('val', 1);
1098                      $objWriter->endElement();
1099                  } else {
1100                      $objWriter->startElement('c:varyColors');
1101                      $objWriter->writeAttribute('val', 0);
1102                      $objWriter->endElement();
1103                  }
1104              }
1105          }
1106  
1107          foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) {
1108              $objWriter->startElement('c:ser');
1109  
1110              $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);
1111              if ($plotLabel) {
1112                  $fillColor = $plotLabel->getFillColor();
1113                  if ($fillColor !== null && !is_array($fillColor)) {
1114                      $objWriter->startElement('c:spPr');
1115                      $objWriter->startElement('a:solidFill');
1116                      $objWriter->startElement('a:srgbClr');
1117                      $objWriter->writeAttribute('val', $fillColor);
1118                      $objWriter->endElement();
1119                      $objWriter->endElement();
1120                      $objWriter->endElement();
1121                  }
1122              }
1123  
1124              $objWriter->startElement('c:idx');
1125              $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesIdx);
1126              $objWriter->endElement();
1127  
1128              $objWriter->startElement('c:order');
1129              $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesRef);
1130              $objWriter->endElement();
1131  
1132              //    Values
1133              $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef);
1134  
1135              if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
1136                  $fillColorValues = $plotSeriesValues->getFillColor();
1137                  if ($fillColorValues !== null && is_array($fillColorValues)) {
1138                      foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) {
1139                          $this->writePlotSeriesValuesElement($objWriter, $dataKey, ($fillColorValues[$dataKey] ?? 'FF9900'));
1140                      }
1141                  } else {
1142                      $this->writePlotSeriesValuesElement($objWriter);
1143                  }
1144              }
1145  
1146              //    Labels
1147              $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesRef);
1148              if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) {
1149                  $objWriter->startElement('c:tx');
1150                  $objWriter->startElement('c:strRef');
1151                  $this->writePlotSeriesLabel($plotSeriesLabel, $objWriter);
1152                  $objWriter->endElement();
1153                  $objWriter->endElement();
1154              }
1155  
1156              //    Formatting for the points
1157              if (($groupType == DataSeries::TYPE_LINECHART) || ($groupType == DataSeries::TYPE_STOCKCHART)) {
1158                  $plotLineWidth = 12700;
1159                  if ($plotSeriesValues) {
1160                      $plotLineWidth = $plotSeriesValues->getLineWidth();
1161                  }
1162  
1163                  $objWriter->startElement('c:spPr');
1164                  $objWriter->startElement('a:ln');
1165                  $objWriter->writeAttribute('w', $plotLineWidth);
1166                  if ($groupType == DataSeries::TYPE_STOCKCHART) {
1167                      $objWriter->startElement('a:noFill');
1168                      $objWriter->endElement();
1169                  }
1170                  $objWriter->endElement();
1171                  $objWriter->endElement();
1172              }
1173  
1174              if ($plotSeriesValues) {
1175                  $plotSeriesMarker = $plotSeriesValues->getPointMarker();
1176                  if ($plotSeriesMarker) {
1177                      $objWriter->startElement('c:marker');
1178                      $objWriter->startElement('c:symbol');
1179                      $objWriter->writeAttribute('val', $plotSeriesMarker);
1180                      $objWriter->endElement();
1181  
1182                      if ($plotSeriesMarker !== 'none') {
1183                          $objWriter->startElement('c:size');
1184                          $objWriter->writeAttribute('val', 3);
1185                          $objWriter->endElement();
1186                      }
1187  
1188                      $objWriter->endElement();
1189                  }
1190              }
1191  
1192              if (($groupType === DataSeries::TYPE_BARCHART) || ($groupType === DataSeries::TYPE_BARCHART_3D) || ($groupType === DataSeries::TYPE_BUBBLECHART)) {
1193                  $objWriter->startElement('c:invertIfNegative');
1194                  $objWriter->writeAttribute('val', 0);
1195                  $objWriter->endElement();
1196              }
1197  
1198              //    Category Labels
1199              $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesRef);
1200              if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) {
1201                  $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries();
1202  
1203                  if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
1204                      if ($plotGroup->getPlotStyle() !== null) {
1205                          $plotStyle = $plotGroup->getPlotStyle();
1206                          if ($plotStyle) {
1207                              $objWriter->startElement('c:explosion');
1208                              $objWriter->writeAttribute('val', 25);
1209                              $objWriter->endElement();
1210                          }
1211                      }
1212                  }
1213  
1214                  if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
1215                      $objWriter->startElement('c:xVal');
1216                  } else {
1217                      $objWriter->startElement('c:cat');
1218                  }
1219  
1220                  $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str');
1221                  $objWriter->endElement();
1222              }
1223  
1224              //    Values
1225              if ($plotSeriesValues) {
1226                  $valIsMultiLevelSeries = $valIsMultiLevelSeries || $plotSeriesValues->isMultiLevelSeries();
1227  
1228                  if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
1229                      $objWriter->startElement('c:yVal');
1230                  } else {
1231                      $objWriter->startElement('c:val');
1232                  }
1233  
1234                  $this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num');
1235                  $objWriter->endElement();
1236              }
1237  
1238              if ($groupType === DataSeries::TYPE_BUBBLECHART) {
1239                  $this->writeBubbles($plotSeriesValues, $objWriter);
1240              }
1241  
1242              $objWriter->endElement();
1243          }
1244  
1245          $this->seriesIndex += $plotSeriesIdx + 1;
1246      }
1247  
1248      /**
1249       * Write Plot Series Label.
1250       *
1251       * @param DataSeriesValues $plotSeriesLabel
1252       * @param XMLWriter $objWriter XML Writer
1253       */
1254      private function writePlotSeriesLabel($plotSeriesLabel, $objWriter)
1255      {
1256          if ($plotSeriesLabel === null) {
1257              return;
1258          }
1259  
1260          $objWriter->startElement('c:f');
1261          $objWriter->writeRawData($plotSeriesLabel->getDataSource());
1262          $objWriter->endElement();
1263  
1264          $objWriter->startElement('c:strCache');
1265          $objWriter->startElement('c:ptCount');
1266          $objWriter->writeAttribute('val', $plotSeriesLabel->getPointCount());
1267          $objWriter->endElement();
1268  
1269          foreach ($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) {
1270              $objWriter->startElement('c:pt');
1271              $objWriter->writeAttribute('idx', $plotLabelKey);
1272  
1273              $objWriter->startElement('c:v');
1274              $objWriter->writeRawData($plotLabelValue);
1275              $objWriter->endElement();
1276              $objWriter->endElement();
1277          }
1278          $objWriter->endElement();
1279      }
1280  
1281      /**
1282       * Write Plot Series Values.
1283       *
1284       * @param DataSeriesValues $plotSeriesValues
1285       * @param XMLWriter $objWriter XML Writer
1286       * @param string $groupType Type of plot for dataseries
1287       * @param string $dataType Datatype of series values
1288       */
1289      private function writePlotSeriesValues($plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str')
1290      {
1291          if ($plotSeriesValues === null) {
1292              return;
1293          }
1294  
1295          if ($plotSeriesValues->isMultiLevelSeries()) {
1296              $levelCount = $plotSeriesValues->multiLevelCount();
1297  
1298              $objWriter->startElement('c:multiLvlStrRef');
1299  
1300              $objWriter->startElement('c:f');
1301              $objWriter->writeRawData($plotSeriesValues->getDataSource());
1302              $objWriter->endElement();
1303  
1304              $objWriter->startElement('c:multiLvlStrCache');
1305  
1306              $objWriter->startElement('c:ptCount');
1307              $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
1308              $objWriter->endElement();
1309  
1310              for ($level = 0; $level < $levelCount; ++$level) {
1311                  $objWriter->startElement('c:lvl');
1312  
1313                  foreach ($plotSeriesValues->getDataValues() as $plotSeriesKey => $plotSeriesValue) {
1314                      if (isset($plotSeriesValue[$level])) {
1315                          $objWriter->startElement('c:pt');
1316                          $objWriter->writeAttribute('idx', $plotSeriesKey);
1317  
1318                          $objWriter->startElement('c:v');
1319                          $objWriter->writeRawData($plotSeriesValue[$level]);
1320                          $objWriter->endElement();
1321                          $objWriter->endElement();
1322                      }
1323                  }
1324  
1325                  $objWriter->endElement();
1326              }
1327  
1328              $objWriter->endElement();
1329  
1330              $objWriter->endElement();
1331          } else {
1332              $objWriter->startElement('c:' . $dataType . 'Ref');
1333  
1334              $objWriter->startElement('c:f');
1335              $objWriter->writeRawData($plotSeriesValues->getDataSource());
1336              $objWriter->endElement();
1337  
1338              $objWriter->startElement('c:' . $dataType . 'Cache');
1339  
1340              if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
1341                  if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
1342                      $objWriter->startElement('c:formatCode');
1343                      $objWriter->writeRawData($plotSeriesValues->getFormatCode());
1344                      $objWriter->endElement();
1345                  }
1346              }
1347  
1348              $objWriter->startElement('c:ptCount');
1349              $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
1350              $objWriter->endElement();
1351  
1352              $dataValues = $plotSeriesValues->getDataValues();
1353              if (!empty($dataValues)) {
1354                  if (is_array($dataValues)) {
1355                      foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
1356                          $objWriter->startElement('c:pt');
1357                          $objWriter->writeAttribute('idx', $plotSeriesKey);
1358  
1359                          $objWriter->startElement('c:v');
1360                          $objWriter->writeRawData($plotSeriesValue);
1361                          $objWriter->endElement();
1362                          $objWriter->endElement();
1363                      }
1364                  }
1365              }
1366  
1367              $objWriter->endElement();
1368  
1369              $objWriter->endElement();
1370          }
1371      }
1372  
1373      /**
1374       * Write Bubble Chart Details.
1375       *
1376       * @param DataSeriesValues $plotSeriesValues
1377       * @param XMLWriter $objWriter XML Writer
1378       */
1379      private function writeBubbles($plotSeriesValues, $objWriter)
1380      {
1381          if ($plotSeriesValues === null) {
1382              return;
1383          }
1384  
1385          $objWriter->startElement('c:bubbleSize');
1386          $objWriter->startElement('c:numLit');
1387  
1388          $objWriter->startElement('c:formatCode');
1389          $objWriter->writeRawData('General');
1390          $objWriter->endElement();
1391  
1392          $objWriter->startElement('c:ptCount');
1393          $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
1394          $objWriter->endElement();
1395  
1396          $dataValues = $plotSeriesValues->getDataValues();
1397          if (!empty($dataValues)) {
1398              if (is_array($dataValues)) {
1399                  foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
1400                      $objWriter->startElement('c:pt');
1401                      $objWriter->writeAttribute('idx', $plotSeriesKey);
1402                      $objWriter->startElement('c:v');
1403                      $objWriter->writeRawData(1);
1404                      $objWriter->endElement();
1405                      $objWriter->endElement();
1406                  }
1407              }
1408          }
1409  
1410          $objWriter->endElement();
1411          $objWriter->endElement();
1412  
1413          $objWriter->startElement('c:bubble3D');
1414          $objWriter->writeAttribute('val', 0);
1415          $objWriter->endElement();
1416      }
1417  
1418      /**
1419       * Write Layout.
1420       *
1421       * @param XMLWriter $objWriter XML Writer
1422       * @param Layout $layout
1423       */
1424      private function writeLayout(XMLWriter $objWriter, Layout $layout = null)
1425      {
1426          $objWriter->startElement('c:layout');
1427  
1428          if ($layout !== null) {
1429              $objWriter->startElement('c:manualLayout');
1430  
1431              $layoutTarget = $layout->getLayoutTarget();
1432              if ($layoutTarget !== null) {
1433                  $objWriter->startElement('c:layoutTarget');
1434                  $objWriter->writeAttribute('val', $layoutTarget);
1435                  $objWriter->endElement();
1436              }
1437  
1438              $xMode = $layout->getXMode();
1439              if ($xMode !== null) {
1440                  $objWriter->startElement('c:xMode');
1441                  $objWriter->writeAttribute('val', $xMode);
1442                  $objWriter->endElement();
1443              }
1444  
1445              $yMode = $layout->getYMode();
1446              if ($yMode !== null) {
1447                  $objWriter->startElement('c:yMode');
1448                  $objWriter->writeAttribute('val', $yMode);
1449                  $objWriter->endElement();
1450              }
1451  
1452              $x = $layout->getXPosition();
1453              if ($x !== null) {
1454                  $objWriter->startElement('c:x');
1455                  $objWriter->writeAttribute('val', $x);
1456                  $objWriter->endElement();
1457              }
1458  
1459              $y = $layout->getYPosition();
1460              if ($y !== null) {
1461                  $objWriter->startElement('c:y');
1462                  $objWriter->writeAttribute('val', $y);
1463                  $objWriter->endElement();
1464              }
1465  
1466              $w = $layout->getWidth();
1467              if ($w !== null) {
1468                  $objWriter->startElement('c:w');
1469                  $objWriter->writeAttribute('val', $w);
1470                  $objWriter->endElement();
1471              }
1472  
1473              $h = $layout->getHeight();
1474              if ($h !== null) {
1475                  $objWriter->startElement('c:h');
1476                  $objWriter->writeAttribute('val', $h);
1477                  $objWriter->endElement();
1478              }
1479  
1480              $objWriter->endElement();
1481          }
1482  
1483          $objWriter->endElement();
1484      }
1485  
1486      /**
1487       * Write Alternate Content block.
1488       *
1489       * @param XMLWriter $objWriter XML Writer
1490       */
1491      private function writeAlternateContent($objWriter)
1492      {
1493          $objWriter->startElement('mc:AlternateContent');
1494          $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
1495  
1496          $objWriter->startElement('mc:Choice');
1497          $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart');
1498          $objWriter->writeAttribute('Requires', 'c14');
1499  
1500          $objWriter->startElement('c14:style');
1501          $objWriter->writeAttribute('val', '102');
1502          $objWriter->endElement();
1503          $objWriter->endElement();
1504  
1505          $objWriter->startElement('mc:Fallback');
1506          $objWriter->startElement('c:style');
1507          $objWriter->writeAttribute('val', '2');
1508          $objWriter->endElement();
1509          $objWriter->endElement();
1510  
1511          $objWriter->endElement();
1512      }
1513  
1514      /**
1515       * Write Printer Settings.
1516       *
1517       * @param XMLWriter $objWriter XML Writer
1518       */
1519      private function writePrintSettings($objWriter)
1520      {
1521          $objWriter->startElement('c:printSettings');
1522  
1523          $objWriter->startElement('c:headerFooter');
1524          $objWriter->endElement();
1525  
1526          $objWriter->startElement('c:pageMargins');
1527          $objWriter->writeAttribute('footer', 0.3);
1528          $objWriter->writeAttribute('header', 0.3);
1529          $objWriter->writeAttribute('r', 0.7);
1530          $objWriter->writeAttribute('l', 0.7);
1531          $objWriter->writeAttribute('t', 0.75);
1532          $objWriter->writeAttribute('b', 0.75);
1533          $objWriter->endElement();
1534  
1535          $objWriter->startElement('c:pageSetup');
1536          $objWriter->writeAttribute('orientation', 'portrait');
1537          $objWriter->endElement();
1538  
1539          $objWriter->endElement();
1540      }
1541  }