Skip to content

Commit 5608e05

Browse files
authored
Add Support to Chart/Axis for Glow/SoftEdges (#2865)
* Add Support to Chart/Axis for Glow/SoftEdges Chart is very under-covered in unit tests. I was hoping to just add some tests and be done with it, but that just won't suffice - many code changes are required. Adding Glow properties for Axis turned out to be a real mixed bag - no support in Xlsx/Reader at all, support in Xlsx/Writer ... for the X-axis only. So we'll just inch forward in many stages. This change does not address other Axis properties (Fill, Shadow, Line, LineStyle, and maybe others, and their sub-properties). On my to-do list. GridLines, and maybe other Chart objects, also should support these properties. This change does not address those. On my to-do list. No support is added for spreadsheet formats other than Xlsx. * Refactoring This should make the code easier to follow, and I hope it will also be extensible to other object types (e.g. GridLines). * More Refactoring Make scheme/srgb easier to handle. * Fix Typo In a very rarely used function.
1 parent d0ff3d9 commit 5608e05

File tree

8 files changed

+437
-75
lines changed

8 files changed

+437
-75
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,31 +1110,6 @@ parameters:
11101110
count: 1
11111111
path: src/PhpSpreadsheet/Cell/Coordinate.php
11121112

1113-
-
1114-
message: "#^Parameter \\#1 \\$textValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:substring\\(\\) expects string, string\\|null given\\.$#"
1115-
count: 1
1116-
path: src/PhpSpreadsheet/Cell/DataType.php
1117-
1118-
-
1119-
message: "#^Parameter \\#1 \\$angle of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowAngle\\(\\) expects int, int\\|null given\\.$#"
1120-
count: 1
1121-
path: src/PhpSpreadsheet/Chart/Axis.php
1122-
1123-
-
1124-
message: "#^Parameter \\#1 \\$blur of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowBlur\\(\\) expects float, float\\|null given\\.$#"
1125-
count: 1
1126-
path: src/PhpSpreadsheet/Chart/Axis.php
1127-
1128-
-
1129-
message: "#^Parameter \\#1 \\$distance of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowDistance\\(\\) expects float, float\\|null given\\.$#"
1130-
count: 1
1131-
path: src/PhpSpreadsheet/Chart/Axis.php
1132-
1133-
-
1134-
message: "#^Parameter \\#2 \\$alpha of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowColor\\(\\) expects int, int\\|string given\\.$#"
1135-
count: 1
1136-
path: src/PhpSpreadsheet/Chart/Axis.php
1137-
11381113
-
11391114
message: "#^Call to an undefined method object\\:\\:render\\(\\)\\.$#"
11401115
count: 1

src/PhpSpreadsheet/Chart/Axis.php

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -346,17 +346,17 @@ public function getLineStyleArrowLength($arrow)
346346
* @param int $shadowPresets
347347
* @param string $colorValue
348348
* @param string $colorType
349-
* @param string $colorAlpha
350-
* @param float $blur
351-
* @param int $angle
352-
* @param float $distance
349+
* @param null|int|string $colorAlpha
350+
* @param null|float $blur
351+
* @param null|int $angle
352+
* @param null|float $distance
353353
*/
354354
public function setShadowProperties($shadowPresets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $distance = null): void
355355
{
356356
$this->setShadowPresetsProperties((int) $shadowPresets)
357357
->setShadowColor(
358358
$colorValue ?? $this->shadowProperties['color']['value'],
359-
$colorAlpha ?? (int) $this->shadowProperties['color']['alpha'],
359+
(int) ($colorAlpha ?? $this->shadowProperties['color']['alpha']),
360360
$colorType ?? $this->shadowProperties['color']['type']
361361
)
362362
->setShadowBlur($blur)
@@ -412,9 +412,9 @@ private function setShadowPropertiesMapValues(array $propertiesMap, &$reference
412412
/**
413413
* Set Shadow Color.
414414
*
415-
* @param string $color
416-
* @param int $alpha
417-
* @param string $alphaType
415+
* @param null|string $color
416+
* @param null|int $alpha
417+
* @param null|string $alphaType
418418
*
419419
* @return $this
420420
*/
@@ -428,7 +428,7 @@ private function setShadowColor($color, $alpha, $alphaType)
428428
/**
429429
* Set Shadow Blur.
430430
*
431-
* @param float $blur
431+
* @param null|float $blur
432432
*
433433
* @return $this
434434
*/
@@ -444,7 +444,7 @@ private function setShadowBlur($blur)
444444
/**
445445
* Set Shadow Angle.
446446
*
447-
* @param int $angle
447+
* @param null|int $angle
448448
*
449449
* @return $this
450450
*/
@@ -460,7 +460,7 @@ private function setShadowAngle($angle)
460460
/**
461461
* Set Shadow Distance.
462462
*
463-
* @param float $distance
463+
* @param null|float $distance
464464
*
465465
* @return $this
466466
*/
@@ -489,9 +489,9 @@ public function getShadowProperty($elements)
489489
* Set Glow Properties.
490490
*
491491
* @param float $size
492-
* @param string $colorValue
493-
* @param int $colorAlpha
494-
* @param string $colorType
492+
* @param null|string $colorValue
493+
* @param null|int $colorAlpha
494+
* @param null|string $colorType
495495
*/
496496
public function setGlowProperties($size, $colorValue = null, $colorAlpha = null, $colorType = null): void
497497
{
@@ -508,7 +508,7 @@ public function setGlowProperties($size, $colorValue = null, $colorAlpha = null,
508508
*
509509
* @param array|string $property
510510
*
511-
* @return string
511+
* @return null|string
512512
*/
513513
public function getGlowProperty($property)
514514
{
@@ -555,7 +555,7 @@ private function setGlowColor($color, $alpha, $colorType)
555555
public function setSoftEdges($size): void
556556
{
557557
if ($size !== null) {
558-
$softEdges['size'] = (string) $this->getExcelPointsWidth($size);
558+
$this->softEdges['size'] = (string) $this->getExcelPointsWidth($size);
559559
}
560560
}
561561

src/PhpSpreadsheet/Chart/Properties.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ abstract class Properties
110110
const SHADOW_PRESETS_PERSPECTIVE_UPPER_LEFT = 21;
111111
const SHADOW_PRESETS_PERSPECTIVE_LOWER_RIGHT = 22;
112112
const SHADOW_PRESETS_PERSPECTIVE_LOWER_LEFT = 23;
113+
const POINTS_WIDTH_MULTIPLIER = 12700;
113114

114115
/**
115116
* @param float $width
@@ -118,7 +119,7 @@ abstract class Properties
118119
*/
119120
protected function getExcelPointsWidth($width)
120121
{
121-
return $width * 12700;
122+
return $width * self::POINTS_WIDTH_MULTIPLIER;
122123
}
123124

124125
/**
@@ -133,7 +134,7 @@ protected function getExcelPointsAngle($angle)
133134

134135
protected function getTrueAlpha($alpha)
135136
{
136-
return (string) 100 - $alpha . '000';
137+
return (string) (100 - $alpha) . '000';
137138
}
138139

139140
protected function setColorProperties($color, $alpha, $colorType)

src/PhpSpreadsheet/Reader/Xlsx/Chart.php

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
44

55
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
6+
use PhpOffice\PhpSpreadsheet\Chart\Axis;
67
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
78
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
89
use PhpOffice\PhpSpreadsheet\Chart\Layout;
910
use PhpOffice\PhpSpreadsheet\Chart\Legend;
1011
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
12+
use PhpOffice\PhpSpreadsheet\Chart\Properties;
1113
use PhpOffice\PhpSpreadsheet\Chart\Title;
1214
use PhpOffice\PhpSpreadsheet\RichText\RichText;
1315
use PhpOffice\PhpSpreadsheet\Style\Color;
@@ -67,6 +69,8 @@ public function readChart(SimpleXMLElement $chartElements, $chartName)
6769
$dispBlanksAs = $plotVisOnly = null;
6870
$plotArea = null;
6971
$rotX = $rotY = $rAngAx = $perspective = null;
72+
$xAxis = new Axis();
73+
$yAxis = new Axis();
7074
foreach ($chartElementsC as $chartElementKey => $chartElement) {
7175
switch ($chartElementKey) {
7276
case 'chart':
@@ -93,6 +97,7 @@ public function readChart(SimpleXMLElement $chartElements, $chartName)
9397
if (isset($chartDetail->title)) {
9498
$XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
9599
}
100+
$this->readEffects($chartDetail, $xAxis);
96101

97102
break;
98103
case 'dateAx':
@@ -102,6 +107,7 @@ public function readChart(SimpleXMLElement $chartElements, $chartName)
102107

103108
break;
104109
case 'valAx':
110+
$whichAxis = null;
105111
if (isset($chartDetail->title, $chartDetail->axPos)) {
106112
$axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
107113
$axPos = self::getAttribute($chartDetail->axPos, 'val', 'string');
@@ -110,15 +116,18 @@ public function readChart(SimpleXMLElement $chartElements, $chartName)
110116
case 't':
111117
case 'b':
112118
$XaxisLabel = $axisLabel;
119+
$whichAxis = $xAxis;
113120

114121
break;
115122
case 'r':
116123
case 'l':
117124
$YaxisLabel = $axisLabel;
125+
$whichAxis = $yAxis;
118126

119127
break;
120128
}
121129
}
130+
$this->readEffects($chartDetail, $whichAxis);
122131

123132
break;
124133
case 'barChart':
@@ -240,7 +249,7 @@ public function readChart(SimpleXMLElement $chartElements, $chartName)
240249
}
241250
}
242251
}
243-
$chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel);
252+
$chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis);
244253
if (is_int($rotX)) {
245254
$chart->setRotX($rotX);
246255
}
@@ -345,9 +354,8 @@ private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType
345354
if (is_countable($ln->noFill) && count($ln->noFill) === 1) {
346355
$noFill = true;
347356
}
348-
$sf = $children->solidFill->schemeClr;
349-
if ($sf) {
350-
$schemeClr = self::getAttribute($sf, 'val', 'string');
357+
if (isset($children->solidFill)) {
358+
$this->readColor($children->solidFill, $srgbClr, $schemeClr);
351359
}
352360

353361
break;
@@ -357,8 +365,8 @@ private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType
357365
$pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null;
358366
if (count($seriesDetail->spPr) === 1) {
359367
$ln = $seriesDetail->spPr->children($this->aNamespace);
360-
if (count($ln->solidFill) === 1) {
361-
$srgbClr = self::getAttribute($ln->solidFill->srgbClr, 'val', 'string');
368+
if (isset($ln->solidFill)) {
369+
$this->readColor($ln->solidFill, $srgbClr, $schemeClr);
362370
}
363371
}
364372

@@ -603,7 +611,8 @@ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
603611
$defaultLatin = null;
604612
$defaultEastAsian = null;
605613
$defaultComplexScript = null;
606-
$defaultColor = null;
614+
$defaultSrgbColor = '';
615+
$defaultSchemeColor = '';
607616
if (isset($titleDetailPart->pPr->defRPr)) {
608617
/** @var ?int */
609618
$defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
@@ -632,9 +641,8 @@ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
632641
/** @var ?string */
633642
$defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
634643
}
635-
if (isset($titleDetailPart->pPr->defRPr->solidFill->srgbClr)) {
636-
/** @var ?string */
637-
$defaultColor = self::getAttribute($titleDetailPart->pPr->defRPr->solidFill->srgbClr, 'val', 'string');
644+
if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
645+
$this->readColor($titleDetailPart->pPr->defRPr->solidFill, $defaultSrgbColor, $defaultSchemeClr);
638646
}
639647
}
640648
foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
@@ -660,7 +668,8 @@ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
660668
$latinName = null;
661669
$eastAsian = null;
662670
$complexScript = null;
663-
$fontColor = null;
671+
$fontSrgbClr = '';
672+
$fontSchemeClr = '';
664673
$uSchemeClr = null;
665674
if (isset($titleDetailElement->rPr)) {
666675
// not used now, not sure it ever was, grandfathering
@@ -686,10 +695,9 @@ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
686695

687696
// not used now, not sure it ever was, grandfathering
688697
/** @var ?string */
689-
$fontColor = self::getAttribute($titleDetailElement->rPr, 'color', 'string');
690-
if (isset($titleDetailElement->rPr->solidFill->srgbClr)) {
691-
/** @var ?string */
692-
$fontColor = self::getAttribute($titleDetailElement->rPr->solidFill->srgbClr, 'val', 'string');
698+
$fontSrgbClr = self::getAttribute($titleDetailElement->rPr, 'color', 'string');
699+
if (isset($titleDetailElement->rPr->solidFill)) {
700+
$this->readColor($titleDetailElement->rPr->solidFill, $fontSrgbClr, $fontSchemeClr);
693701
}
694702

695703
/** @var ?bool */
@@ -742,11 +750,17 @@ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
742750
$fontFound = true;
743751
}
744752

745-
$fontColor = $fontColor ?? $defaultColor;
746-
if ($fontColor !== null) {
747-
$objText->getFont()->setColor(new Color($fontColor));
753+
$fontSrgbClr = $fontSrgbClr ?? $defaultSrgbColor;
754+
if (!empty($fontSrgbClr)) {
755+
$objText->getFont()->setColor(new Color($fontSrgbClr));
748756
$fontFound = true;
749757
}
758+
// need to think about what to do here
759+
//$fontSchemeClr = $fontSchemeClr ?? $defaultSchemeColor;
760+
//if (!empty($fontSchemeClr)) {
761+
// $objText->getFont()->setColor(new Color($fontSrgbClr));
762+
// $fontFound = true;
763+
//}
750764

751765
$bold = $bold ?? $defaultBold;
752766
if ($bold !== null) {
@@ -877,4 +891,56 @@ private function setChartAttributes(Layout $plotArea, $plotAttributes): void
877891
}
878892
}
879893
}
894+
895+
/**
896+
* @param null|Axis $chartObject may be extended to include other types
897+
*/
898+
private function readEffects(SimpleXMLElement $chartDetail, $chartObject): void
899+
{
900+
if (!isset($chartObject, $chartDetail->spPr)) {
901+
return;
902+
}
903+
$sppr = $chartDetail->spPr->children($this->aNamespace);
904+
905+
if (isset($sppr->effectLst->glow)) {
906+
$axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / Properties::POINTS_WIDTH_MULTIPLIER;
907+
if ($axisGlowSize != 0.0) {
908+
$srgbClr = $schemeClr = '';
909+
$colorArray = $this->readColor($sppr->effectLst->glow, $srgbClr, $schemeClr);
910+
$chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']);
911+
}
912+
}
913+
914+
if (isset($sppr->effectLst->softEdge)) {
915+
$chartObject->setSoftEdges((float) self::getAttribute($sppr->effectLst->softEdge, 'rad', 'string') / Properties::POINTS_WIDTH_MULTIPLIER);
916+
}
917+
}
918+
919+
private function readColor(SimpleXMLElement $colorXml, ?string &$srgbClr, ?string &$schemeClr): array
920+
{
921+
$result = [
922+
'type' => null,
923+
'value' => null,
924+
'alpha' => null,
925+
];
926+
if (isset($colorXml->srgbClr)) {
927+
$result['type'] = Properties::EXCEL_COLOR_TYPE_ARGB;
928+
$result['value'] = $srgbClr = self::getAttribute($colorXml->srgbClr, 'val', 'string');
929+
if (isset($colorXml->srgbClr->alpha)) {
930+
$alpha = (int) self::getAttribute($colorXml->srgbClr->alpha, 'val', 'string');
931+
$alpha = 100 - (int) ($alpha / 1000);
932+
$result['alpha'] = $alpha;
933+
}
934+
} elseif (isset($colorXml->schemeClr)) {
935+
$result['type'] = Properties::EXCEL_COLOR_TYPE_SCHEME;
936+
$result['value'] = $schemeClr = self::getAttribute($colorXml->schemeClr, 'val', 'string');
937+
if (isset($colorXml->schemeClr->alpha)) {
938+
$alpha = (int) self::getAttribute($colorXml->schemeClr->alpha, 'val', 'string');
939+
$alpha = 100 - (int) ($alpha / 1000);
940+
$result['alpha'] = $alpha;
941+
}
942+
}
943+
944+
return $result;
945+
}
880946
}

src/PhpSpreadsheet/Shared/StringHelper.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,15 +470,15 @@ public static function countCharacters($textValue, $encoding = 'UTF-8')
470470
/**
471471
* Get a substring of a UTF-8 encoded string.
472472
*
473-
* @param string $textValue UTF-8 encoded string
473+
* @param null|string $textValue UTF-8 encoded string
474474
* @param int $offset Start offset
475475
* @param int $length Maximum number of characters in substring
476476
*
477477
* @return string
478478
*/
479479
public static function substring($textValue, $offset, $length = 0)
480480
{
481-
return mb_substr($textValue, $offset, $length, 'UTF-8');
481+
return mb_substr($textValue ?? '', $offset, $length, 'UTF-8');
482482
}
483483

484484
/**

0 commit comments

Comments
 (0)