Skip to content

Commit

Permalink
Add Support to Chart/Axis for Glow/SoftEdges (#2865)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
oleibman authored Jun 3, 2022
1 parent d0ff3d9 commit 5608e05
Show file tree
Hide file tree
Showing 8 changed files with 437 additions and 75 deletions.
25 changes: 0 additions & 25 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1110,31 +1110,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Cell/Coordinate.php

-
message: "#^Parameter \\#1 \\$textValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:substring\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: src/PhpSpreadsheet/Cell/DataType.php

-
message: "#^Parameter \\#1 \\$angle of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowAngle\\(\\) expects int, int\\|null given\\.$#"
count: 1
path: src/PhpSpreadsheet/Chart/Axis.php

-
message: "#^Parameter \\#1 \\$blur of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowBlur\\(\\) expects float, float\\|null given\\.$#"
count: 1
path: src/PhpSpreadsheet/Chart/Axis.php

-
message: "#^Parameter \\#1 \\$distance of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowDistance\\(\\) expects float, float\\|null given\\.$#"
count: 1
path: src/PhpSpreadsheet/Chart/Axis.php

-
message: "#^Parameter \\#2 \\$alpha of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowColor\\(\\) expects int, int\\|string given\\.$#"
count: 1
path: src/PhpSpreadsheet/Chart/Axis.php

-
message: "#^Call to an undefined method object\\:\\:render\\(\\)\\.$#"
count: 1
Expand Down
32 changes: 16 additions & 16 deletions src/PhpSpreadsheet/Chart/Axis.php
Original file line number Diff line number Diff line change
Expand Up @@ -346,17 +346,17 @@ public function getLineStyleArrowLength($arrow)
* @param int $shadowPresets
* @param string $colorValue
* @param string $colorType
* @param string $colorAlpha
* @param float $blur
* @param int $angle
* @param float $distance
* @param null|int|string $colorAlpha
* @param null|float $blur
* @param null|int $angle
* @param null|float $distance
*/
public function setShadowProperties($shadowPresets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $distance = null): void
{
$this->setShadowPresetsProperties((int) $shadowPresets)
->setShadowColor(
$colorValue ?? $this->shadowProperties['color']['value'],
$colorAlpha ?? (int) $this->shadowProperties['color']['alpha'],
(int) ($colorAlpha ?? $this->shadowProperties['color']['alpha']),
$colorType ?? $this->shadowProperties['color']['type']
)
->setShadowBlur($blur)
Expand Down Expand Up @@ -412,9 +412,9 @@ private function setShadowPropertiesMapValues(array $propertiesMap, &$reference
/**
* Set Shadow Color.
*
* @param string $color
* @param int $alpha
* @param string $alphaType
* @param null|string $color
* @param null|int $alpha
* @param null|string $alphaType
*
* @return $this
*/
Expand All @@ -428,7 +428,7 @@ private function setShadowColor($color, $alpha, $alphaType)
/**
* Set Shadow Blur.
*
* @param float $blur
* @param null|float $blur
*
* @return $this
*/
Expand All @@ -444,7 +444,7 @@ private function setShadowBlur($blur)
/**
* Set Shadow Angle.
*
* @param int $angle
* @param null|int $angle
*
* @return $this
*/
Expand All @@ -460,7 +460,7 @@ private function setShadowAngle($angle)
/**
* Set Shadow Distance.
*
* @param float $distance
* @param null|float $distance
*
* @return $this
*/
Expand Down Expand Up @@ -489,9 +489,9 @@ public function getShadowProperty($elements)
* Set Glow Properties.
*
* @param float $size
* @param string $colorValue
* @param int $colorAlpha
* @param string $colorType
* @param null|string $colorValue
* @param null|int $colorAlpha
* @param null|string $colorType
*/
public function setGlowProperties($size, $colorValue = null, $colorAlpha = null, $colorType = null): void
{
Expand All @@ -508,7 +508,7 @@ public function setGlowProperties($size, $colorValue = null, $colorAlpha = null,
*
* @param array|string $property
*
* @return string
* @return null|string
*/
public function getGlowProperty($property)
{
Expand Down Expand Up @@ -555,7 +555,7 @@ private function setGlowColor($color, $alpha, $colorType)
public function setSoftEdges($size): void
{
if ($size !== null) {
$softEdges['size'] = (string) $this->getExcelPointsWidth($size);
$this->softEdges['size'] = (string) $this->getExcelPointsWidth($size);
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/PhpSpreadsheet/Chart/Properties.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ abstract class Properties
const SHADOW_PRESETS_PERSPECTIVE_UPPER_LEFT = 21;
const SHADOW_PRESETS_PERSPECTIVE_LOWER_RIGHT = 22;
const SHADOW_PRESETS_PERSPECTIVE_LOWER_LEFT = 23;
const POINTS_WIDTH_MULTIPLIER = 12700;

/**
* @param float $width
Expand All @@ -118,7 +119,7 @@ abstract class Properties
*/
protected function getExcelPointsWidth($width)
{
return $width * 12700;
return $width * self::POINTS_WIDTH_MULTIPLIER;
}

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

protected function getTrueAlpha($alpha)
{
return (string) 100 - $alpha . '000';
return (string) (100 - $alpha) . '000';
}

protected function setColorProperties($color, $alpha, $colorType)
Expand Down
102 changes: 84 additions & 18 deletions src/PhpSpreadsheet/Reader/Xlsx/Chart.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;

use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Chart\Axis;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\Layout;
use PhpOffice\PhpSpreadsheet\Chart\Legend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Properties;
use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Style\Color;
Expand Down Expand Up @@ -67,6 +69,8 @@ public function readChart(SimpleXMLElement $chartElements, $chartName)
$dispBlanksAs = $plotVisOnly = null;
$plotArea = null;
$rotX = $rotY = $rAngAx = $perspective = null;
$xAxis = new Axis();
$yAxis = new Axis();
foreach ($chartElementsC as $chartElementKey => $chartElement) {
switch ($chartElementKey) {
case 'chart':
Expand All @@ -93,6 +97,7 @@ public function readChart(SimpleXMLElement $chartElements, $chartName)
if (isset($chartDetail->title)) {
$XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
}
$this->readEffects($chartDetail, $xAxis);

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

break;
case 'valAx':
$whichAxis = null;
if (isset($chartDetail->title, $chartDetail->axPos)) {
$axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
$axPos = self::getAttribute($chartDetail->axPos, 'val', 'string');
Expand All @@ -110,15 +116,18 @@ public function readChart(SimpleXMLElement $chartElements, $chartName)
case 't':
case 'b':
$XaxisLabel = $axisLabel;
$whichAxis = $xAxis;

break;
case 'r':
case 'l':
$YaxisLabel = $axisLabel;
$whichAxis = $yAxis;

break;
}
}
$this->readEffects($chartDetail, $whichAxis);

break;
case 'barChart':
Expand Down Expand Up @@ -240,7 +249,7 @@ public function readChart(SimpleXMLElement $chartElements, $chartName)
}
}
}
$chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel);
$chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis);
if (is_int($rotX)) {
$chart->setRotX($rotX);
}
Expand Down Expand Up @@ -345,9 +354,8 @@ private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType
if (is_countable($ln->noFill) && count($ln->noFill) === 1) {
$noFill = true;
}
$sf = $children->solidFill->schemeClr;
if ($sf) {
$schemeClr = self::getAttribute($sf, 'val', 'string');
if (isset($children->solidFill)) {
$this->readColor($children->solidFill, $srgbClr, $schemeClr);
}

break;
Expand All @@ -357,8 +365,8 @@ private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType
$pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null;
if (count($seriesDetail->spPr) === 1) {
$ln = $seriesDetail->spPr->children($this->aNamespace);
if (count($ln->solidFill) === 1) {
$srgbClr = self::getAttribute($ln->solidFill->srgbClr, 'val', 'string');
if (isset($ln->solidFill)) {
$this->readColor($ln->solidFill, $srgbClr, $schemeClr);
}
}

Expand Down Expand Up @@ -603,7 +611,8 @@ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
$defaultLatin = null;
$defaultEastAsian = null;
$defaultComplexScript = null;
$defaultColor = null;
$defaultSrgbColor = '';
$defaultSchemeColor = '';
if (isset($titleDetailPart->pPr->defRPr)) {
/** @var ?int */
$defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
Expand Down Expand Up @@ -632,9 +641,8 @@ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
/** @var ?string */
$defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
}
if (isset($titleDetailPart->pPr->defRPr->solidFill->srgbClr)) {
/** @var ?string */
$defaultColor = self::getAttribute($titleDetailPart->pPr->defRPr->solidFill->srgbClr, 'val', 'string');
if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
$this->readColor($titleDetailPart->pPr->defRPr->solidFill, $defaultSrgbColor, $defaultSchemeClr);
}
}
foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
Expand All @@ -660,7 +668,8 @@ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
$latinName = null;
$eastAsian = null;
$complexScript = null;
$fontColor = null;
$fontSrgbClr = '';
$fontSchemeClr = '';
$uSchemeClr = null;
if (isset($titleDetailElement->rPr)) {
// not used now, not sure it ever was, grandfathering
Expand All @@ -686,10 +695,9 @@ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText

// not used now, not sure it ever was, grandfathering
/** @var ?string */
$fontColor = self::getAttribute($titleDetailElement->rPr, 'color', 'string');
if (isset($titleDetailElement->rPr->solidFill->srgbClr)) {
/** @var ?string */
$fontColor = self::getAttribute($titleDetailElement->rPr->solidFill->srgbClr, 'val', 'string');
$fontSrgbClr = self::getAttribute($titleDetailElement->rPr, 'color', 'string');
if (isset($titleDetailElement->rPr->solidFill)) {
$this->readColor($titleDetailElement->rPr->solidFill, $fontSrgbClr, $fontSchemeClr);
}

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

$fontColor = $fontColor ?? $defaultColor;
if ($fontColor !== null) {
$objText->getFont()->setColor(new Color($fontColor));
$fontSrgbClr = $fontSrgbClr ?? $defaultSrgbColor;
if (!empty($fontSrgbClr)) {
$objText->getFont()->setColor(new Color($fontSrgbClr));
$fontFound = true;
}
// need to think about what to do here
//$fontSchemeClr = $fontSchemeClr ?? $defaultSchemeColor;
//if (!empty($fontSchemeClr)) {
// $objText->getFont()->setColor(new Color($fontSrgbClr));
// $fontFound = true;
//}

$bold = $bold ?? $defaultBold;
if ($bold !== null) {
Expand Down Expand Up @@ -877,4 +891,56 @@ private function setChartAttributes(Layout $plotArea, $plotAttributes): void
}
}
}

/**
* @param null|Axis $chartObject may be extended to include other types
*/
private function readEffects(SimpleXMLElement $chartDetail, $chartObject): void
{
if (!isset($chartObject, $chartDetail->spPr)) {
return;
}
$sppr = $chartDetail->spPr->children($this->aNamespace);

if (isset($sppr->effectLst->glow)) {
$axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / Properties::POINTS_WIDTH_MULTIPLIER;
if ($axisGlowSize != 0.0) {
$srgbClr = $schemeClr = '';
$colorArray = $this->readColor($sppr->effectLst->glow, $srgbClr, $schemeClr);
$chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']);
}
}

if (isset($sppr->effectLst->softEdge)) {
$chartObject->setSoftEdges((float) self::getAttribute($sppr->effectLst->softEdge, 'rad', 'string') / Properties::POINTS_WIDTH_MULTIPLIER);
}
}

private function readColor(SimpleXMLElement $colorXml, ?string &$srgbClr, ?string &$schemeClr): array
{
$result = [
'type' => null,
'value' => null,
'alpha' => null,
];
if (isset($colorXml->srgbClr)) {
$result['type'] = Properties::EXCEL_COLOR_TYPE_ARGB;
$result['value'] = $srgbClr = self::getAttribute($colorXml->srgbClr, 'val', 'string');
if (isset($colorXml->srgbClr->alpha)) {
$alpha = (int) self::getAttribute($colorXml->srgbClr->alpha, 'val', 'string');
$alpha = 100 - (int) ($alpha / 1000);
$result['alpha'] = $alpha;
}
} elseif (isset($colorXml->schemeClr)) {
$result['type'] = Properties::EXCEL_COLOR_TYPE_SCHEME;
$result['value'] = $schemeClr = self::getAttribute($colorXml->schemeClr, 'val', 'string');
if (isset($colorXml->schemeClr->alpha)) {
$alpha = (int) self::getAttribute($colorXml->schemeClr->alpha, 'val', 'string');
$alpha = 100 - (int) ($alpha / 1000);
$result['alpha'] = $alpha;
}
}

return $result;
}
}
4 changes: 2 additions & 2 deletions src/PhpSpreadsheet/Shared/StringHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -470,15 +470,15 @@ public static function countCharacters($textValue, $encoding = 'UTF-8')
/**
* Get a substring of a UTF-8 encoded string.
*
* @param string $textValue UTF-8 encoded string
* @param null|string $textValue UTF-8 encoded string
* @param int $offset Start offset
* @param int $length Maximum number of characters in substring
*
* @return string
*/
public static function substring($textValue, $offset, $length = 0)
{
return mb_substr($textValue, $offset, $length, 'UTF-8');
return mb_substr($textValue ?? '', $offset, $length, 'UTF-8');
}

/**
Expand Down
Loading

0 comments on commit 5608e05

Please sign in to comment.