Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

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