Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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