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