Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; 4 5 use PhpOffice\PhpSpreadsheet\Calculation\Functions; 6 use PhpOffice\PhpSpreadsheet\Chart\DataSeries; 7 use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; 8 use PhpOffice\PhpSpreadsheet\Chart\Layout; 9 use PhpOffice\PhpSpreadsheet\Chart\Legend; 10 use PhpOffice\PhpSpreadsheet\Chart\PlotArea; 11 use PhpOffice\PhpSpreadsheet\Chart\Title; 12 use PhpOffice\PhpSpreadsheet\RichText\RichText; 13 use PhpOffice\PhpSpreadsheet\Style\Color; 14 use PhpOffice\PhpSpreadsheet\Style\Font; 15 use SimpleXMLElement; 16 17 class Chart 18 { 19 /** 20 * @param string $name 21 * @param string $format 22 * 23 * @return null|bool|float|int|string 24 */ 25 private static function getAttribute(SimpleXMLElement $component, $name, $format) 26 { 27 $attributes = $component->attributes(); 28 if (isset($attributes[$name])) { 29 if ($format == 'string') { 30 return (string) $attributes[$name]; 31 } elseif ($format == 'integer') { 32 return (int) $attributes[$name]; 33 } elseif ($format == 'boolean') { 34 $value = (string) $attributes[$name]; 35 36 return $value === 'true' || $value === '1'; 37 } 38 39 return (float) $attributes[$name]; 40 } 41 42 return null; 43 } 44 45 private static function readColor($color, $background = false) 46 { 47 if (isset($color['rgb'])) { 48 return (string) $color['rgb']; 49 } elseif (isset($color['indexed'])) { 50 return Color::indexedColor($color['indexed'] - 7, $background)->getARGB(); 51 } 52 } 53 54 /** 55 * @param string $chartName 56 * 57 * @return \PhpOffice\PhpSpreadsheet\Chart\Chart 58 */ 59 public static function readChart(SimpleXMLElement $chartElements, $chartName) 60 { 61 $namespacesChartMeta = $chartElements->getNamespaces(true); 62 $chartElementsC = $chartElements->children($namespacesChartMeta['c']); 63 64 $XaxisLabel = $YaxisLabel = $legend = $title = null; 65 $dispBlanksAs = $plotVisOnly = null; 66 $plotArea = null; 67 foreach ($chartElementsC as $chartElementKey => $chartElement) { 68 switch ($chartElementKey) { 69 case 'chart': 70 foreach ($chartElement as $chartDetailsKey => $chartDetails) { 71 $chartDetailsC = $chartDetails->children($namespacesChartMeta['c']); 72 switch ($chartDetailsKey) { 73 case 'plotArea': 74 $plotAreaLayout = $XaxisLable = $YaxisLable = null; 75 $plotSeries = $plotAttributes = []; 76 foreach ($chartDetails as $chartDetailKey => $chartDetail) { 77 switch ($chartDetailKey) { 78 case 'layout': 79 $plotAreaLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta); 80 81 break; 82 case 'catAx': 83 if (isset($chartDetail->title)) { 84 $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); 85 } 86 87 break; 88 case 'dateAx': 89 if (isset($chartDetail->title)) { 90 $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); 91 } 92 93 break; 94 case 'valAx': 95 if (isset($chartDetail->title, $chartDetail->axPos)) { 96 $axisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); 97 $axPos = self::getAttribute($chartDetail->axPos, 'val', 'string'); 98 99 switch ($axPos) { 100 case 't': 101 case 'b': 102 $XaxisLabel = $axisLabel; 103 104 break; 105 case 'r': 106 case 'l': 107 $YaxisLabel = $axisLabel; 108 109 break; 110 } 111 } 112 113 break; 114 case 'barChart': 115 case 'bar3DChart': 116 $barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string'); 117 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); 118 $plotSer->setPlotDirection($barDirection); 119 $plotSeries[] = $plotSer; 120 $plotAttributes = self::readChartAttributes($chartDetail); 121 122 break; 123 case 'lineChart': 124 case 'line3DChart': 125 $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); 126 $plotAttributes = self::readChartAttributes($chartDetail); 127 128 break; 129 case 'areaChart': 130 case 'area3DChart': 131 $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); 132 $plotAttributes = self::readChartAttributes($chartDetail); 133 134 break; 135 case 'doughnutChart': 136 case 'pieChart': 137 case 'pie3DChart': 138 $explosion = isset($chartDetail->ser->explosion); 139 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); 140 $plotSer->setPlotStyle($explosion); 141 $plotSeries[] = $plotSer; 142 $plotAttributes = self::readChartAttributes($chartDetail); 143 144 break; 145 case 'scatterChart': 146 $scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string'); 147 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); 148 $plotSer->setPlotStyle($scatterStyle); 149 $plotSeries[] = $plotSer; 150 $plotAttributes = self::readChartAttributes($chartDetail); 151 152 break; 153 case 'bubbleChart': 154 $bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer'); 155 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); 156 $plotSer->setPlotStyle($bubbleScale); 157 $plotSeries[] = $plotSer; 158 $plotAttributes = self::readChartAttributes($chartDetail); 159 160 break; 161 case 'radarChart': 162 $radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string'); 163 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); 164 $plotSer->setPlotStyle($radarStyle); 165 $plotSeries[] = $plotSer; 166 $plotAttributes = self::readChartAttributes($chartDetail); 167 168 break; 169 case 'surfaceChart': 170 case 'surface3DChart': 171 $wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean'); 172 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); 173 $plotSer->setPlotStyle($wireFrame); 174 $plotSeries[] = $plotSer; 175 $plotAttributes = self::readChartAttributes($chartDetail); 176 177 break; 178 case 'stockChart': 179 $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); 180 $plotAttributes = self::readChartAttributes($plotAreaLayout); 181 182 break; 183 } 184 } 185 if ($plotAreaLayout == null) { 186 $plotAreaLayout = new Layout(); 187 } 188 $plotArea = new PlotArea($plotAreaLayout, $plotSeries); 189 self::setChartAttributes($plotAreaLayout, $plotAttributes); 190 191 break; 192 case 'plotVisOnly': 193 $plotVisOnly = self::getAttribute($chartDetails, 'val', 'string'); 194 195 break; 196 case 'dispBlanksAs': 197 $dispBlanksAs = self::getAttribute($chartDetails, 'val', 'string'); 198 199 break; 200 case 'title': 201 $title = self::chartTitle($chartDetails, $namespacesChartMeta); 202 203 break; 204 case 'legend': 205 $legendPos = 'r'; 206 $legendLayout = null; 207 $legendOverlay = false; 208 foreach ($chartDetails as $chartDetailKey => $chartDetail) { 209 switch ($chartDetailKey) { 210 case 'legendPos': 211 $legendPos = self::getAttribute($chartDetail, 'val', 'string'); 212 213 break; 214 case 'overlay': 215 $legendOverlay = self::getAttribute($chartDetail, 'val', 'boolean'); 216 217 break; 218 case 'layout': 219 $legendLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta); 220 221 break; 222 } 223 } 224 $legend = new Legend($legendPos, $legendLayout, $legendOverlay); 225 226 break; 227 } 228 } 229 } 230 } 231 $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, $dispBlanksAs, $XaxisLabel, $YaxisLabel); 232 233 return $chart; 234 } 235 236 private static function chartTitle(SimpleXMLElement $titleDetails, array $namespacesChartMeta) 237 { 238 $caption = []; 239 $titleLayout = null; 240 foreach ($titleDetails as $titleDetailKey => $chartDetail) { 241 switch ($titleDetailKey) { 242 case 'tx': 243 $titleDetails = $chartDetail->rich->children($namespacesChartMeta['a']); 244 foreach ($titleDetails as $titleKey => $titleDetail) { 245 switch ($titleKey) { 246 case 'p': 247 $titleDetailPart = $titleDetail->children($namespacesChartMeta['a']); 248 $caption[] = self::parseRichText($titleDetailPart); 249 } 250 } 251 252 break; 253 case 'layout': 254 $titleLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta); 255 256 break; 257 } 258 } 259 260 return new Title($caption, $titleLayout); 261 } 262 263 private static function chartLayoutDetails($chartDetail, $namespacesChartMeta) 264 { 265 if (!isset($chartDetail->manualLayout)) { 266 return null; 267 } 268 $details = $chartDetail->manualLayout->children($namespacesChartMeta['c']); 269 if ($details === null) { 270 return null; 271 } 272 $layout = []; 273 foreach ($details as $detailKey => $detail) { 274 $layout[$detailKey] = self::getAttribute($detail, 'val', 'string'); 275 } 276 277 return new Layout($layout); 278 } 279 280 private static function chartDataSeries($chartDetail, $namespacesChartMeta, $plotType) 281 { 282 $multiSeriesType = null; 283 $smoothLine = false; 284 $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = []; 285 286 $seriesDetailSet = $chartDetail->children($namespacesChartMeta['c']); 287 foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) { 288 switch ($seriesDetailKey) { 289 case 'grouping': 290 $multiSeriesType = self::getAttribute($chartDetail->grouping, 'val', 'string'); 291 292 break; 293 case 'ser': 294 $marker = null; 295 $seriesIndex = ''; 296 foreach ($seriesDetails as $seriesKey => $seriesDetail) { 297 switch ($seriesKey) { 298 case 'idx': 299 $seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer'); 300 301 break; 302 case 'order': 303 $seriesOrder = self::getAttribute($seriesDetail, 'val', 'integer'); 304 $plotOrder[$seriesIndex] = $seriesOrder; 305 306 break; 307 case 'tx': 308 $seriesLabel[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta); 309 310 break; 311 case 'marker': 312 $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string'); 313 314 break; 315 case 'smooth': 316 $smoothLine = self::getAttribute($seriesDetail, 'val', 'boolean'); 317 318 break; 319 case 'cat': 320 $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta); 321 322 break; 323 case 'val': 324 $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker); 325 326 break; 327 case 'xVal': 328 $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker); 329 330 break; 331 case 'yVal': 332 $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker); 333 334 break; 335 } 336 } 337 } 338 } 339 340 return new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine); 341 } 342 343 private static function chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker = null) 344 { 345 if (isset($seriesDetail->strRef)) { 346 $seriesSource = (string) $seriesDetail->strRef->f; 347 $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); 348 349 if (isset($seriesDetail->strRef->strCache)) { 350 $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's'); 351 $seriesValues 352 ->setFormatCode($seriesData['formatCode']) 353 ->setDataValues($seriesData['dataValues']); 354 } 355 356 return $seriesValues; 357 } elseif (isset($seriesDetail->numRef)) { 358 $seriesSource = (string) $seriesDetail->numRef->f; 359 $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, null, null, $marker); 360 if (isset($seriesDetail->numRef->numCache)) { 361 $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c'])); 362 $seriesValues 363 ->setFormatCode($seriesData['formatCode']) 364 ->setDataValues($seriesData['dataValues']); 365 } 366 367 return $seriesValues; 368 } elseif (isset($seriesDetail->multiLvlStrRef)) { 369 $seriesSource = (string) $seriesDetail->multiLvlStrRef->f; 370 $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); 371 372 if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) { 373 $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's'); 374 $seriesValues 375 ->setFormatCode($seriesData['formatCode']) 376 ->setDataValues($seriesData['dataValues']); 377 } 378 379 return $seriesValues; 380 } elseif (isset($seriesDetail->multiLvlNumRef)) { 381 $seriesSource = (string) $seriesDetail->multiLvlNumRef->f; 382 $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); 383 384 if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) { 385 $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's'); 386 $seriesValues 387 ->setFormatCode($seriesData['formatCode']) 388 ->setDataValues($seriesData['dataValues']); 389 } 390 391 return $seriesValues; 392 } 393 394 return null; 395 } 396 397 private static function chartDataSeriesValues($seriesValueSet, $dataType = 'n') 398 { 399 $seriesVal = []; 400 $formatCode = ''; 401 $pointCount = 0; 402 403 foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) { 404 switch ($seriesValueIdx) { 405 case 'ptCount': 406 $pointCount = self::getAttribute($seriesValue, 'val', 'integer'); 407 408 break; 409 case 'formatCode': 410 $formatCode = (string) $seriesValue; 411 412 break; 413 case 'pt': 414 $pointVal = self::getAttribute($seriesValue, 'idx', 'integer'); 415 if ($dataType == 's') { 416 $seriesVal[$pointVal] = (string) $seriesValue->v; 417 } elseif ($seriesValue->v === Functions::NA()) { 418 $seriesVal[$pointVal] = null; 419 } else { 420 $seriesVal[$pointVal] = (float) $seriesValue->v; 421 } 422 423 break; 424 } 425 } 426 427 return [ 428 'formatCode' => $formatCode, 429 'pointCount' => $pointCount, 430 'dataValues' => $seriesVal, 431 ]; 432 } 433 434 private static function chartDataSeriesValuesMultiLevel($seriesValueSet, $dataType = 'n') 435 { 436 $seriesVal = []; 437 $formatCode = ''; 438 $pointCount = 0; 439 440 foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) { 441 foreach ($seriesLevel as $seriesValueIdx => $seriesValue) { 442 switch ($seriesValueIdx) { 443 case 'ptCount': 444 $pointCount = self::getAttribute($seriesValue, 'val', 'integer'); 445 446 break; 447 case 'formatCode': 448 $formatCode = (string) $seriesValue; 449 450 break; 451 case 'pt': 452 $pointVal = self::getAttribute($seriesValue, 'idx', 'integer'); 453 if ($dataType == 's') { 454 $seriesVal[$pointVal][] = (string) $seriesValue->v; 455 } elseif ($seriesValue->v === Functions::NA()) { 456 $seriesVal[$pointVal] = null; 457 } else { 458 $seriesVal[$pointVal][] = (float) $seriesValue->v; 459 } 460 461 break; 462 } 463 } 464 } 465 466 return [ 467 'formatCode' => $formatCode, 468 'pointCount' => $pointCount, 469 'dataValues' => $seriesVal, 470 ]; 471 } 472 473 private static function parseRichText(SimpleXMLElement $titleDetailPart) 474 { 475 $value = new RichText(); 476 $objText = null; 477 foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) { 478 if (isset($titleDetailElement->t)) { 479 $objText = $value->createTextRun((string) $titleDetailElement->t); 480 } 481 if (isset($titleDetailElement->rPr)) { 482 if (isset($titleDetailElement->rPr->rFont['val'])) { 483 $objText->getFont()->setName((string) $titleDetailElement->rPr->rFont['val']); 484 } 485 486 $fontSize = (self::getAttribute($titleDetailElement->rPr, 'sz', 'integer')); 487 if (is_int($fontSize)) { 488 $objText->getFont()->setSize(floor($fontSize / 100)); 489 } 490 491 $fontColor = (self::getAttribute($titleDetailElement->rPr, 'color', 'string')); 492 if ($fontColor !== null) { 493 $objText->getFont()->setColor(new Color(self::readColor($fontColor))); 494 } 495 496 $bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean'); 497 if ($bold !== null) { 498 $objText->getFont()->setBold($bold); 499 } 500 501 $italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean'); 502 if ($italic !== null) { 503 $objText->getFont()->setItalic($italic); 504 } 505 506 $baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer'); 507 if ($baseline !== null) { 508 if ($baseline > 0) { 509 $objText->getFont()->setSuperscript(true); 510 } elseif ($baseline < 0) { 511 $objText->getFont()->setSubscript(true); 512 } 513 } 514 515 $underscore = (self::getAttribute($titleDetailElement->rPr, 'u', 'string')); 516 if ($underscore !== null) { 517 if ($underscore == 'sng') { 518 $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE); 519 } elseif ($underscore == 'dbl') { 520 $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE); 521 } else { 522 $objText->getFont()->setUnderline(Font::UNDERLINE_NONE); 523 } 524 } 525 526 $strikethrough = (self::getAttribute($titleDetailElement->rPr, 's', 'string')); 527 if ($strikethrough !== null) { 528 if ($strikethrough == 'noStrike') { 529 $objText->getFont()->setStrikethrough(false); 530 } else { 531 $objText->getFont()->setStrikethrough(true); 532 } 533 } 534 } 535 } 536 537 return $value; 538 } 539 540 private static function readChartAttributes($chartDetail) 541 { 542 $plotAttributes = []; 543 if (isset($chartDetail->dLbls)) { 544 if (isset($chartDetail->dLbls->showLegendKey)) { 545 $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string'); 546 } 547 if (isset($chartDetail->dLbls->showVal)) { 548 $plotAttributes['showVal'] = self::getAttribute($chartDetail->dLbls->showVal, 'val', 'string'); 549 } 550 if (isset($chartDetail->dLbls->showCatName)) { 551 $plotAttributes['showCatName'] = self::getAttribute($chartDetail->dLbls->showCatName, 'val', 'string'); 552 } 553 if (isset($chartDetail->dLbls->showSerName)) { 554 $plotAttributes['showSerName'] = self::getAttribute($chartDetail->dLbls->showSerName, 'val', 'string'); 555 } 556 if (isset($chartDetail->dLbls->showPercent)) { 557 $plotAttributes['showPercent'] = self::getAttribute($chartDetail->dLbls->showPercent, 'val', 'string'); 558 } 559 if (isset($chartDetail->dLbls->showBubbleSize)) { 560 $plotAttributes['showBubbleSize'] = self::getAttribute($chartDetail->dLbls->showBubbleSize, 'val', 'string'); 561 } 562 if (isset($chartDetail->dLbls->showLeaderLines)) { 563 $plotAttributes['showLeaderLines'] = self::getAttribute($chartDetail->dLbls->showLeaderLines, 'val', 'string'); 564 } 565 } 566 567 return $plotAttributes; 568 } 569 570 /** 571 * @param mixed $plotAttributes 572 */ 573 private static function setChartAttributes(Layout $plotArea, $plotAttributes): void 574 { 575 foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) { 576 switch ($plotAttributeKey) { 577 case 'showLegendKey': 578 $plotArea->setShowLegendKey($plotAttributeValue); 579 580 break; 581 case 'showVal': 582 $plotArea->setShowVal($plotAttributeValue); 583 584 break; 585 case 'showCatName': 586 $plotArea->setShowCatName($plotAttributeValue); 587 588 break; 589 case 'showSerName': 590 $plotArea->setShowSerName($plotAttributeValue); 591 592 break; 593 case 'showPercent': 594 $plotArea->setShowPercent($plotAttributeValue); 595 596 break; 597 case 'showBubbleSize': 598 $plotArea->setShowBubbleSize($plotAttributeValue); 599 600 break; 601 case 'showLeaderLines': 602 $plotArea->setShowLeaderLines($plotAttributeValue); 603 604 break; 605 } 606 } 607 } 608 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body