updated a couple packages (#1567)
[openemr.git] / vendor / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Reader / Xlsx / Chart.php
blob641d6d1ca2d49861174088ed5edcb231024d7c2b
1 <?php
3 namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
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;
17 class Chart
19 /**
20 * @param SimpleXMLElement $component
21 * @param string $name
22 * @param string $format
24 * @return null|bool|float|int|string
26 private static function getAttribute(SimpleXMLElement $component, $name, $format)
28 $attributes = $component->attributes();
29 if (isset($attributes[$name])) {
30 if ($format == 'string') {
31 return (string) $attributes[$name];
32 } elseif ($format == 'integer') {
33 return (int) $attributes[$name];
34 } elseif ($format == 'boolean') {
35 return (bool) ($attributes[$name] === '0' || $attributes[$name] !== 'true') ? false : true;
38 return (float) $attributes[$name];
41 return null;
44 private static function readColor($color, $background = false)
46 if (isset($color['rgb'])) {
47 return (string) $color['rgb'];
48 } elseif (isset($color['indexed'])) {
49 return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
53 /**
54 * @param SimpleXMLElement $chartElements
55 * @param string $chartName
57 * @return \PhpOffice\PhpSpreadsheet\Chart\Chart
59 public static function readChart(SimpleXMLElement $chartElements, $chartName)
61 $namespacesChartMeta = $chartElements->getNamespaces(true);
62 $chartElementsC = $chartElements->children($namespacesChartMeta['c']);
64 $XaxisLabel = $YaxisLabel = $legend = $title = null;
65 $dispBlanksAs = $plotVisOnly = 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);
81 break;
82 case 'catAx':
83 if (isset($chartDetail->title)) {
84 $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta);
87 break;
88 case 'dateAx':
89 if (isset($chartDetail->title)) {
90 $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta);
93 break;
94 case 'valAx':
95 if (isset($chartDetail->title)) {
96 $YaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta);
99 break;
100 case 'barChart':
101 case 'bar3DChart':
102 $barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string');
103 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
104 $plotSer->setPlotDirection($barDirection);
105 $plotSeries[] = $plotSer;
106 $plotAttributes = self::readChartAttributes($chartDetail);
108 break;
109 case 'lineChart':
110 case 'line3DChart':
111 $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
112 $plotAttributes = self::readChartAttributes($chartDetail);
114 break;
115 case 'areaChart':
116 case 'area3DChart':
117 $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
118 $plotAttributes = self::readChartAttributes($chartDetail);
120 break;
121 case 'doughnutChart':
122 case 'pieChart':
123 case 'pie3DChart':
124 $explosion = isset($chartDetail->ser->explosion);
125 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
126 $plotSer->setPlotStyle($explosion);
127 $plotSeries[] = $plotSer;
128 $plotAttributes = self::readChartAttributes($chartDetail);
130 break;
131 case 'scatterChart':
132 $scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string');
133 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
134 $plotSer->setPlotStyle($scatterStyle);
135 $plotSeries[] = $plotSer;
136 $plotAttributes = self::readChartAttributes($chartDetail);
138 break;
139 case 'bubbleChart':
140 $bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer');
141 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
142 $plotSer->setPlotStyle($bubbleScale);
143 $plotSeries[] = $plotSer;
144 $plotAttributes = self::readChartAttributes($chartDetail);
146 break;
147 case 'radarChart':
148 $radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string');
149 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
150 $plotSer->setPlotStyle($radarStyle);
151 $plotSeries[] = $plotSer;
152 $plotAttributes = self::readChartAttributes($chartDetail);
154 break;
155 case 'surfaceChart':
156 case 'surface3DChart':
157 $wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean');
158 $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
159 $plotSer->setPlotStyle($wireFrame);
160 $plotSeries[] = $plotSer;
161 $plotAttributes = self::readChartAttributes($chartDetail);
163 break;
164 case 'stockChart':
165 $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
166 $plotAttributes = self::readChartAttributes($plotAreaLayout);
168 break;
171 if ($plotAreaLayout == null) {
172 $plotAreaLayout = new Layout();
174 $plotArea = new PlotArea($plotAreaLayout, $plotSeries);
175 self::setChartAttributes($plotAreaLayout, $plotAttributes);
177 break;
178 case 'plotVisOnly':
179 $plotVisOnly = self::getAttribute($chartDetails, 'val', 'string');
181 break;
182 case 'dispBlanksAs':
183 $dispBlanksAs = self::getAttribute($chartDetails, 'val', 'string');
185 break;
186 case 'title':
187 $title = self::chartTitle($chartDetails, $namespacesChartMeta);
189 break;
190 case 'legend':
191 $legendPos = 'r';
192 $legendLayout = null;
193 $legendOverlay = false;
194 foreach ($chartDetails as $chartDetailKey => $chartDetail) {
195 switch ($chartDetailKey) {
196 case 'legendPos':
197 $legendPos = self::getAttribute($chartDetail, 'val', 'string');
199 break;
200 case 'overlay':
201 $legendOverlay = self::getAttribute($chartDetail, 'val', 'boolean');
203 break;
204 case 'layout':
205 $legendLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta);
207 break;
210 $legend = new Legend($legendPos, $legendLayout, $legendOverlay);
212 break;
217 $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, $dispBlanksAs, $XaxisLabel, $YaxisLabel);
219 return $chart;
222 private static function chartTitle(SimpleXMLElement $titleDetails, array $namespacesChartMeta)
224 $caption = [];
225 $titleLayout = null;
226 foreach ($titleDetails as $titleDetailKey => $chartDetail) {
227 switch ($titleDetailKey) {
228 case 'tx':
229 $titleDetails = $chartDetail->rich->children($namespacesChartMeta['a']);
230 foreach ($titleDetails as $titleKey => $titleDetail) {
231 switch ($titleKey) {
232 case 'p':
233 $titleDetailPart = $titleDetail->children($namespacesChartMeta['a']);
234 $caption[] = self::parseRichText($titleDetailPart);
238 break;
239 case 'layout':
240 $titleLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta);
242 break;
246 return new Title($caption, $titleLayout);
249 private static function chartLayoutDetails($chartDetail, $namespacesChartMeta)
251 if (!isset($chartDetail->manualLayout)) {
252 return null;
254 $details = $chartDetail->manualLayout->children($namespacesChartMeta['c']);
255 if ($details === null) {
256 return null;
258 $layout = [];
259 foreach ($details as $detailKey => $detail) {
260 $layout[$detailKey] = self::getAttribute($detail, 'val', 'string');
263 return new Layout($layout);
266 private static function chartDataSeries($chartDetail, $namespacesChartMeta, $plotType)
268 $multiSeriesType = null;
269 $smoothLine = false;
270 $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = [];
272 $seriesDetailSet = $chartDetail->children($namespacesChartMeta['c']);
273 foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
274 switch ($seriesDetailKey) {
275 case 'grouping':
276 $multiSeriesType = self::getAttribute($chartDetail->grouping, 'val', 'string');
278 break;
279 case 'ser':
280 $marker = null;
281 foreach ($seriesDetails as $seriesKey => $seriesDetail) {
282 switch ($seriesKey) {
283 case 'idx':
284 $seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer');
286 break;
287 case 'order':
288 $seriesOrder = self::getAttribute($seriesDetail, 'val', 'integer');
289 $plotOrder[$seriesIndex] = $seriesOrder;
291 break;
292 case 'tx':
293 $seriesLabel[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta);
295 break;
296 case 'marker':
297 $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string');
299 break;
300 case 'smooth':
301 $smoothLine = self::getAttribute($seriesDetail, 'val', 'boolean');
303 break;
304 case 'cat':
305 $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta);
307 break;
308 case 'val':
309 $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker);
311 break;
312 case 'xVal':
313 $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker);
315 break;
316 case 'yVal':
317 $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker);
319 break;
325 return new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine);
328 private static function chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker = null)
330 if (isset($seriesDetail->strRef)) {
331 $seriesSource = (string) $seriesDetail->strRef->f;
332 $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's');
334 return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
335 } elseif (isset($seriesDetail->numRef)) {
336 $seriesSource = (string) $seriesDetail->numRef->f;
337 $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c']));
339 return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
340 } elseif (isset($seriesDetail->multiLvlStrRef)) {
341 $seriesSource = (string) $seriesDetail->multiLvlStrRef->f;
342 $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's');
343 $seriesData['pointCount'] = count($seriesData['dataValues']);
345 return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
346 } elseif (isset($seriesDetail->multiLvlNumRef)) {
347 $seriesSource = (string) $seriesDetail->multiLvlNumRef->f;
348 $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's');
349 $seriesData['pointCount'] = count($seriesData['dataValues']);
351 return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
354 return null;
357 private static function chartDataSeriesValues($seriesValueSet, $dataType = 'n')
359 $seriesVal = [];
360 $formatCode = '';
361 $pointCount = 0;
363 foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) {
364 switch ($seriesValueIdx) {
365 case 'ptCount':
366 $pointCount = self::getAttribute($seriesValue, 'val', 'integer');
368 break;
369 case 'formatCode':
370 $formatCode = (string) $seriesValue;
372 break;
373 case 'pt':
374 $pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
375 if ($dataType == 's') {
376 $seriesVal[$pointVal] = (string) $seriesValue->v;
377 } elseif ($seriesValue->v === Functions::NA()) {
378 $seriesVal[$pointVal] = null;
379 } else {
380 $seriesVal[$pointVal] = (float) $seriesValue->v;
383 break;
387 return [
388 'formatCode' => $formatCode,
389 'pointCount' => $pointCount,
390 'dataValues' => $seriesVal,
394 private static function chartDataSeriesValuesMultiLevel($seriesValueSet, $dataType = 'n')
396 $seriesVal = [];
397 $formatCode = '';
398 $pointCount = 0;
400 foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) {
401 foreach ($seriesLevel as $seriesValueIdx => $seriesValue) {
402 switch ($seriesValueIdx) {
403 case 'ptCount':
404 $pointCount = self::getAttribute($seriesValue, 'val', 'integer');
406 break;
407 case 'formatCode':
408 $formatCode = (string) $seriesValue;
410 break;
411 case 'pt':
412 $pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
413 if ($dataType == 's') {
414 $seriesVal[$pointVal][] = (string) $seriesValue->v;
415 } elseif ($seriesValue->v === Functions::NA()) {
416 $seriesVal[$pointVal] = null;
417 } else {
418 $seriesVal[$pointVal][] = (float) $seriesValue->v;
421 break;
426 return [
427 'formatCode' => $formatCode,
428 'pointCount' => $pointCount,
429 'dataValues' => $seriesVal,
433 private static function parseRichText(SimpleXMLElement $titleDetailPart)
435 $value = new RichText();
437 foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
438 if (isset($titleDetailElement->t)) {
439 $objText = $value->createTextRun((string) $titleDetailElement->t);
441 if (isset($titleDetailElement->rPr)) {
442 if (isset($titleDetailElement->rPr->rFont['val'])) {
443 $objText->getFont()->setName((string) $titleDetailElement->rPr->rFont['val']);
446 $fontSize = (self::getAttribute($titleDetailElement->rPr, 'sz', 'integer'));
447 if ($fontSize !== null) {
448 $objText->getFont()->setSize(floor($fontSize / 100));
451 $fontColor = (self::getAttribute($titleDetailElement->rPr, 'color', 'string'));
452 if ($fontColor !== null) {
453 $objText->getFont()->setColor(new Color(self::readColor($fontColor)));
456 $bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean');
457 if ($bold !== null) {
458 $objText->getFont()->setBold($bold);
461 $italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean');
462 if ($italic !== null) {
463 $objText->getFont()->setItalic($italic);
466 $baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer');
467 if ($baseline !== null) {
468 if ($baseline > 0) {
469 $objText->getFont()->setSuperscript(true);
470 } elseif ($baseline < 0) {
471 $objText->getFont()->setSubscript(true);
475 $underscore = (self::getAttribute($titleDetailElement->rPr, 'u', 'string'));
476 if ($underscore !== null) {
477 if ($underscore == 'sng') {
478 $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
479 } elseif ($underscore == 'dbl') {
480 $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
481 } else {
482 $objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
486 $strikethrough = (self::getAttribute($titleDetailElement->rPr, 's', 'string'));
487 if ($strikethrough !== null) {
488 if ($strikethrough == 'noStrike') {
489 $objText->getFont()->setStrikethrough(false);
490 } else {
491 $objText->getFont()->setStrikethrough(true);
497 return $value;
500 private static function readChartAttributes($chartDetail)
502 $plotAttributes = [];
503 if (isset($chartDetail->dLbls)) {
504 if (isset($chartDetail->dLbls->howLegendKey)) {
505 $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string');
507 if (isset($chartDetail->dLbls->showVal)) {
508 $plotAttributes['showVal'] = self::getAttribute($chartDetail->dLbls->showVal, 'val', 'string');
510 if (isset($chartDetail->dLbls->showCatName)) {
511 $plotAttributes['showCatName'] = self::getAttribute($chartDetail->dLbls->showCatName, 'val', 'string');
513 if (isset($chartDetail->dLbls->showSerName)) {
514 $plotAttributes['showSerName'] = self::getAttribute($chartDetail->dLbls->showSerName, 'val', 'string');
516 if (isset($chartDetail->dLbls->showPercent)) {
517 $plotAttributes['showPercent'] = self::getAttribute($chartDetail->dLbls->showPercent, 'val', 'string');
519 if (isset($chartDetail->dLbls->showBubbleSize)) {
520 $plotAttributes['showBubbleSize'] = self::getAttribute($chartDetail->dLbls->showBubbleSize, 'val', 'string');
522 if (isset($chartDetail->dLbls->showLeaderLines)) {
523 $plotAttributes['showLeaderLines'] = self::getAttribute($chartDetail->dLbls->showLeaderLines, 'val', 'string');
527 return $plotAttributes;
531 * @param Layout $plotArea
532 * @param mixed $plotAttributes
534 private static function setChartAttributes(Layout $plotArea, $plotAttributes)
536 foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) {
537 switch ($plotAttributeKey) {
538 case 'showLegendKey':
539 $plotArea->setShowLegendKey($plotAttributeValue);
541 break;
542 case 'showVal':
543 $plotArea->setShowVal($plotAttributeValue);
545 break;
546 case 'showCatName':
547 $plotArea->setShowCatName($plotAttributeValue);
549 break;
550 case 'showSerName':
551 $plotArea->setShowSerName($plotAttributeValue);
553 break;
554 case 'showPercent':
555 $plotArea->setShowPercent($plotAttributeValue);
557 break;
558 case 'showBubbleSize':
559 $plotArea->setShowBubbleSize($plotAttributeValue);
561 break;
562 case 'showLeaderLines':
563 $plotArea->setShowLeaderLines($plotAttributeValue);
565 break;