Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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