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\Reader\Xlsx;
   4  
   5  use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
   6  use PhpOffice\PhpSpreadsheet\Chart\Axis;
   7  use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
   8  use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
   9  use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
  10  use PhpOffice\PhpSpreadsheet\Chart\GridLines;
  11  use PhpOffice\PhpSpreadsheet\Chart\Layout;
  12  use PhpOffice\PhpSpreadsheet\Chart\Legend;
  13  use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
  14  use PhpOffice\PhpSpreadsheet\Chart\Properties as ChartProperties;
  15  use PhpOffice\PhpSpreadsheet\Chart\Title;
  16  use PhpOffice\PhpSpreadsheet\Chart\TrendLine;
  17  use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
  18  use PhpOffice\PhpSpreadsheet\RichText\RichText;
  19  use PhpOffice\PhpSpreadsheet\Style\Font;
  20  use SimpleXMLElement;
  21  
  22  class Chart
  23  {
  24      /** @var string */
  25      private $cNamespace;
  26  
  27      /** @var string */
  28      private $aNamespace;
  29  
  30      public function __construct(string $cNamespace = Namespaces::CHART, string $aNamespace = Namespaces::DRAWINGML)
  31      {
  32          $this->cNamespace = $cNamespace;
  33          $this->aNamespace = $aNamespace;
  34      }
  35  
  36      /**
  37       * @param string $name
  38       * @param string $format
  39       *
  40       * @return null|bool|float|int|string
  41       */
  42      private static function getAttribute(SimpleXMLElement $component, $name, $format)
  43      {
  44          $attributes = $component->attributes();
  45          if (@isset($attributes[$name])) {
  46              if ($format == 'string') {
  47                  return (string) $attributes[$name];
  48              } elseif ($format == 'integer') {
  49                  return (int) $attributes[$name];
  50              } elseif ($format == 'boolean') {
  51                  $value = (string) $attributes[$name];
  52  
  53                  return $value === 'true' || $value === '1';
  54              }
  55  
  56              return (float) $attributes[$name];
  57          }
  58  
  59          return null;
  60      }
  61  
  62      /**
  63       * @param string $chartName
  64       *
  65       * @return \PhpOffice\PhpSpreadsheet\Chart\Chart
  66       */
  67      public function readChart(SimpleXMLElement $chartElements, $chartName)
  68      {
  69          $chartElementsC = $chartElements->children($this->cNamespace);
  70  
  71          $XaxisLabel = $YaxisLabel = $legend = $title = null;
  72          $dispBlanksAs = $plotVisOnly = null;
  73          $plotArea = null;
  74          $rotX = $rotY = $rAngAx = $perspective = null;
  75          $xAxis = new Axis();
  76          $yAxis = new Axis();
  77          $autoTitleDeleted = null;
  78          $chartNoFill = false;
  79          $gradientArray = [];
  80          $gradientLin = null;
  81          $roundedCorners = false;
  82          foreach ($chartElementsC as $chartElementKey => $chartElement) {
  83              switch ($chartElementKey) {
  84                  case 'spPr':
  85                      $possibleNoFill = $chartElementsC->spPr->children($this->aNamespace);
  86                      if (isset($possibleNoFill->noFill)) {
  87                          $chartNoFill = true;
  88                      }
  89  
  90                      break;
  91                  case 'roundedCorners':
  92                      /** @var bool */
  93                      $roundedCorners = self::getAttribute($chartElementsC->roundedCorners, 'val', 'boolean');
  94  
  95                      break;
  96                  case 'chart':
  97                      foreach ($chartElement as $chartDetailsKey => $chartDetails) {
  98                          $chartDetails = Xlsx::testSimpleXml($chartDetails);
  99                          switch ($chartDetailsKey) {
 100                              case 'autoTitleDeleted':
 101                                  /** @var bool */
 102                                  $autoTitleDeleted = self::getAttribute($chartElementsC->chart->autoTitleDeleted, 'val', 'boolean');
 103  
 104                                  break;
 105                              case 'view3D':
 106                                  $rotX = self::getAttribute($chartDetails->rotX, 'val', 'integer');
 107                                  $rotY = self::getAttribute($chartDetails->rotY, 'val', 'integer');
 108                                  $rAngAx = self::getAttribute($chartDetails->rAngAx, 'val', 'integer');
 109                                  $perspective = self::getAttribute($chartDetails->perspective, 'val', 'integer');
 110  
 111                                  break;
 112                              case 'plotArea':
 113                                  $plotAreaLayout = $XaxisLabel = $YaxisLabel = null;
 114                                  $plotSeries = $plotAttributes = [];
 115                                  $catAxRead = false;
 116                                  $plotNoFill = false;
 117                                  foreach ($chartDetails as $chartDetailKey => $chartDetail) {
 118                                      $chartDetail = Xlsx::testSimpleXml($chartDetail);
 119                                      switch ($chartDetailKey) {
 120                                          case 'spPr':
 121                                              $possibleNoFill = $chartDetails->spPr->children($this->aNamespace);
 122                                              if (isset($possibleNoFill->noFill)) {
 123                                                  $plotNoFill = true;
 124                                              }
 125                                              if (isset($possibleNoFill->gradFill->gsLst)) {
 126                                                  foreach ($possibleNoFill->gradFill->gsLst->gs as $gradient) {
 127                                                      $gradient = Xlsx::testSimpleXml($gradient);
 128                                                      /** @var float */
 129                                                      $pos = self::getAttribute($gradient, 'pos', 'float');
 130                                                      $gradientArray[] = [
 131                                                          $pos / ChartProperties::PERCENTAGE_MULTIPLIER,
 132                                                          new ChartColor($this->readColor($gradient)),
 133                                                      ];
 134                                                  }
 135                                              }
 136                                              if (isset($possibleNoFill->gradFill->lin)) {
 137                                                  $gradientLin = ChartProperties::XmlToAngle((string) self::getAttribute($possibleNoFill->gradFill->lin, 'ang', 'string'));
 138                                              }
 139  
 140                                              break;
 141                                          case 'layout':
 142                                              $plotAreaLayout = $this->chartLayoutDetails($chartDetail);
 143  
 144                                              break;
 145                                          case Axis::AXIS_TYPE_CATEGORY:
 146                                          case Axis::AXIS_TYPE_DATE:
 147                                              $catAxRead = true;
 148                                              if (isset($chartDetail->title)) {
 149                                                  $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
 150                                              }
 151                                              $xAxis->setAxisType($chartDetailKey);
 152                                              $this->readEffects($chartDetail, $xAxis);
 153                                              if (isset($chartDetail->spPr)) {
 154                                                  $sppr = $chartDetail->spPr->children($this->aNamespace);
 155                                                  if (isset($sppr->solidFill)) {
 156                                                      $axisColorArray = $this->readColor($sppr->solidFill);
 157                                                      $xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
 158                                                  }
 159                                              }
 160                                              if (isset($chartDetail->majorGridlines)) {
 161                                                  $majorGridlines = new GridLines();
 162                                                  if (isset($chartDetail->majorGridlines->spPr)) {
 163                                                      $this->readEffects($chartDetail->majorGridlines, $majorGridlines);
 164                                                      $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
 165                                                  }
 166                                                  $xAxis->setMajorGridlines($majorGridlines);
 167                                              }
 168                                              if (isset($chartDetail->minorGridlines)) {
 169                                                  $minorGridlines = new GridLines();
 170                                                  $minorGridlines->activateObject();
 171                                                  if (isset($chartDetail->minorGridlines->spPr)) {
 172                                                      $this->readEffects($chartDetail->minorGridlines, $minorGridlines);
 173                                                      $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
 174                                                  }
 175                                                  $xAxis->setMinorGridlines($minorGridlines);
 176                                              }
 177                                              $this->setAxisProperties($chartDetail, $xAxis);
 178  
 179                                              break;
 180                                          case Axis::AXIS_TYPE_VALUE:
 181                                              $whichAxis = null;
 182                                              $axPos = null;
 183                                              if (isset($chartDetail->axPos)) {
 184                                                  $axPos = self::getAttribute($chartDetail->axPos, 'val', 'string');
 185                                              }
 186                                              if ($catAxRead) {
 187                                                  $whichAxis = $yAxis;
 188                                                  $yAxis->setAxisType($chartDetailKey);
 189                                              } elseif (!empty($axPos)) {
 190                                                  switch ($axPos) {
 191                                                      case 't':
 192                                                      case 'b':
 193                                                          $whichAxis = $xAxis;
 194                                                          $xAxis->setAxisType($chartDetailKey);
 195  
 196                                                          break;
 197                                                      case 'r':
 198                                                      case 'l':
 199                                                          $whichAxis = $yAxis;
 200                                                          $yAxis->setAxisType($chartDetailKey);
 201  
 202                                                          break;
 203                                                  }
 204                                              }
 205                                              if (isset($chartDetail->title)) {
 206                                                  $axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
 207  
 208                                                  switch ($axPos) {
 209                                                      case 't':
 210                                                      case 'b':
 211                                                          $XaxisLabel = $axisLabel;
 212  
 213                                                          break;
 214                                                      case 'r':
 215                                                      case 'l':
 216                                                          $YaxisLabel = $axisLabel;
 217  
 218                                                          break;
 219                                                  }
 220                                              }
 221                                              $this->readEffects($chartDetail, $whichAxis);
 222                                              if ($whichAxis !== null && isset($chartDetail->spPr)) {
 223                                                  $sppr = $chartDetail->spPr->children($this->aNamespace);
 224                                                  if (isset($sppr->solidFill)) {
 225                                                      $axisColorArray = $this->readColor($sppr->solidFill);
 226                                                      $whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
 227                                                  }
 228                                              }
 229                                              if ($whichAxis !== null && isset($chartDetail->majorGridlines)) {
 230                                                  $majorGridlines = new GridLines();
 231                                                  if (isset($chartDetail->majorGridlines->spPr)) {
 232                                                      $this->readEffects($chartDetail->majorGridlines, $majorGridlines);
 233                                                      $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
 234                                                  }
 235                                                  $whichAxis->setMajorGridlines($majorGridlines);
 236                                              }
 237                                              if ($whichAxis !== null && isset($chartDetail->minorGridlines)) {
 238                                                  $minorGridlines = new GridLines();
 239                                                  $minorGridlines->activateObject();
 240                                                  if (isset($chartDetail->minorGridlines->spPr)) {
 241                                                      $this->readEffects($chartDetail->minorGridlines, $minorGridlines);
 242                                                      $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
 243                                                  }
 244                                                  $whichAxis->setMinorGridlines($minorGridlines);
 245                                              }
 246                                              $this->setAxisProperties($chartDetail, $whichAxis);
 247  
 248                                              break;
 249                                          case 'barChart':
 250                                          case 'bar3DChart':
 251                                              $barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string');
 252                                              $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
 253                                              $plotSer->setPlotDirection("$barDirection");
 254                                              $plotSeries[] = $plotSer;
 255                                              $plotAttributes = $this->readChartAttributes($chartDetail);
 256  
 257                                              break;
 258                                          case 'lineChart':
 259                                          case 'line3DChart':
 260                                              $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
 261                                              $plotAttributes = $this->readChartAttributes($chartDetail);
 262  
 263                                              break;
 264                                          case 'areaChart':
 265                                          case 'area3DChart':
 266                                              $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
 267                                              $plotAttributes = $this->readChartAttributes($chartDetail);
 268  
 269                                              break;
 270                                          case 'doughnutChart':
 271                                          case 'pieChart':
 272                                          case 'pie3DChart':
 273                                              $explosion = self::getAttribute($chartDetail->ser->explosion, 'val', 'string');
 274                                              $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
 275                                              $plotSer->setPlotStyle("$explosion");
 276                                              $plotSeries[] = $plotSer;
 277                                              $plotAttributes = $this->readChartAttributes($chartDetail);
 278  
 279                                              break;
 280                                          case 'scatterChart':
 281                                              /** @var string */
 282                                              $scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string');
 283                                              $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
 284                                              $plotSer->setPlotStyle($scatterStyle);
 285                                              $plotSeries[] = $plotSer;
 286                                              $plotAttributes = $this->readChartAttributes($chartDetail);
 287  
 288                                              break;
 289                                          case 'bubbleChart':
 290                                              $bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer');
 291                                              $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
 292                                              $plotSer->setPlotStyle("$bubbleScale");
 293                                              $plotSeries[] = $plotSer;
 294                                              $plotAttributes = $this->readChartAttributes($chartDetail);
 295  
 296                                              break;
 297                                          case 'radarChart':
 298                                              /** @var string */
 299                                              $radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string');
 300                                              $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
 301                                              $plotSer->setPlotStyle($radarStyle);
 302                                              $plotSeries[] = $plotSer;
 303                                              $plotAttributes = $this->readChartAttributes($chartDetail);
 304  
 305                                              break;
 306                                          case 'surfaceChart':
 307                                          case 'surface3DChart':
 308                                              $wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean');
 309                                              $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
 310                                              $plotSer->setPlotStyle("$wireFrame");
 311                                              $plotSeries[] = $plotSer;
 312                                              $plotAttributes = $this->readChartAttributes($chartDetail);
 313  
 314                                              break;
 315                                          case 'stockChart':
 316                                              $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
 317                                              $plotAttributes = $this->readChartAttributes($chartDetail);
 318  
 319                                              break;
 320                                      }
 321                                  }
 322                                  if ($plotAreaLayout == null) {
 323                                      $plotAreaLayout = new Layout();
 324                                  }
 325                                  $plotArea = new PlotArea($plotAreaLayout, $plotSeries);
 326                                  $this->setChartAttributes($plotAreaLayout, $plotAttributes);
 327                                  if ($plotNoFill) {
 328                                      $plotArea->setNoFill(true);
 329                                  }
 330                                  if (!empty($gradientArray)) {
 331                                      $plotArea->setGradientFillProperties($gradientArray, $gradientLin);
 332                                  }
 333  
 334                                  break;
 335                              case 'plotVisOnly':
 336                                  $plotVisOnly = self::getAttribute($chartDetails, 'val', 'string');
 337  
 338                                  break;
 339                              case 'dispBlanksAs':
 340                                  $dispBlanksAs = self::getAttribute($chartDetails, 'val', 'string');
 341  
 342                                  break;
 343                              case 'title':
 344                                  $title = $this->chartTitle($chartDetails);
 345  
 346                                  break;
 347                              case 'legend':
 348                                  $legendPos = 'r';
 349                                  $legendLayout = null;
 350                                  $legendOverlay = false;
 351                                  foreach ($chartDetails as $chartDetailKey => $chartDetail) {
 352                                      $chartDetail = Xlsx::testSimpleXml($chartDetail);
 353                                      switch ($chartDetailKey) {
 354                                          case 'legendPos':
 355                                              $legendPos = self::getAttribute($chartDetail, 'val', 'string');
 356  
 357                                              break;
 358                                          case 'overlay':
 359                                              $legendOverlay = self::getAttribute($chartDetail, 'val', 'boolean');
 360  
 361                                              break;
 362                                          case 'layout':
 363                                              $legendLayout = $this->chartLayoutDetails($chartDetail);
 364  
 365                                              break;
 366                                      }
 367                                  }
 368                                  $legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay);
 369  
 370                                  break;
 371                          }
 372                      }
 373              }
 374          }
 375          $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis);
 376          if ($chartNoFill) {
 377              $chart->setNoFill(true);
 378          }
 379          $chart->setRoundedCorners($roundedCorners);
 380          if (is_bool($autoTitleDeleted)) {
 381              $chart->setAutoTitleDeleted($autoTitleDeleted);
 382          }
 383          if (is_int($rotX)) {
 384              $chart->setRotX($rotX);
 385          }
 386          if (is_int($rotY)) {
 387              $chart->setRotY($rotY);
 388          }
 389          if (is_int($rAngAx)) {
 390              $chart->setRAngAx($rAngAx);
 391          }
 392          if (is_int($perspective)) {
 393              $chart->setPerspective($perspective);
 394          }
 395  
 396          return $chart;
 397      }
 398  
 399      private function chartTitle(SimpleXMLElement $titleDetails): Title
 400      {
 401          $caption = [];
 402          $titleLayout = null;
 403          foreach ($titleDetails as $titleDetailKey => $chartDetail) {
 404              $chartDetail = Xlsx::testSimpleXml($chartDetail);
 405              switch ($titleDetailKey) {
 406                  case 'tx':
 407                      if (isset($chartDetail->rich)) {
 408                          $titleDetails = $chartDetail->rich->children($this->aNamespace);
 409                          foreach ($titleDetails as $titleKey => $titleDetail) {
 410                              $titleDetail = Xlsx::testSimpleXml($titleDetail);
 411                              switch ($titleKey) {
 412                                  case 'p':
 413                                      $titleDetailPart = $titleDetail->children($this->aNamespace);
 414                                      $caption[] = $this->parseRichText($titleDetailPart);
 415                              }
 416                          }
 417                      } elseif (isset($chartDetail->strRef->strCache)) {
 418                          foreach ($chartDetail->strRef->strCache->pt as $pt) {
 419                              if (isset($pt->v)) {
 420                                  $caption[] = (string) $pt->v;
 421                              }
 422                          }
 423                      }
 424  
 425                      break;
 426                  case 'layout':
 427                      $titleLayout = $this->chartLayoutDetails($chartDetail);
 428  
 429                      break;
 430              }
 431          }
 432  
 433          return new Title($caption, $titleLayout);
 434      }
 435  
 436      private function chartLayoutDetails(SimpleXMLElement $chartDetail): ?Layout
 437      {
 438          if (!isset($chartDetail->manualLayout)) {
 439              return null;
 440          }
 441          $details = $chartDetail->manualLayout->children($this->cNamespace);
 442          if ($details === null) {
 443              return null;
 444          }
 445          $layout = [];
 446          foreach ($details as $detailKey => $detail) {
 447              $detail = Xlsx::testSimpleXml($detail);
 448              $layout[$detailKey] = self::getAttribute($detail, 'val', 'string');
 449          }
 450  
 451          return new Layout($layout);
 452      }
 453  
 454      private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType): DataSeries
 455      {
 456          $multiSeriesType = null;
 457          $smoothLine = false;
 458          $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = [];
 459  
 460          $seriesDetailSet = $chartDetail->children($this->cNamespace);
 461          foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
 462              switch ($seriesDetailKey) {
 463                  case 'grouping':
 464                      $multiSeriesType = self::getAttribute($chartDetail->grouping, 'val', 'string');
 465  
 466                      break;
 467                  case 'ser':
 468                      $marker = null;
 469                      $seriesIndex = '';
 470                      $fillColor = null;
 471                      $pointSize = null;
 472                      $noFill = false;
 473                      $bubble3D = false;
 474                      $dptColors = [];
 475                      $markerFillColor = null;
 476                      $markerBorderColor = null;
 477                      $lineStyle = null;
 478                      $labelLayout = null;
 479                      $trendLines = [];
 480                      foreach ($seriesDetails as $seriesKey => $seriesDetail) {
 481                          $seriesDetail = Xlsx::testSimpleXml($seriesDetail);
 482                          switch ($seriesKey) {
 483                              case 'idx':
 484                                  $seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer');
 485  
 486                                  break;
 487                              case 'order':
 488                                  $seriesOrder = self::getAttribute($seriesDetail, 'val', 'integer');
 489                                  $plotOrder[$seriesIndex] = $seriesOrder;
 490  
 491                                  break;
 492                              case 'tx':
 493                                  $seriesLabel[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail);
 494  
 495                                  break;
 496                              case 'spPr':
 497                                  $children = $seriesDetail->children($this->aNamespace);
 498                                  if (isset($children->ln)) {
 499                                      $ln = $children->ln;
 500                                      if (is_countable($ln->noFill) && count($ln->noFill) === 1) {
 501                                          $noFill = true;
 502                                      }
 503                                      $lineStyle = new GridLines();
 504                                      $this->readLineStyle($seriesDetails, $lineStyle);
 505                                  }
 506                                  if (isset($children->effectLst)) {
 507                                      if ($lineStyle === null) {
 508                                          $lineStyle = new GridLines();
 509                                      }
 510                                      $this->readEffects($seriesDetails, $lineStyle);
 511                                  }
 512                                  if (isset($children->solidFill)) {
 513                                      $fillColor = new ChartColor($this->readColor($children->solidFill));
 514                                  }
 515  
 516                                  break;
 517                              case 'dPt':
 518                                  $dptIdx = (int) self::getAttribute($seriesDetail->idx, 'val', 'string');
 519                                  if (isset($seriesDetail->spPr)) {
 520                                      $children = $seriesDetail->spPr->children($this->aNamespace);
 521                                      if (isset($children->solidFill)) {
 522                                          $arrayColors = $this->readColor($children->solidFill);
 523                                          $dptColors[$dptIdx] = new ChartColor($arrayColors);
 524                                      }
 525                                  }
 526  
 527                                  break;
 528                              case 'trendline':
 529                                  $trendLine = new TrendLine();
 530                                  $this->readLineStyle($seriesDetail, $trendLine);
 531                                  /** @var ?string */
 532                                  $trendLineType = self::getAttribute($seriesDetail->trendlineType, 'val', 'string');
 533                                  /** @var ?bool */
 534                                  $dispRSqr = self::getAttribute($seriesDetail->dispRSqr, 'val', 'boolean');
 535                                  /** @var ?bool */
 536                                  $dispEq = self::getAttribute($seriesDetail->dispEq, 'val', 'boolean');
 537                                  /** @var ?int */
 538                                  $order = self::getAttribute($seriesDetail->order, 'val', 'integer');
 539                                  /** @var ?int */
 540                                  $period = self::getAttribute($seriesDetail->period, 'val', 'integer');
 541                                  /** @var ?float */
 542                                  $forward = self::getAttribute($seriesDetail->forward, 'val', 'float');
 543                                  /** @var ?float */
 544                                  $backward = self::getAttribute($seriesDetail->backward, 'val', 'float');
 545                                  /** @var ?float */
 546                                  $intercept = self::getAttribute($seriesDetail->intercept, 'val', 'float');
 547                                  /** @var ?string */
 548                                  $name = (string) $seriesDetail->name;
 549                                  $trendLine->setTrendLineProperties(
 550                                      $trendLineType,
 551                                      $order,
 552                                      $period,
 553                                      $dispRSqr,
 554                                      $dispEq,
 555                                      $backward,
 556                                      $forward,
 557                                      $intercept,
 558                                      $name
 559                                  );
 560                                  $trendLines[] = $trendLine;
 561  
 562                                  break;
 563                              case 'marker':
 564                                  $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string');
 565                                  $pointSize = self::getAttribute($seriesDetail->size, 'val', 'string');
 566                                  $pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null;
 567                                  if (isset($seriesDetail->spPr)) {
 568                                      $children = $seriesDetail->spPr->children($this->aNamespace);
 569                                      if (isset($children->solidFill)) {
 570                                          $markerFillColor = $this->readColor($children->solidFill);
 571                                      }
 572                                      if (isset($children->ln->solidFill)) {
 573                                          $markerBorderColor = $this->readColor($children->ln->solidFill);
 574                                      }
 575                                  }
 576  
 577                                  break;
 578                              case 'smooth':
 579                                  $smoothLine = self::getAttribute($seriesDetail, 'val', 'boolean');
 580  
 581                                  break;
 582                              case 'cat':
 583                                  $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail);
 584  
 585                                  break;
 586                              case 'val':
 587                                  $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
 588  
 589                                  break;
 590                              case 'xVal':
 591                                  $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
 592  
 593                                  break;
 594                              case 'yVal':
 595                                  $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
 596  
 597                                  break;
 598                              case 'bubbleSize':
 599                                  $seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
 600  
 601                                  break;
 602                              case 'bubble3D':
 603                                  $bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean');
 604  
 605                                  break;
 606                              case 'dLbls':
 607                                  $labelLayout = new Layout($this->readChartAttributes($seriesDetails));
 608  
 609                                  break;
 610                          }
 611                      }
 612                      if ($labelLayout) {
 613                          if (isset($seriesLabel[$seriesIndex])) {
 614                              $seriesLabel[$seriesIndex]->setLabelLayout($labelLayout);
 615                          }
 616                          if (isset($seriesCategory[$seriesIndex])) {
 617                              $seriesCategory[$seriesIndex]->setLabelLayout($labelLayout);
 618                          }
 619                          if (isset($seriesValues[$seriesIndex])) {
 620                              $seriesValues[$seriesIndex]->setLabelLayout($labelLayout);
 621                          }
 622                      }
 623                      if ($noFill) {
 624                          if (isset($seriesLabel[$seriesIndex])) {
 625                              $seriesLabel[$seriesIndex]->setScatterLines(false);
 626                          }
 627                          if (isset($seriesCategory[$seriesIndex])) {
 628                              $seriesCategory[$seriesIndex]->setScatterLines(false);
 629                          }
 630                          if (isset($seriesValues[$seriesIndex])) {
 631                              $seriesValues[$seriesIndex]->setScatterLines(false);
 632                          }
 633                      }
 634                      if ($lineStyle !== null) {
 635                          if (isset($seriesLabel[$seriesIndex])) {
 636                              $seriesLabel[$seriesIndex]->copyLineStyles($lineStyle);
 637                          }
 638                          if (isset($seriesCategory[$seriesIndex])) {
 639                              $seriesCategory[$seriesIndex]->copyLineStyles($lineStyle);
 640                          }
 641                          if (isset($seriesValues[$seriesIndex])) {
 642                              $seriesValues[$seriesIndex]->copyLineStyles($lineStyle);
 643                          }
 644                      }
 645                      if ($bubble3D) {
 646                          if (isset($seriesLabel[$seriesIndex])) {
 647                              $seriesLabel[$seriesIndex]->setBubble3D($bubble3D);
 648                          }
 649                          if (isset($seriesCategory[$seriesIndex])) {
 650                              $seriesCategory[$seriesIndex]->setBubble3D($bubble3D);
 651                          }
 652                          if (isset($seriesValues[$seriesIndex])) {
 653                              $seriesValues[$seriesIndex]->setBubble3D($bubble3D);
 654                          }
 655                      }
 656                      if (!empty($dptColors)) {
 657                          if (isset($seriesLabel[$seriesIndex])) {
 658                              $seriesLabel[$seriesIndex]->setFillColor($dptColors);
 659                          }
 660                          if (isset($seriesCategory[$seriesIndex])) {
 661                              $seriesCategory[$seriesIndex]->setFillColor($dptColors);
 662                          }
 663                          if (isset($seriesValues[$seriesIndex])) {
 664                              $seriesValues[$seriesIndex]->setFillColor($dptColors);
 665                          }
 666                      }
 667                      if ($markerFillColor !== null) {
 668                          if (isset($seriesLabel[$seriesIndex])) {
 669                              $seriesLabel[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
 670                          }
 671                          if (isset($seriesCategory[$seriesIndex])) {
 672                              $seriesCategory[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
 673                          }
 674                          if (isset($seriesValues[$seriesIndex])) {
 675                              $seriesValues[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
 676                          }
 677                      }
 678                      if ($markerBorderColor !== null) {
 679                          if (isset($seriesLabel[$seriesIndex])) {
 680                              $seriesLabel[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
 681                          }
 682                          if (isset($seriesCategory[$seriesIndex])) {
 683                              $seriesCategory[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
 684                          }
 685                          if (isset($seriesValues[$seriesIndex])) {
 686                              $seriesValues[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
 687                          }
 688                      }
 689                      if ($smoothLine) {
 690                          if (isset($seriesLabel[$seriesIndex])) {
 691                              $seriesLabel[$seriesIndex]->setSmoothLine(true);
 692                          }
 693                          if (isset($seriesCategory[$seriesIndex])) {
 694                              $seriesCategory[$seriesIndex]->setSmoothLine(true);
 695                          }
 696                          if (isset($seriesValues[$seriesIndex])) {
 697                              $seriesValues[$seriesIndex]->setSmoothLine(true);
 698                          }
 699                      }
 700                      if (!empty($trendLines)) {
 701                          if (isset($seriesLabel[$seriesIndex])) {
 702                              $seriesLabel[$seriesIndex]->setTrendLines($trendLines);
 703                          }
 704                          if (isset($seriesCategory[$seriesIndex])) {
 705                              $seriesCategory[$seriesIndex]->setTrendLines($trendLines);
 706                          }
 707                          if (isset($seriesValues[$seriesIndex])) {
 708                              $seriesValues[$seriesIndex]->setTrendLines($trendLines);
 709                          }
 710                      }
 711              }
 712          }
 713          /** @phpstan-ignore-next-line */
 714          $series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine);
 715          $series->setPlotBubbleSizes($seriesBubbles);
 716  
 717          return $series;
 718      }
 719  
 720      /**
 721       * @return mixed
 722       */
 723      private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?ChartColor $fillColor = null, ?string $pointSize = null)
 724      {
 725          if (isset($seriesDetail->strRef)) {
 726              $seriesSource = (string) $seriesDetail->strRef->f;
 727              $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
 728  
 729              if (isset($seriesDetail->strRef->strCache)) {
 730                  $seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's');
 731                  $seriesValues
 732                      ->setFormatCode($seriesData['formatCode'])
 733                      ->setDataValues($seriesData['dataValues']);
 734              }
 735  
 736              return $seriesValues;
 737          } elseif (isset($seriesDetail->numRef)) {
 738              $seriesSource = (string) $seriesDetail->numRef->f;
 739              $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
 740              if (isset($seriesDetail->numRef->numCache)) {
 741                  $seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace));
 742                  $seriesValues
 743                      ->setFormatCode($seriesData['formatCode'])
 744                      ->setDataValues($seriesData['dataValues']);
 745              }
 746  
 747              return $seriesValues;
 748          } elseif (isset($seriesDetail->multiLvlStrRef)) {
 749              $seriesSource = (string) $seriesDetail->multiLvlStrRef->f;
 750              $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
 751  
 752              if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) {
 753                  $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's');
 754                  $seriesValues
 755                      ->setFormatCode($seriesData['formatCode'])
 756                      ->setDataValues($seriesData['dataValues']);
 757              }
 758  
 759              return $seriesValues;
 760          } elseif (isset($seriesDetail->multiLvlNumRef)) {
 761              $seriesSource = (string) $seriesDetail->multiLvlNumRef->f;
 762              $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
 763  
 764              if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) {
 765                  $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's');
 766                  $seriesValues
 767                      ->setFormatCode($seriesData['formatCode'])
 768                      ->setDataValues($seriesData['dataValues']);
 769              }
 770  
 771              return $seriesValues;
 772          }
 773  
 774          if (isset($seriesDetail->v)) {
 775              return new DataSeriesValues(
 776                  DataSeriesValues::DATASERIES_TYPE_STRING,
 777                  null,
 778                  null,
 779                  1,
 780                  [(string) $seriesDetail->v]
 781              );
 782          }
 783  
 784          return null;
 785      }
 786  
 787      private function chartDataSeriesValues(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
 788      {
 789          $seriesVal = [];
 790          $formatCode = '';
 791          $pointCount = 0;
 792  
 793          foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) {
 794              $seriesValue = Xlsx::testSimpleXml($seriesValue);
 795              switch ($seriesValueIdx) {
 796                  case 'ptCount':
 797                      $pointCount = self::getAttribute($seriesValue, 'val', 'integer');
 798  
 799                      break;
 800                  case 'formatCode':
 801                      $formatCode = (string) $seriesValue;
 802  
 803                      break;
 804                  case 'pt':
 805                      $pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
 806                      if ($dataType == 's') {
 807                          $seriesVal[$pointVal] = (string) $seriesValue->v;
 808                      } elseif ((string) $seriesValue->v === ExcelError::NA()) {
 809                          $seriesVal[$pointVal] = null;
 810                      } else {
 811                          $seriesVal[$pointVal] = (float) $seriesValue->v;
 812                      }
 813  
 814                      break;
 815              }
 816          }
 817  
 818          return [
 819              'formatCode' => $formatCode,
 820              'pointCount' => $pointCount,
 821              'dataValues' => $seriesVal,
 822          ];
 823      }
 824  
 825      private function chartDataSeriesValuesMultiLevel(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
 826      {
 827          $seriesVal = [];
 828          $formatCode = '';
 829          $pointCount = 0;
 830  
 831          foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) {
 832              foreach ($seriesLevel as $seriesValueIdx => $seriesValue) {
 833                  switch ($seriesValueIdx) {
 834                      case 'ptCount':
 835                          $pointCount = self::getAttribute($seriesValue, 'val', 'integer');
 836  
 837                          break;
 838                      case 'formatCode':
 839                          $formatCode = (string) $seriesValue;
 840  
 841                          break;
 842                      case 'pt':
 843                          $pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
 844                          if ($dataType == 's') {
 845                              $seriesVal[$pointVal][] = (string) $seriesValue->v;
 846                          } elseif ((string) $seriesValue->v === ExcelError::NA()) {
 847                              $seriesVal[$pointVal] = null;
 848                          } else {
 849                              $seriesVal[$pointVal][] = (float) $seriesValue->v;
 850                          }
 851  
 852                          break;
 853                  }
 854              }
 855          }
 856  
 857          return [
 858              'formatCode' => $formatCode,
 859              'pointCount' => $pointCount,
 860              'dataValues' => $seriesVal,
 861          ];
 862      }
 863  
 864      private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
 865      {
 866          $value = new RichText();
 867          $defaultFontSize = null;
 868          $defaultBold = null;
 869          $defaultItalic = null;
 870          $defaultUnderscore = null;
 871          $defaultStrikethrough = null;
 872          $defaultBaseline = null;
 873          $defaultFontName = null;
 874          $defaultLatin = null;
 875          $defaultEastAsian = null;
 876          $defaultComplexScript = null;
 877          $defaultFontColor = null;
 878          if (isset($titleDetailPart->pPr->defRPr)) {
 879              /** @var ?int */
 880              $defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
 881              /** @var ?bool */
 882              $defaultBold = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean');
 883              /** @var ?bool */
 884              $defaultItalic = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean');
 885              /** @var ?string */
 886              $defaultUnderscore = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string');
 887              /** @var ?string */
 888              $defaultStrikethrough = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string');
 889              /** @var ?int */
 890              $defaultBaseline = self::getAttribute($titleDetailPart->pPr->defRPr, 'baseline', 'integer');
 891              if (isset($titleDetailPart->defRPr->rFont['val'])) {
 892                  $defaultFontName = (string) $titleDetailPart->defRPr->rFont['val'];
 893              }
 894              if (isset($titleDetailPart->pPr->defRPr->latin)) {
 895                  /** @var ?string */
 896                  $defaultLatin = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
 897              }
 898              if (isset($titleDetailPart->pPr->defRPr->ea)) {
 899                  /** @var ?string */
 900                  $defaultEastAsian = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string');
 901              }
 902              if (isset($titleDetailPart->pPr->defRPr->cs)) {
 903                  /** @var ?string */
 904                  $defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
 905              }
 906              if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
 907                  $defaultFontColor = $this->readColor($titleDetailPart->pPr->defRPr->solidFill);
 908              }
 909          }
 910          foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
 911              if (
 912                  (string) $titleDetailElementKey !== 'r'
 913                  || !isset($titleDetailElement->t)
 914              ) {
 915                  continue;
 916              }
 917              $objText = $value->createTextRun((string) $titleDetailElement->t);
 918              if ($objText->getFont() === null) {
 919                  // @codeCoverageIgnoreStart
 920                  continue;
 921                  // @codeCoverageIgnoreEnd
 922              }
 923              $fontSize = null;
 924              $bold = null;
 925              $italic = null;
 926              $underscore = null;
 927              $strikethrough = null;
 928              $baseline = null;
 929              $fontName = null;
 930              $latinName = null;
 931              $eastAsian = null;
 932              $complexScript = null;
 933              $fontColor = null;
 934              $underlineColor = null;
 935              if (isset($titleDetailElement->rPr)) {
 936                  // not used now, not sure it ever was, grandfathering
 937                  if (isset($titleDetailElement->rPr->rFont['val'])) {
 938                      // @codeCoverageIgnoreStart
 939                      $fontName = (string) $titleDetailElement->rPr->rFont['val'];
 940                      // @codeCoverageIgnoreEnd
 941                  }
 942                  if (isset($titleDetailElement->rPr->latin)) {
 943                      /** @var ?string */
 944                      $latinName = self::getAttribute($titleDetailElement->rPr->latin, 'typeface', 'string');
 945                  }
 946                  if (isset($titleDetailElement->rPr->ea)) {
 947                      /** @var ?string */
 948                      $eastAsian = self::getAttribute($titleDetailElement->rPr->ea, 'typeface', 'string');
 949                  }
 950                  if (isset($titleDetailElement->rPr->cs)) {
 951                      /** @var ?string */
 952                      $complexScript = self::getAttribute($titleDetailElement->rPr->cs, 'typeface', 'string');
 953                  }
 954                  /** @var ?int */
 955                  $fontSize = self::getAttribute($titleDetailElement->rPr, 'sz', 'integer');
 956  
 957                  // not used now, not sure it ever was, grandfathering
 958                  if (isset($titleDetailElement->rPr->solidFill)) {
 959                      $fontColor = $this->readColor($titleDetailElement->rPr->solidFill);
 960                  }
 961  
 962                  /** @var ?bool */
 963                  $bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean');
 964  
 965                  /** @var ?bool */
 966                  $italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean');
 967  
 968                  /** @var ?int */
 969                  $baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer');
 970  
 971                  /** @var ?string */
 972                  $underscore = self::getAttribute($titleDetailElement->rPr, 'u', 'string');
 973                  if (isset($titleDetailElement->rPr->uFill->solidFill)) {
 974                      $underlineColor = $this->readColor($titleDetailElement->rPr->uFill->solidFill);
 975                  }
 976  
 977                  /** @var ?string */
 978                  $strikethrough = self::getAttribute($titleDetailElement->rPr, 'strike', 'string');
 979              }
 980  
 981              $fontFound = false;
 982              $latinName = $latinName ?? $defaultLatin;
 983              if ($latinName !== null) {
 984                  $objText->getFont()->setLatin($latinName);
 985                  $fontFound = true;
 986              }
 987              $eastAsian = $eastAsian ?? $defaultEastAsian;
 988              if ($eastAsian !== null) {
 989                  $objText->getFont()->setEastAsian($eastAsian);
 990                  $fontFound = true;
 991              }
 992              $complexScript = $complexScript ?? $defaultComplexScript;
 993              if ($complexScript !== null) {
 994                  $objText->getFont()->setComplexScript($complexScript);
 995                  $fontFound = true;
 996              }
 997              $fontName = $fontName ?? $defaultFontName;
 998              if ($fontName !== null) {
 999                  // @codeCoverageIgnoreStart
1000                  $objText->getFont()->setName($fontName);
1001                  $fontFound = true;
1002                  // @codeCoverageIgnoreEnd
1003              }
1004  
1005              $fontSize = $fontSize ?? $defaultFontSize;
1006              if (is_int($fontSize)) {
1007                  $objText->getFont()->setSize(floor($fontSize / 100));
1008                  $fontFound = true;
1009              } else {
1010                  $objText->getFont()->setSize(null, true);
1011              }
1012  
1013              $fontColor = $fontColor ?? $defaultFontColor;
1014              if (!empty($fontColor)) {
1015                  $objText->getFont()->setChartColor($fontColor);
1016                  $fontFound = true;
1017              }
1018  
1019              $bold = $bold ?? $defaultBold;
1020              if ($bold !== null) {
1021                  $objText->getFont()->setBold($bold);
1022                  $fontFound = true;
1023              }
1024  
1025              $italic = $italic ?? $defaultItalic;
1026              if ($italic !== null) {
1027                  $objText->getFont()->setItalic($italic);
1028                  $fontFound = true;
1029              }
1030  
1031              $baseline = $baseline ?? $defaultBaseline;
1032              if ($baseline !== null) {
1033                  $objText->getFont()->setBaseLine($baseline);
1034                  if ($baseline > 0) {
1035                      $objText->getFont()->setSuperscript(true);
1036                  } elseif ($baseline < 0) {
1037                      $objText->getFont()->setSubscript(true);
1038                  }
1039                  $fontFound = true;
1040              }
1041  
1042              $underscore = $underscore ?? $defaultUnderscore;
1043              if ($underscore !== null) {
1044                  if ($underscore == 'sng') {
1045                      $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
1046                  } elseif ($underscore == 'dbl') {
1047                      $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
1048                  } elseif ($underscore !== '') {
1049                      $objText->getFont()->setUnderline($underscore);
1050                  } else {
1051                      $objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
1052                  }
1053                  $fontFound = true;
1054                  if ($underlineColor) {
1055                      $objText->getFont()->setUnderlineColor($underlineColor);
1056                  }
1057              }
1058  
1059              $strikethrough = $strikethrough ?? $defaultStrikethrough;
1060              if ($strikethrough !== null) {
1061                  $objText->getFont()->setStrikeType($strikethrough);
1062                  if ($strikethrough == 'noStrike') {
1063                      $objText->getFont()->setStrikethrough(false);
1064                  } else {
1065                      $objText->getFont()->setStrikethrough(true);
1066                  }
1067                  $fontFound = true;
1068              }
1069              if ($fontFound === false) {
1070                  $objText->setFont(null);
1071              }
1072          }
1073  
1074          return $value;
1075      }
1076  
1077      /**
1078       * @param ?SimpleXMLElement $chartDetail
1079       */
1080      private function readChartAttributes($chartDetail): array
1081      {
1082          $plotAttributes = [];
1083          if (isset($chartDetail->dLbls)) {
1084              if (isset($chartDetail->dLbls->dLblPos)) {
1085                  $plotAttributes['dLblPos'] = self::getAttribute($chartDetail->dLbls->dLblPos, 'val', 'string');
1086              }
1087              if (isset($chartDetail->dLbls->numFmt)) {
1088                  $plotAttributes['numFmtCode'] = self::getAttribute($chartDetail->dLbls->numFmt, 'formatCode', 'string');
1089                  $plotAttributes['numFmtLinked'] = self::getAttribute($chartDetail->dLbls->numFmt, 'sourceLinked', 'boolean');
1090              }
1091              if (isset($chartDetail->dLbls->showLegendKey)) {
1092                  $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string');
1093              }
1094              if (isset($chartDetail->dLbls->showVal)) {
1095                  $plotAttributes['showVal'] = self::getAttribute($chartDetail->dLbls->showVal, 'val', 'string');
1096              }
1097              if (isset($chartDetail->dLbls->showCatName)) {
1098                  $plotAttributes['showCatName'] = self::getAttribute($chartDetail->dLbls->showCatName, 'val', 'string');
1099              }
1100              if (isset($chartDetail->dLbls->showSerName)) {
1101                  $plotAttributes['showSerName'] = self::getAttribute($chartDetail->dLbls->showSerName, 'val', 'string');
1102              }
1103              if (isset($chartDetail->dLbls->showPercent)) {
1104                  $plotAttributes['showPercent'] = self::getAttribute($chartDetail->dLbls->showPercent, 'val', 'string');
1105              }
1106              if (isset($chartDetail->dLbls->showBubbleSize)) {
1107                  $plotAttributes['showBubbleSize'] = self::getAttribute($chartDetail->dLbls->showBubbleSize, 'val', 'string');
1108              }
1109              if (isset($chartDetail->dLbls->showLeaderLines)) {
1110                  $plotAttributes['showLeaderLines'] = self::getAttribute($chartDetail->dLbls->showLeaderLines, 'val', 'string');
1111              }
1112              if (isset($chartDetail->dLbls->spPr)) {
1113                  $sppr = $chartDetail->dLbls->spPr->children($this->aNamespace);
1114                  if (isset($sppr->solidFill)) {
1115                      $plotAttributes['labelFillColor'] = new ChartColor($this->readColor($sppr->solidFill));
1116                  }
1117                  if (isset($sppr->ln->solidFill)) {
1118                      $plotAttributes['labelBorderColor'] = new ChartColor($this->readColor($sppr->ln->solidFill));
1119                  }
1120              }
1121              if (isset($chartDetail->dLbls->txPr)) {
1122                  $txpr = $chartDetail->dLbls->txPr->children($this->aNamespace);
1123                  if (isset($txpr->p->pPr->defRPr->solidFill)) {
1124                      $plotAttributes['labelFontColor'] = new ChartColor($this->readColor($txpr->p->pPr->defRPr->solidFill));
1125                  }
1126              }
1127          }
1128  
1129          return $plotAttributes;
1130      }
1131  
1132      /**
1133       * @param mixed $plotAttributes
1134       */
1135      private function setChartAttributes(Layout $plotArea, $plotAttributes): void
1136      {
1137          foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) {
1138              switch ($plotAttributeKey) {
1139                  case 'showLegendKey':
1140                      $plotArea->setShowLegendKey($plotAttributeValue);
1141  
1142                      break;
1143                  case 'showVal':
1144                      $plotArea->setShowVal($plotAttributeValue);
1145  
1146                      break;
1147                  case 'showCatName':
1148                      $plotArea->setShowCatName($plotAttributeValue);
1149  
1150                      break;
1151                  case 'showSerName':
1152                      $plotArea->setShowSerName($plotAttributeValue);
1153  
1154                      break;
1155                  case 'showPercent':
1156                      $plotArea->setShowPercent($plotAttributeValue);
1157  
1158                      break;
1159                  case 'showBubbleSize':
1160                      $plotArea->setShowBubbleSize($plotAttributeValue);
1161  
1162                      break;
1163                  case 'showLeaderLines':
1164                      $plotArea->setShowLeaderLines($plotAttributeValue);
1165  
1166                      break;
1167              }
1168          }
1169      }
1170  
1171      private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void
1172      {
1173          if (!isset($chartObject, $chartDetail->spPr)) {
1174              return;
1175          }
1176          $sppr = $chartDetail->spPr->children($this->aNamespace);
1177  
1178          if (isset($sppr->effectLst->glow)) {
1179              $axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / ChartProperties::POINTS_WIDTH_MULTIPLIER;
1180              if ($axisGlowSize != 0.0) {
1181                  $colorArray = $this->readColor($sppr->effectLst->glow);
1182                  $chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']);
1183              }
1184          }
1185  
1186          if (isset($sppr->effectLst->softEdge)) {
1187              /** @var string */
1188              $softEdgeSize = self::getAttribute($sppr->effectLst->softEdge, 'rad', 'string');
1189              if (is_numeric($softEdgeSize)) {
1190                  $chartObject->setSoftEdges((float) ChartProperties::xmlToPoints($softEdgeSize));
1191              }
1192          }
1193  
1194          $type = '';
1195          foreach (self::SHADOW_TYPES as $shadowType) {
1196              if (isset($sppr->effectLst->$shadowType)) {
1197                  $type = $shadowType;
1198  
1199                  break;
1200              }
1201          }
1202          if ($type !== '') {
1203              /** @var string */
1204              $blur = self::getAttribute($sppr->effectLst->$type, 'blurRad', 'string');
1205              $blur = is_numeric($blur) ? ChartProperties::xmlToPoints($blur) : null;
1206              /** @var string */
1207              $dist = self::getAttribute($sppr->effectLst->$type, 'dist', 'string');
1208              $dist = is_numeric($dist) ? ChartProperties::xmlToPoints($dist) : null;
1209              /** @var string */
1210              $direction = self::getAttribute($sppr->effectLst->$type, 'dir', 'string');
1211              $direction = is_numeric($direction) ? ChartProperties::xmlToAngle($direction) : null;
1212              $algn = self::getAttribute($sppr->effectLst->$type, 'algn', 'string');
1213              $rot = self::getAttribute($sppr->effectLst->$type, 'rotWithShape', 'string');
1214              $size = [];
1215              foreach (['sx', 'sy'] as $sizeType) {
1216                  $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string');
1217                  if (is_numeric($sizeValue)) {
1218                      $size[$sizeType] = ChartProperties::xmlToTenthOfPercent((string) $sizeValue);
1219                  } else {
1220                      $size[$sizeType] = null;
1221                  }
1222              }
1223              foreach (['kx', 'ky'] as $sizeType) {
1224                  $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string');
1225                  if (is_numeric($sizeValue)) {
1226                      $size[$sizeType] = ChartProperties::xmlToAngle((string) $sizeValue);
1227                  } else {
1228                      $size[$sizeType] = null;
1229                  }
1230              }
1231              $colorArray = $this->readColor($sppr->effectLst->$type);
1232              $chartObject
1233                  ->setShadowProperty('effect', $type)
1234                  ->setShadowProperty('blur', $blur)
1235                  ->setShadowProperty('direction', $direction)
1236                  ->setShadowProperty('distance', $dist)
1237                  ->setShadowProperty('algn', $algn)
1238                  ->setShadowProperty('rotWithShape', $rot)
1239                  ->setShadowProperty('size', $size)
1240                  ->setShadowProperty('color', $colorArray);
1241          }
1242      }
1243  
1244      private const SHADOW_TYPES = [
1245          'outerShdw',
1246          'innerShdw',
1247      ];
1248  
1249      private function readColor(SimpleXMLElement $colorXml): array
1250      {
1251          $result = [
1252              'type' => null,
1253              'value' => null,
1254              'alpha' => null,
1255              'brightness' => null,
1256          ];
1257          foreach (ChartColor::EXCEL_COLOR_TYPES as $type) {
1258              if (isset($colorXml->$type)) {
1259                  $result['type'] = $type;
1260                  $result['value'] = self::getAttribute($colorXml->$type, 'val', 'string');
1261                  if (isset($colorXml->$type->alpha)) {
1262                      /** @var string */
1263                      $alpha = self::getAttribute($colorXml->$type->alpha, 'val', 'string');
1264                      if (is_numeric($alpha)) {
1265                          $result['alpha'] = ChartColor::alphaFromXml($alpha);
1266                      }
1267                  }
1268                  if (isset($colorXml->$type->lumMod)) {
1269                      /** @var string */
1270                      $brightness = self::getAttribute($colorXml->$type->lumMod, 'val', 'string');
1271                      if (is_numeric($brightness)) {
1272                          $result['brightness'] = ChartColor::alphaFromXml($brightness);
1273                      }
1274                  }
1275  
1276                  break;
1277              }
1278          }
1279  
1280          return $result;
1281      }
1282  
1283      private function readLineStyle(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void
1284      {
1285          if (!isset($chartObject, $chartDetail->spPr)) {
1286              return;
1287          }
1288          $sppr = $chartDetail->spPr->children($this->aNamespace);
1289  
1290          if (!isset($sppr->ln)) {
1291              return;
1292          }
1293          $lineWidth = null;
1294          /** @var string */
1295          $lineWidthTemp = self::getAttribute($sppr->ln, 'w', 'string');
1296          if (is_numeric($lineWidthTemp)) {
1297              $lineWidth = ChartProperties::xmlToPoints($lineWidthTemp);
1298          }
1299          /** @var string */
1300          $compoundType = self::getAttribute($sppr->ln, 'cmpd', 'string');
1301          /** @var string */
1302          $dashType = self::getAttribute($sppr->ln->prstDash, 'val', 'string');
1303          /** @var string */
1304          $capType = self::getAttribute($sppr->ln, 'cap', 'string');
1305          if (isset($sppr->ln->miter)) {
1306              $joinType = ChartProperties::LINE_STYLE_JOIN_MITER;
1307          } elseif (isset($sppr->ln->bevel)) {
1308              $joinType = ChartProperties::LINE_STYLE_JOIN_BEVEL;
1309          } else {
1310              $joinType = '';
1311          }
1312          $headArrowSize = '';
1313          $endArrowSize = '';
1314          /** @var string */
1315          $headArrowType = self::getAttribute($sppr->ln->headEnd, 'type', 'string');
1316          /** @var string */
1317          $headArrowWidth = self::getAttribute($sppr->ln->headEnd, 'w', 'string');
1318          /** @var string */
1319          $headArrowLength = self::getAttribute($sppr->ln->headEnd, 'len', 'string');
1320          /** @var string */
1321          $endArrowType = self::getAttribute($sppr->ln->tailEnd, 'type', 'string');
1322          /** @var string */
1323          $endArrowWidth = self::getAttribute($sppr->ln->tailEnd, 'w', 'string');
1324          /** @var string */
1325          $endArrowLength = self::getAttribute($sppr->ln->tailEnd, 'len', 'string');
1326          $chartObject->setLineStyleProperties(
1327              $lineWidth,
1328              $compoundType,
1329              $dashType,
1330              $capType,
1331              $joinType,
1332              $headArrowType,
1333              $headArrowSize,
1334              $endArrowType,
1335              $endArrowSize,
1336              $headArrowWidth,
1337              $headArrowLength,
1338              $endArrowWidth,
1339              $endArrowLength
1340          );
1341          $colorArray = $this->readColor($sppr->ln->solidFill);
1342          $chartObject->getLineColor()->setColorPropertiesArray($colorArray);
1343      }
1344  
1345      private function setAxisProperties(SimpleXMLElement $chartDetail, ?Axis $whichAxis): void
1346      {
1347          if (!isset($whichAxis)) {
1348              return;
1349          }
1350          if (isset($chartDetail->delete)) {
1351              $whichAxis->setAxisOption('hidden', (string) self::getAttribute($chartDetail->delete, 'val', 'string'));
1352          }
1353          if (isset($chartDetail->numFmt)) {
1354              $whichAxis->setAxisNumberProperties(
1355                  (string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'),
1356                  null,
1357                  (int) self::getAttribute($chartDetail->numFmt, 'sourceLinked', 'int')
1358              );
1359          }
1360          if (isset($chartDetail->crossBetween)) {
1361              $whichAxis->setCrossBetween((string) self::getAttribute($chartDetail->crossBetween, 'val', 'string'));
1362          }
1363          if (isset($chartDetail->majorTickMark)) {
1364              $whichAxis->setAxisOption('major_tick_mark', (string) self::getAttribute($chartDetail->majorTickMark, 'val', 'string'));
1365          }
1366          if (isset($chartDetail->minorTickMark)) {
1367              $whichAxis->setAxisOption('minor_tick_mark', (string) self::getAttribute($chartDetail->minorTickMark, 'val', 'string'));
1368          }
1369          if (isset($chartDetail->tickLblPos)) {
1370              $whichAxis->setAxisOption('axis_labels', (string) self::getAttribute($chartDetail->tickLblPos, 'val', 'string'));
1371          }
1372          if (isset($chartDetail->crosses)) {
1373              $whichAxis->setAxisOption('horizontal_crosses', (string) self::getAttribute($chartDetail->crosses, 'val', 'string'));
1374          }
1375          if (isset($chartDetail->crossesAt)) {
1376              $whichAxis->setAxisOption('horizontal_crosses_value', (string) self::getAttribute($chartDetail->crossesAt, 'val', 'string'));
1377          }
1378          if (isset($chartDetail->scaling->orientation)) {
1379              $whichAxis->setAxisOption('orientation', (string) self::getAttribute($chartDetail->scaling->orientation, 'val', 'string'));
1380          }
1381          if (isset($chartDetail->scaling->max)) {
1382              $whichAxis->setAxisOption('maximum', (string) self::getAttribute($chartDetail->scaling->max, 'val', 'string'));
1383          }
1384          if (isset($chartDetail->scaling->min)) {
1385              $whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string'));
1386          }
1387          if (isset($chartDetail->scaling->min)) {
1388              $whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string'));
1389          }
1390          if (isset($chartDetail->majorUnit)) {
1391              $whichAxis->setAxisOption('major_unit', (string) self::getAttribute($chartDetail->majorUnit, 'val', 'string'));
1392          }
1393          if (isset($chartDetail->minorUnit)) {
1394              $whichAxis->setAxisOption('minor_unit', (string) self::getAttribute($chartDetail->minorUnit, 'val', 'string'));
1395          }
1396          if (isset($chartDetail->baseTimeUnit)) {
1397              $whichAxis->setAxisOption('baseTimeUnit', (string) self::getAttribute($chartDetail->baseTimeUnit, 'val', 'string'));
1398          }
1399          if (isset($chartDetail->majorTimeUnit)) {
1400              $whichAxis->setAxisOption('majorTimeUnit', (string) self::getAttribute($chartDetail->majorTimeUnit, 'val', 'string'));
1401          }
1402          if (isset($chartDetail->minorTimeUnit)) {
1403              $whichAxis->setAxisOption('minorTimeUnit', (string) self::getAttribute($chartDetail->minorTimeUnit, 'val', 'string'));
1404          }
1405          if (isset($chartDetail->txPr)) {
1406              $children = $chartDetail->txPr->children($this->aNamespace);
1407              if (isset($children->bodyPr)) {
1408                  /** @var string */
1409                  $textRotation = self::getAttribute($children->bodyPr, 'rot', 'string');
1410                  if (is_numeric($textRotation)) {
1411                      $whichAxis->setAxisOption('textRotation', (string) ChartProperties::xmlToAngle($textRotation));
1412                  }
1413              }
1414          }
1415      }
1416  }