From 39a21e0549acb35db7da3cff83d2366fa15b64de Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 24 Aug 2022 17:18:38 -0700 Subject: [PATCH] Additional Properties for Trendlines Fix #3011. Some properties for Trendlines were omitted in the original request for this feature. Also, the trendlines sample spreadsheet included two charts. The rendering script 35_Chart_render handles this, but overlays the first output file with the second. It is changed to produce files with different names. --- .../33_Chart_create_scatter5_trendlines.php | 3 +- samples/Chart/35_Chart_render.php | 3 + .../32readwriteScatterChartTrendlines1.xlsx | Bin 15275 -> 15334 bytes src/PhpSpreadsheet/Chart/TrendLine.php | 108 +++++++++++++++++- src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 20 +++- src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 24 ++++ .../Chart/TrendLineTest.php | 12 ++ 7 files changed, 164 insertions(+), 6 deletions(-) diff --git a/samples/Chart/33_Chart_create_scatter5_trendlines.php b/samples/Chart/33_Chart_create_scatter5_trendlines.php index a87f6ee13e..350046ad35 100644 --- a/samples/Chart/33_Chart_create_scatter5_trendlines.php +++ b/samples/Chart/33_Chart_create_scatter5_trendlines.php @@ -193,7 +193,7 @@ // 3- moving Avg (period=2) single-arrow trendline, w=1.5, same color as marker fill; no dispRSqr, no dispEq $trendLines = [ new TrendLine(TrendLine::TRENDLINE_LINEAR, null, null, false, false), - new TrendLine(TrendLine::TRENDLINE_POLYNOMIAL, 3, null, true, true), + new TrendLine(TrendLine::TRENDLINE_POLYNOMIAL, 3, null, true, true, 20.0, 28.0, 44104.5, 'metric3 polynomial fit'), new TrendLine(TrendLine::TRENDLINE_MOVING_AVG, null, 2, true), ]; $dataSeriesValues[0]->setTrendLines($trendLines); @@ -263,6 +263,7 @@ // Add the chart to the worksheet $chartSheet $chartSheet->addChart($chart); +$spreadsheet->setActiveSheetIndex(1); // Save Excel 2007 file $filename = $helper->getFilename(__FILE__); diff --git a/samples/Chart/35_Chart_render.php b/samples/Chart/35_Chart_render.php index 2376008c64..891cd27c7f 100644 --- a/samples/Chart/35_Chart_render.php +++ b/samples/Chart/35_Chart_render.php @@ -73,6 +73,9 @@ $helper->log(' ' . $chartName . ' - ' . $caption); $jpegFile = $helper->getFilename('35-' . $inputFileNameShort, 'png'); + if ($i !== 0) { + $jpegFile = substr($jpegFile, 0, -3) . "$i.png"; + } if (file_exists($jpegFile)) { unlink($jpegFile); } diff --git a/samples/templates/32readwriteScatterChartTrendlines1.xlsx b/samples/templates/32readwriteScatterChartTrendlines1.xlsx index f48366fe0a3705bfaa1c91671a26c72b054b72e1..c8411d4daf83f0d6095c3d8721efaa54423bc5cb 100644 GIT binary patch delta 3291 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 Z00000#FIliJ^_-Gf;%GyU^f5&004C^C3^q> delta 3230 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 Q0auf~J0k`qHvj+t0D=4oYybcN diff --git a/src/PhpSpreadsheet/Chart/TrendLine.php b/src/PhpSpreadsheet/Chart/TrendLine.php index e177f8198a..75a5896c93 100644 --- a/src/PhpSpreadsheet/Chart/TrendLine.php +++ b/src/PhpSpreadsheet/Chart/TrendLine.php @@ -34,13 +34,44 @@ class TrendLine extends Properties /** @var bool */ private $dispEq = false; + /** @var string */ + private $name = ''; + + /** @var float */ + private $backward = 0.0; + + /** @var float */ + private $forward = 0.0; + + /** @var float */ + private $intercept = 0.0; + /** * Create a new TrendLine object. */ - public function __construct(string $trendLineType = '', ?int $order = null, ?int $period = null, bool $dispRSqr = false, bool $dispEq = false) - { + public function __construct( + string $trendLineType = '', + ?int $order = null, + ?int $period = null, + bool $dispRSqr = false, + bool $dispEq = false, + ?float $backward = null, + ?float $forward = null, + ?float $intercept = null, + ?string $name = null + ) { parent::__construct(); - $this->setTrendLineProperties($trendLineType, $order, $period, $dispRSqr, $dispEq); + $this->setTrendLineProperties( + $trendLineType, + $order, + $period, + $dispRSqr, + $dispEq, + $backward, + $forward, + $intercept, + $name + ); } public function getTrendLineType(): string @@ -103,8 +134,65 @@ public function setDispEq(bool $dispEq): self return $this; } - public function setTrendLineProperties(?string $trendLineType = null, ?int $order = 0, ?int $period = 0, ?bool $dispRSqr = false, ?bool $dispEq = false): self + public function getName(): string { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getBackward(): float + { + return $this->backward; + } + + public function setBackward(float $backward): self + { + $this->backward = $backward; + + return $this; + } + + public function getForward(): float + { + return $this->forward; + } + + public function setForward(float $forward): self + { + $this->forward = $forward; + + return $this; + } + + public function getIntercept(): float + { + return $this->intercept; + } + + public function setIntercept(float $intercept): self + { + $this->intercept = $intercept; + + return $this; + } + + public function setTrendLineProperties( + ?string $trendLineType = null, + ?int $order = 0, + ?int $period = 0, + ?bool $dispRSqr = false, + ?bool $dispEq = false, + ?float $backward = null, + ?float $forward = null, + ?float $intercept = null, + ?string $name = null + ): self { if (!empty($trendLineType)) { $this->setTrendLineType($trendLineType); } @@ -120,6 +208,18 @@ public function setTrendLineProperties(?string $trendLineType = null, ?int $orde if ($dispEq !== null) { $this->setDispEq($dispEq); } + if ($backward !== null) { + $this->setBackward($backward); + } + if ($forward !== null) { + $this->setForward($forward); + } + if ($intercept !== null) { + $this->setIntercept($intercept); + } + if ($name !== null) { + $this->setName($name); + } return $this; } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index dab2b41089..6a6c3182bb 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -536,7 +536,25 @@ private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType $order = self::getAttribute($seriesDetail->order, 'val', 'integer'); /** @var ?int */ $period = self::getAttribute($seriesDetail->period, 'val', 'integer'); - $trendLine->setTrendLineProperties($trendLineType, $order, $period, $dispRSqr, $dispEq); + /** @var ?float */ + $forward = self::getAttribute($seriesDetail->forward, 'val', 'float'); + /** @var ?float */ + $backward = self::getAttribute($seriesDetail->backward, 'val', 'float'); + /** @var ?float */ + $intercept = self::getAttribute($seriesDetail->intercept, 'val', 'float'); + /** @var ?string */ + $name = (string) $seriesDetail->name; + $trendLine->setTrendLineProperties( + $trendLineType, + $order, + $period, + $dispRSqr, + $dispEq, + $backward, + $forward, + $intercept, + $name + ); $trendLines[] = $trendLine; break; diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index ad746a0a82..797d71dd77 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -1132,10 +1132,19 @@ private function writePlotGroup(?DataSeries $plotGroup, string $groupType, XMLWr $period = $trendLine->getPeriod(); $dispRSqr = $trendLine->getDispRSqr(); $dispEq = $trendLine->getDispEq(); + $forward = $trendLine->getForward(); + $backward = $trendLine->getBackward(); + $intercept = $trendLine->getIntercept(); + $name = $trendLine->getName(); $trendLineColor = $trendLine->getLineColor(); // ChartColor $trendLineWidth = $trendLine->getLineStyleProperty('width'); $objWriter->startElement('c:trendline'); // N.B. lowercase 'ell' + if ($name !== '') { + $objWriter->startElement('c:name'); + $objWriter->writeRawData($name); + $objWriter->endElement(); // c:name + } $objWriter->startElement('c:spPr'); if (!$trendLineColor->isUsable()) { @@ -1155,6 +1164,21 @@ private function writePlotGroup(?DataSeries $plotGroup, string $groupType, XMLWr $objWriter->startElement('c:trendlineType'); // N.B lowercase 'ell' $objWriter->writeAttribute('val', $trendLineType); $objWriter->endElement(); // trendlineType + if ($backward !== 0.0) { + $objWriter->startElement('c:backward'); + $objWriter->writeAttribute('val', "$backward"); + $objWriter->endElement(); // c:backward + } + if ($forward !== 0.0) { + $objWriter->startElement('c:forward'); + $objWriter->writeAttribute('val', "$forward"); + $objWriter->endElement(); // c:forward + } + if ($intercept !== 0.0) { + $objWriter->startElement('c:intercept'); + $objWriter->writeAttribute('val', "$intercept"); + $objWriter->endElement(); // c:intercept + } if ($trendLineType == TrendLine::TRENDLINE_POLYNOMIAL) { $objWriter->startElement('c:order'); $objWriter->writeAttribute('val', $order); diff --git a/tests/PhpSpreadsheetTests/Chart/TrendLineTest.php b/tests/PhpSpreadsheetTests/Chart/TrendLineTest.php index c8550d8319..954a733688 100644 --- a/tests/PhpSpreadsheetTests/Chart/TrendLineTest.php +++ b/tests/PhpSpreadsheetTests/Chart/TrendLineTest.php @@ -73,6 +73,10 @@ public function testTrendLine(): void self::assertSame('accent4', $lineColor->getValue()); self::assertSame('stealth', $trendLine->getLineStyleProperty(['arrow', 'head', 'type'])); self::assertEquals(0.5, $trendLine->getLineStyleProperty('width')); + self::assertSame('', $trendLine->getName()); + self::assertSame(0.0, $trendLine->getBackward()); + self::assertSame(0.0, $trendLine->getForward()); + self::assertSame(0.0, $trendLine->getIntercept()); $trendLine = $trendLines[1]; self::assertSame('poly', $trendLine->getTrendLineType()); @@ -82,6 +86,10 @@ public function testTrendLine(): void self::assertSame('accent3', $lineColor->getValue()); self::assertNull($trendLine->getLineStyleProperty(['arrow', 'head', 'type'])); self::assertEquals(1.25, $trendLine->getLineStyleProperty('width')); + self::assertSame('metric3 polynomial', $trendLine->getName()); + self::assertSame(20.0, $trendLine->getBackward()); + self::assertSame(28.0, $trendLine->getForward()); + self::assertSame(14400.5, $trendLine->getIntercept()); $trendLine = $trendLines[2]; self::assertSame('movingAvg', $trendLine->getTrendLineType()); @@ -91,6 +99,10 @@ public function testTrendLine(): void self::assertSame('accent2', $lineColor->getValue()); self::assertNull($trendLine->getLineStyleProperty(['arrow', 'head', 'type'])); self::assertEquals(1.5, $trendLine->getLineStyleProperty('width')); + self::assertSame('', $trendLine->getName()); + self::assertSame(0.0, $trendLine->getBackward()); + self::assertSame(0.0, $trendLine->getForward()); + self::assertSame(0.0, $trendLine->getIntercept()); $reloadedSpreadsheet->disconnectWorksheets(); }