Skip to content

Commit fe85cc0

Browse files
committed
More Chart Fixes
Taking up where PHPOffice#2828 left off. Most of the following changes are demonstrated in 32readwriteChartWithImages1: - Adds support for "scheme" colors (because rgb, theme, and index colors just weren't enough for Excel) for DataSeriesValues. See issue PHPOffice#2299. - For chart titles (including axis labels), rather than a font name, Excel supplies a 3-fold series of font names for Latin, East Asian, and Complex Scripts. New properties `latin`, `eastAsian`, and `complexScript` are added to the Font class. I frankly have no idea how, or even if, you can set these in Excel; my test case (sample 32readwriteScatterChart7) is a result of manually editing the XML. - Add support for subscript/superscript to chart titles. This requires a new property `baseLine` in Font (positive=superscript negative=subscript baseline value says how high/low). - Support for underscore with different scheme color than its text, using a new string property `uSchemeClr` in Font. - Support for extra options for strikethrough, using a new string property `strikeType` in Font. - Support for extra options for underscore type, using the existing string property `underline` in Font. - I do not anticipate that any of the new Font properties will be used except for chart titles. - If no default font overrides are found for a Rich Text element in chart titles, and no explicit font overrides are found for a Run under such an element, the font element of the Run is set to null. - PhpSpreadsheet will always write a tag `a:pPr` and, underneath that, an empty tag `a:defRPr`, for default font settings for chart titles and axis labels. Combined with the previous bullet item, this will prevent PhpSpreadsheet from inadvertently overriding the Excel defaults (18 point bold Calibri for chart title, 10 point bold Calibri for axis labels). - Axis labels will now be written to XML in the same manner as chart titles. Among other considerations, this means that they can now have colors. Fix PHPOffice#2700. Supersedes PR PHPOffice#2701. Demonstrated in sample 32readwriteStockChart5.
1 parent b895720 commit fe85cc0

File tree

10 files changed

+513
-31
lines changed

10 files changed

+513
-31
lines changed

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4982,7 +4982,7 @@ parameters:
49824982

49834983
-
49844984
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#"
4985-
count: 5
4985+
count: 4
49864986
path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
49874987

49884988
-
30.9 KB
Binary file not shown.
30.7 KB
Binary file not shown.

src/PhpSpreadsheet/Chart/DataSeriesValues.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ class DataSeriesValues
7373
*/
7474
private $fillColor;
7575

76+
/** @var string */
77+
private $schemeClr = '';
78+
7679
/**
7780
* Line Width.
7881
*
@@ -440,4 +443,16 @@ public function setScatterLines(bool $scatterLines): self
440443

441444
return $this;
442445
}
446+
447+
public function getSchemeClr(): string
448+
{
449+
return $this->schemeClr;
450+
}
451+
452+
public function setSchemeClr(string $schemeClr): self
453+
{
454+
$this->schemeClr = $schemeClr;
455+
456+
return $this;
457+
}
443458
}

src/PhpSpreadsheet/Reader/Xlsx/Chart.php

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ private static function chartDataSeries($chartDetail, $namespacesChartMeta, $plo
288288
$lineWidth = null;
289289
$pointSize = null;
290290
$noFill = false;
291+
$schemeClr = '';
291292
foreach ($seriesDetails as $seriesKey => $seriesDetail) {
292293
switch ($seriesKey) {
293294
case 'idx':
@@ -304,11 +305,16 @@ private static function chartDataSeries($chartDetail, $namespacesChartMeta, $plo
304305

305306
break;
306307
case 'spPr':
307-
$ln = $seriesDetail->children($namespacesChartMeta['a'])->ln;
308+
$children = $seriesDetail->children($namespacesChartMeta['a']);
309+
$ln = $children->ln;
308310
$lineWidth = self::getAttribute($ln, 'w', 'string');
309311
if (is_countable($ln->noFill) && count($ln->noFill) === 1) {
310312
$noFill = true;
311313
}
314+
$sf = $children->solidFill->schemeClr;
315+
if ($sf) {
316+
$schemeClr = self::getAttribute($sf, 'val', 'string');
317+
}
312318

313319
break;
314320
case 'marker':
@@ -367,6 +373,17 @@ private static function chartDataSeries($chartDetail, $namespacesChartMeta, $plo
367373
$seriesValues[$seriesIndex]->setLineWidth((int) $lineWidth);
368374
}
369375
}
376+
if ($schemeClr) {
377+
if (isset($seriesLabel[$seriesIndex])) {
378+
$seriesLabel[$seriesIndex]->setSchemeClr($schemeClr);
379+
}
380+
if (isset($seriesCategory[$seriesIndex])) {
381+
$seriesCategory[$seriesIndex]->setSchemeClr($schemeClr);
382+
}
383+
if (isset($seriesValues[$seriesIndex])) {
384+
$seriesValues[$seriesIndex]->setSchemeClr($schemeClr);
385+
}
386+
}
370387
}
371388
}
372389

@@ -514,6 +531,9 @@ private static function parseRichText(SimpleXMLElement $titleDetailPart)
514531
$defaultStrikethrough = null;
515532
$defaultBaseline = null;
516533
$defaultFontName = null;
534+
$defaultLatin = null;
535+
$defaultEastAsian = null;
536+
$defaultComplexScript = null;
517537
$defaultColor = null;
518538
if (isset($titleDetailPart->pPr->defRPr)) {
519539
/** @var ?int */
@@ -528,21 +548,38 @@ private static function parseRichText(SimpleXMLElement $titleDetailPart)
528548
$defaultStrikethrough = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string');
529549
/** @var ?int */
530550
$defaultBaseline = self::getAttribute($titleDetailPart->pPr->defRPr, 'baseline', 'integer');
551+
if (isset($titleDetailPart->defRPr->rFont['val'])) {
552+
$defaultFontName = (string) $titleDetailPart->defRPr->rFont['val'];
553+
}
531554
if (isset($titleDetailPart->pPr->defRPr->latin)) {
532555
/** @var ?string */
533-
$defaultFontName = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
556+
$defaultLatin = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
557+
}
558+
if (isset($titleDetailPart->pPr->defRPr->ea)) {
559+
/** @var ?string */
560+
$defaultEastAsian = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string');
561+
}
562+
if (isset($titleDetailPart->pPr->defRPr->cs)) {
563+
/** @var ?string */
564+
$defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
534565
}
535566
if (isset($titleDetailPart->pPr->defRPr->solidFill->srgbClr)) {
536567
/** @var ?string */
537568
$defaultColor = self::getAttribute($titleDetailPart->pPr->defRPr->solidFill->srgbClr, 'val', 'string');
538569
}
539570
}
540571
foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
541-
if (isset($titleDetailElement->t)) {
542-
$objText = $value->createTextRun((string) $titleDetailElement->t);
572+
if (
573+
(string) $titleDetailElementKey !== 'r'
574+
|| !isset($titleDetailElement->t)
575+
) {
576+
continue;
543577
}
544-
if ($objText === null || $objText->getFont() === null) {
578+
$objText = $value->createTextRun((string) $titleDetailElement->t);
579+
if ($objText->getFont() === null) {
580+
// @codeCoverageIgnoreStart
545581
continue;
582+
// @codeCoverageIgnoreEnd
546583
}
547584
$fontSize = null;
548585
$bold = null;
@@ -551,15 +588,29 @@ private static function parseRichText(SimpleXMLElement $titleDetailPart)
551588
$strikethrough = null;
552589
$baseline = null;
553590
$fontName = null;
591+
$latinName = null;
592+
$eastAsian = null;
593+
$complexScript = null;
554594
$fontColor = null;
595+
$uSchemeClr = null;
555596
if (isset($titleDetailElement->rPr)) {
556597
// not used now, not sure it ever was, grandfathering
557598
if (isset($titleDetailElement->rPr->rFont['val'])) {
599+
// @codeCoverageIgnoreStart
558600
$fontName = (string) $titleDetailElement->rPr->rFont['val'];
601+
// @codeCoverageIgnoreEnd
559602
}
560603
if (isset($titleDetailElement->rPr->latin)) {
561604
/** @var ?string */
562-
$fontName = self::getAttribute($titleDetailElement->rPr->latin, 'typeface', 'string');
605+
$latinName = self::getAttribute($titleDetailElement->rPr->latin, 'typeface', 'string');
606+
}
607+
if (isset($titleDetailElement->rPr->ea)) {
608+
/** @var ?string */
609+
$eastAsian = self::getAttribute($titleDetailElement->rPr->ea, 'typeface', 'string');
610+
}
611+
if (isset($titleDetailElement->rPr->cs)) {
612+
/** @var ?string */
613+
$complexScript = self::getAttribute($titleDetailElement->rPr->cs, 'typeface', 'string');
563614
}
564615
/** @var ?int */
565616
$fontSize = self::getAttribute($titleDetailElement->rPr, 'sz', 'integer');
@@ -583,43 +634,72 @@ private static function parseRichText(SimpleXMLElement $titleDetailPart)
583634

584635
/** @var ?string */
585636
$underscore = self::getAttribute($titleDetailElement->rPr, 'u', 'string');
637+
if (isset($titleDetailElement->rPr->uFill->solidFill->schemeClr)) {
638+
/** @var ?string */
639+
$uSchemeClr = self::getAttribute($titleDetailElement->rPr->uFill->solidFill->schemeClr, 'val', 'string');
640+
}
586641

587642
/** @var ?string */
588-
$strikethrough = self::getAttribute($titleDetailElement->rPr, 's', 'string');
643+
$strikethrough = self::getAttribute($titleDetailElement->rPr, 'strike', 'string');
589644
}
590645

646+
$fontFound = false;
647+
$latinName = $latinName ?? $defaultLatin;
648+
if ($latinName !== null) {
649+
$objText->getFont()->setLatin($latinName);
650+
$fontFound = true;
651+
}
652+
$eastAsian = $eastAsian ?? $defaultEastAsian;
653+
if ($eastAsian !== null) {
654+
$objText->getFont()->setEastAsian($eastAsian);
655+
$fontFound = true;
656+
}
657+
$complexScript = $complexScript ?? $defaultComplexScript;
658+
if ($complexScript !== null) {
659+
$objText->getFont()->setComplexScript($complexScript);
660+
$fontFound = true;
661+
}
591662
$fontName = $fontName ?? $defaultFontName;
592663
if ($fontName !== null) {
664+
// @codeCoverageIgnoreStart
593665
$objText->getFont()->setName($fontName);
666+
$fontFound = true;
667+
// @codeCoverageIgnoreEnd
594668
}
595669

596670
$fontSize = $fontSize ?? $defaultFontSize;
597671
if (is_int($fontSize)) {
598672
$objText->getFont()->setSize(floor($fontSize / 100));
673+
$fontFound = true;
599674
}
600675

601676
$fontColor = $fontColor ?? $defaultColor;
602677
if ($fontColor !== null) {
603678
$objText->getFont()->setColor(new Color($fontColor));
679+
$fontFound = true;
604680
}
605681

606682
$bold = $bold ?? $defaultBold;
607683
if ($bold !== null) {
608684
$objText->getFont()->setBold($bold);
685+
$fontFound = true;
609686
}
610687

611688
$italic = $italic ?? $defaultItalic;
612689
if ($italic !== null) {
613690
$objText->getFont()->setItalic($italic);
691+
$fontFound = true;
614692
}
615693

616694
$baseline = $baseline ?? $defaultBaseline;
617695
if ($baseline !== null) {
696+
$objText->getFont()->setBaseLine($baseline);
618697
if ($baseline > 0) {
619698
$objText->getFont()->setSuperscript(true);
620699
} elseif ($baseline < 0) {
621700
$objText->getFont()->setSubscript(true);
622701
}
702+
$fontFound = true;
623703
}
624704

625705
$underscore = $underscore ?? $defaultUnderscore;
@@ -628,18 +708,29 @@ private static function parseRichText(SimpleXMLElement $titleDetailPart)
628708
$objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
629709
} elseif ($underscore == 'dbl') {
630710
$objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
711+
} elseif ($underscore !== '') {
712+
$objText->getFont()->setUnderline($underscore);
631713
} else {
632714
$objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
633715
}
716+
$fontFound = true;
717+
if ($uSchemeClr) {
718+
$objText->getFont()->setUSchemeClr($uSchemeClr);
719+
}
634720
}
635721

636722
$strikethrough = $strikethrough ?? $defaultStrikethrough;
637723
if ($strikethrough !== null) {
724+
$objText->getFont()->setStrikeType($strikethrough);
638725
if ($strikethrough == 'noStrike') {
639726
$objText->getFont()->setStrikethrough(false);
640727
} else {
641728
$objText->getFont()->setStrikethrough(true);
642729
}
730+
$fontFound = true;
731+
}
732+
if ($fontFound === false) {
733+
$objText->setFont(null);
643734
}
644735
}
645736

0 commit comments

Comments
 (0)