From 784eb6cbef3f3f4dad28e1040439ed261498e47b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 15 Apr 2023 07:17:44 -0700 Subject: [PATCH] Stock Chart Improvements - Minor Break (#3515) Stock charts currently ignore upDownBars tag and its subsidiary gapWidth, upBars, and downBars tags when reading, and hard-codes those tags on write. As a result, the sample reproductions of stock charts in the 32* series aren't faithful to the originals. This PR fixes samples 1, 2, and 5. Samples 3 and 4 are reproduced better, but they require currently unsupported secondary axes (issue #560, issue #1072, and PR #1073 were closed as stale; see also https://github.com/PHPOffice/PHPExcel/pull/1037). I will start to look at those, but it could take a while, and I don't think there's a reason to delay this in the meantime. Charts which depended on the hard-coded values written by the Xlsx Chart writer will be slightly different as a result of this change. To restore the hard-coded behavior: ```php $plotArea->setGapWidth(300); $plotArea->setUseUpBars(true); $plotArea->setUseDownBars(true); ``` The new behavior is demonstrated in 33_Chart_create_stock. The old behavior is demonstrated (with the code above) in new 33_Chart_create_stock2. --- samples/Chart/33_Chart_create_stock2.php | 127 ++++++++++++++++++ samples/templates/32readwriteStockChart2.xlsx | Bin 30413 -> 31424 bytes src/PhpSpreadsheet/Chart/PlotArea.php | 45 +++++++ src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 21 +++ src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 33 +++-- 5 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 samples/Chart/33_Chart_create_stock2.php diff --git a/samples/Chart/33_Chart_create_stock2.php b/samples/Chart/33_Chart_create_stock2.php new file mode 100644 index 000000000..0d5ebf2c0 --- /dev/null +++ b/samples/Chart/33_Chart_create_stock2.php @@ -0,0 +1,127 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['Counts', 'Max', 'Min', 'Min Threshold', 'Max Threshold'], + [10, 10, 5, 0, 50], + [30, 20, 10, 0, 50], + [20, 30, 15, 0, 50], + [40, 10, 0, 0, 50], + [100, 40, 5, 0, 50], + ], + null, + 'A1', + true +); +$worksheet->getStyle('B2:E6')->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_NUMBER_00); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), //Max / Open + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), //Min / Close + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), //Min Threshold / Min + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$E$1', null, 1), //Max Threshold / Max +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$6', null, 5), // Counts +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$6', null, 5), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$6', null, 5), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$6', null, 5), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$E$2:$E$6', null, 5), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_STOCKCHART, // plotType + null, // plotGrouping - if we set this to not null, then xlsx throws error + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); +$legend->getBorderLines()->setLineColorProperties('ffc000', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$legend->getFillColor()->setColorProperties('cccccc'); +$legendText = new AxisText(); +$legendText->getFillColorObject()->setValue('008080')->setType(ChartColor::EXCEL_COLOR_TYPE_RGB); +$legendText->setShadowProperties(1); +$legend->setLegendText($legendText); + +$title = new Title('Test Stock Chart'); +$xAxisLabel = new Title('Counts'); +$yAxisLabel = new Title('Values'); + +// 3 stmts below are only difference from 33_chart_create_stock.php +$plotArea->setGapWidth(300); +$plotArea->setUseUpBars(true); +$plotArea->setUseDownBars(true); + +// Create the chart +$chart = new Chart( + 'stock-chart', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + $xAxisLabel, // xAxisLabel + $yAxisLabel // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('A7'); +$chart->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); +$worksheet->setSelectedCells('G2'); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/samples/templates/32readwriteStockChart2.xlsx b/samples/templates/32readwriteStockChart2.xlsx index 1e6d1fff6c04ad30f58e7555bf2fa26bb1dd2c33..d5d1a99b7cc7d6e76a3f8fdbe080f8ef1d2dba0e 100644 GIT binary patch delta 15096 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 zn(sdeP{gradientFillStops; } + + /** @var ?int */ + private $gapWidth; + + /** @var bool */ + private $useUpBars = false; + + /** @var bool */ + private $useDownBars = false; + + public function getGapWidth(): ?int + { + return $this->gapWidth; + } + + public function setGapWidth(?int $gapWidth): self + { + $this->gapWidth = $gapWidth; + + return $this; + } + + public function getUseUpBars(): bool + { + return $this->useUpBars; + } + + public function setUseUpBars(bool $useUpBars): self + { + $this->useUpBars = $useUpBars; + + return $this; + } + + public function getUseDownBars(): bool + { + return $this->useDownBars; + } + + public function setUseDownBars(bool $useDownBars): self + { + $this->useDownBars = $useDownBars; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index 7d33c6eba..c5a59f558 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -82,6 +82,9 @@ class Chart $gradientArray = []; $gradientLin = null; $roundedCorners = false; + $gapWidth = null; + $useUpBars = null; + $useDownBars = null; foreach ($chartElementsC as $chartElementKey => $chartElement) { switch ($chartElementKey) { case 'spPr': @@ -332,6 +335,15 @@ class Chart break; case 'stockChart': $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey); + if (isset($chartDetail->upDownBars->gapWidth)) { + $gapWidth = self::getAttribute($chartDetail->upDownBars->gapWidth, 'val', 'integer'); + } + if (isset($chartDetail->upDownBars->upBars)) { + $useUpBars = true; + } + if (isset($chartDetail->upDownBars->downBars)) { + $useDownBars = true; + } $plotAttributes = $this->readChartAttributes($chartDetail); break; @@ -348,6 +360,15 @@ class Chart if (!empty($gradientArray)) { $plotArea->setGradientFillProperties($gradientArray, $gradientLin); } + if (is_int($gapWidth)) { + $plotArea->setGapWidth($gapWidth); + } + if ($useUpBars === true) { + $plotArea->setUseUpBars(true); + } + if ($useDownBars === true) { + $plotArea->setUseDownBars(true); + } break; case 'plotVisOnly': diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index 6200159a7..6d302aac4 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -331,19 +331,26 @@ class Chart extends WriterPart $objWriter->startElement('c:hiLowLines'); $objWriter->endElement(); - $objWriter->startElement('c:upDownBars'); - - $objWriter->startElement('c:gapWidth'); - $objWriter->writeAttribute('val', '300'); - $objWriter->endElement(); - - $objWriter->startElement('c:upBars'); - $objWriter->endElement(); - - $objWriter->startElement('c:downBars'); - $objWriter->endElement(); - - $objWriter->endElement(); + $gapWidth = $plotArea->getGapWidth(); + $upBars = $plotArea->getUseUpBars(); + $downBars = $plotArea->getUseDownBars(); + if ($gapWidth !== null || $upBars || $downBars) { + $objWriter->startElement('c:upDownBars'); + if ($gapWidth !== null) { + $objWriter->startElement('c:gapWidth'); + $objWriter->writeAttribute('val', "$gapWidth"); + $objWriter->endElement(); + } + if ($upBars) { + $objWriter->startElement('c:upBars'); + $objWriter->endElement(); + } + if ($downBars) { + $objWriter->startElement('c:downBars'); + $objWriter->endElement(); + } + $objWriter->endElement(); // c:upDownBars + } } // Generate 3 unique numbers to use for axId values