diff --git a/CHANGELOG.md b/CHANGELOG.md index 9148425d..b7d55bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Time interval formatting [Issue #2768](https://github.com/PHPOffice/PhpSpreadsheet/issues/2768) [PR #2772](https://github.com/PHPOffice/PhpSpreadsheet/pull/2772) - Copy from Xls(x) to Html/Pdf loses drawings [PR #2788](https://github.com/PHPOffice/PhpSpreadsheet/pull/2788) - Html Reader converting cell containing 0 to null string [Issue #2810](https://github.com/PHPOffice/PhpSpreadsheet/issues/2810) [PR #2813](https://github.com/PHPOffice/PhpSpreadsheet/pull/2813) -- Many fixes for Charts, especially, but not limited to, Scatter, Bubble, and Surface charts. [Issue #2762](https://github.com/PHPOffice/PhpSpreadsheet/issues/2762) [Issue #2299](https://github.com/PHPOffice/PhpSpreadsheet/issues/2299) [Issue #2700](https://github.com/PHPOffice/PhpSpreadsheet/issues/2700) [Issue #2817](https://github.com/PHPOffice/PhpSpreadsheet/issues/2817) [Issue #2763](https://github.com/PHPOffice/PhpSpreadsheet/issues/2763) [Issue #2219](https://github.com/PHPOffice/PhpSpreadsheet/issues/2219) [PR #2828](https://github.com/PHPOffice/PhpSpreadsheet/pull/2828) [PR #2841](https://github.com/PHPOffice/PhpSpreadsheet/pull/2841) [PR #2846](https://github.com/PHPOffice/PhpSpreadsheet/pull/2846) [PR #2852](https://github.com/PHPOffice/PhpSpreadsheet/pull/2852) [PR #2856](https://github.com/PHPOffice/PhpSpreadsheet/pull/2856) [PR #2865](https://github.com/PHPOffice/PhpSpreadsheet/pull/2865) [PR #2872](https://github.com/PHPOffice/PhpSpreadsheet/pull/2872) [PR #2879](https://github.com/PHPOffice/PhpSpreadsheet/pull/2879) +- Many fixes for Charts, especially, but not limited to, Scatter, Bubble, and Surface charts. [Issue #2762](https://github.com/PHPOffice/PhpSpreadsheet/issues/2762) [Issue #2299](https://github.com/PHPOffice/PhpSpreadsheet/issues/2299) [Issue #2700](https://github.com/PHPOffice/PhpSpreadsheet/issues/2700) [Issue #2817](https://github.com/PHPOffice/PhpSpreadsheet/issues/2817) [Issue #2763](https://github.com/PHPOffice/PhpSpreadsheet/issues/2763) [Issue #2219](https://github.com/PHPOffice/PhpSpreadsheet/issues/2219) [Issue #2863](https://github.com/PHPOffice/PhpSpreadsheet/issues/2863) [PR #2828](https://github.com/PHPOffice/PhpSpreadsheet/pull/2828) [PR #2841](https://github.com/PHPOffice/PhpSpreadsheet/pull/2841) [PR #2846](https://github.com/PHPOffice/PhpSpreadsheet/pull/2846) [PR #2852](https://github.com/PHPOffice/PhpSpreadsheet/pull/2852) [PR #2856](https://github.com/PHPOffice/PhpSpreadsheet/pull/2856) [PR #2865](https://github.com/PHPOffice/PhpSpreadsheet/pull/2865) [PR #2872](https://github.com/PHPOffice/PhpSpreadsheet/pull/2872) [PR #2879](https://github.com/PHPOffice/PhpSpreadsheet/pull/2879) [PR #2898](https://github.com/PHPOffice/PhpSpreadsheet/pull/2898) [PR #2906](https://github.com/PHPOffice/PhpSpreadsheet/pull/2906) - Calculating Engine regexp for Column/Row references when there are multiple quoted worksheet references in the formula [Issue #2874](https://github.com/PHPOffice/PhpSpreadsheet/issues/2874) [PR #2899](https://github.com/PHPOffice/PhpSpreadsheet/pull/2899) ## 1.23.0 - 2022-04-24 diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d1f2a03b..858502fd 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4055,16 +4055,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Writer/Xlsx.php - - - message: "#^Cannot call method getDataValues\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php - - - - message: "#^Cannot call method getFillColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php - - message: "#^Parameter \\#1 \\$plotSeriesValues of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeBubbles\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|null, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false given\\.$#" count: 1 @@ -4087,7 +4077,7 @@ parameters: - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" - count: 42 + count: 41 path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php - diff --git a/samples/Chart/33_Chart_create_bar_custom_colors.php b/samples/Chart/33_Chart_create_bar_custom_colors.php new file mode 100644 index 00000000..75f2306b --- /dev/null +++ b/samples/Chart/33_Chart_create_bar_custom_colors.php @@ -0,0 +1,183 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Custom colors for dataSeries (gray, blue, red, orange) +$colors = [ + 'cccccc', '00abb8', 'b8292f', 'eb8500', +]; + +// 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 +$dataSeriesLabels1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// 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 +// Custom colors +$dataSeriesValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4, [], null, $colors), +]; + +// Build the dataseries +$series1 = new DataSeries( + DataSeries::TYPE_BARCHART, // plotType + null, // plotGrouping (Pie charts don't have any grouping) + range(0, count($dataSeriesValues1) - 1), // plotOrder + $dataSeriesLabels1, // plotLabel + $xAxisTickValues1, // plotCategory + $dataSeriesValues1 // plotValues +); + +// Set up a layout object for the Pie chart +$layout1 = new Layout(); +$layout1->setShowVal(true); +$layout1->setShowPercent(true); + +// Set the series in the plot area +$plotArea1 = new PlotArea($layout1, [$series1]); +// Set the chart legend +$legend1 = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); + +$title1 = new Title('Test Bar Chart'); + +// Create the chart +$chart1 = new Chart( + 'chart1', // name + $title1, // title + $legend1, // legend + $plotArea1, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + null // yAxisLabel - Pie charts don't have a Y-Axis +); + +// Set the position where the chart should appear in the worksheet +$chart1->setTopLeftPosition('A7'); +$chart1->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart1); + +// 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 +$dataSeriesLabels2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// 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 +// Custom colors +$dataSeriesValues2 = [ + $dataSeriesValues2Element = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), +]; +$dataSeriesValues2Element->setFillColor($colors); + +// Build the dataseries +$series2 = new DataSeries( + DataSeries::TYPE_DONUTCHART, // plotType + null, // plotGrouping (Donut charts don't have any grouping) + range(0, count($dataSeriesValues2) - 1), // plotOrder + $dataSeriesLabels2, // plotLabel + $xAxisTickValues2, // plotCategory + $dataSeriesValues2 // plotValues +); + +// Set up a layout object for the Pie chart +$layout2 = new Layout(); +$layout2->setShowVal(true); +$layout2->setShowCatName(true); + +// Set the series in the plot area +$plotArea2 = new PlotArea($layout2, [$series2]); + +$title2 = new Title('Test Donut Chart'); + +// Create the chart +$chart2 = new Chart( + 'chart2', // name + $title2, // title + null, // legend + $plotArea2, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + null // yAxisLabel - Like Pie charts, Donut charts don't have a Y-Axis +); + +// Set the position where the chart should appear in the worksheet +$chart2->setTopLeftPosition('I7'); +$chart2->setBottomRightPosition('P20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart2); + +// 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/Chart/33_Chart_create_line.php b/samples/Chart/33_Chart_create_line.php index fee2a284..8cb87e30 100644 --- a/samples/Chart/33_Chart_create_line.php +++ b/samples/Chart/33_Chart_create_line.php @@ -5,6 +5,7 @@ use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; +use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Title; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -35,6 +36,7 @@ $dataSeriesLabels = [ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 ]; +$dataSeriesLabels[0]->setFillColor('FF0000'); // Set the X-Axis Labels // Datatype // Cell reference for data @@ -57,7 +59,7 @@ $dataSeriesValues = [ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), ]; -$dataSeriesValues[2]->setLineWidth(60000); +$dataSeriesValues[2]->setLineWidth(60000 / Properties::POINTS_WIDTH_MULTIPLIER); // Build the dataseries $series = new DataSeries( diff --git a/samples/Chart/33_Chart_create_scatter2.php b/samples/Chart/33_Chart_create_scatter2.php index 1d6e331c..ef6353bb 100644 --- a/samples/Chart/33_Chart_create_scatter2.php +++ b/samples/Chart/33_Chart_create_scatter2.php @@ -2,6 +2,7 @@ use PhpOffice\PhpSpreadsheet\Chart\Axis; use PhpOffice\PhpSpreadsheet\Chart\Chart; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend; @@ -64,11 +65,76 @@ $dataSeriesValues = [ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', Properties::FORMAT_CODE_NUMBER, 4), new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', Properties::FORMAT_CODE_NUMBER, 4), ]; + +// series 1 +// marker details +$dataSeriesValues[0] + ->setPointMarker('diamond') + ->setPointSize(5) + ->getMarkerFillColor() + ->setColorProperties('0070C0', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[0] + ->getMarkerBorderColor() + ->setColorProperties('002060', null, ChartColor::EXCEL_COLOR_TYPE_RGB); + +// line details - smooth line, connected +$dataSeriesValues[0] + ->setScatterLines(true) + ->setSmoothLine(true) + ->setLineColorProperties('accent1', 40, ChartColor::EXCEL_COLOR_TYPE_SCHEME); // value, alpha, type +$dataSeriesValues[0]->setLineStyleProperties( + 2.5, // width in points + Properties::LINE_STYLE_COMPOUND_TRIPLE, // compound + Properties::LINE_STYLE_DASH_SQUARE_DOT, // dash + Properties::LINE_STYLE_CAP_SQUARE, // cap + Properties::LINE_STYLE_JOIN_MITER, // join + Properties::LINE_STYLE_ARROW_TYPE_OPEN, // head type + Properties::LINE_STYLE_ARROW_SIZE_4, // head size preset index + Properties::LINE_STYLE_ARROW_TYPE_ARROW, // end type + Properties::LINE_STYLE_ARROW_SIZE_6 // end size preset index +); + +// series 2 - straight line - no special effects, connected, straight line +$dataSeriesValues[1] // square fill + ->setPointMarker('square') + ->setPointSize(6) + ->getMarkerBorderColor() + ->setColorProperties('accent6', 3, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[1] // square border + ->getMarkerFillColor() + ->setColorProperties('0FFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[1] + ->setScatterLines(true) + ->setSmoothLine(false) + ->setLineColorProperties('FF0000', 80, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[1]->setLineWidth(2.0); + +// series 3 - markers, no line +$dataSeriesValues[2] // triangle fill + //->setPointMarker('triangle') // let Excel choose shape + ->setPointSize(7) + ->getMarkerFillColor() + ->setColorProperties('FFFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[2] // triangle border + ->getMarkerBorderColor() + ->setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[2]->setScatterLines(false); // points not connected + // Added so that Xaxis shows dates instead of Excel-equivalent-year1900-numbers $xAxis = new Axis(); //$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE ); $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, true); +$yAxis = new Axis(); +$yAxis->setLineStyleProperties( + 2.5, // width in points + Properties::LINE_STYLE_COMPOUND_SIMPLE, + Properties::LINE_STYLE_DASH_DASH_DOT, + Properties::LINE_STYLE_CAP_FLAT, + Properties::LINE_STYLE_JOIN_BEVEL +); +$yAxis->setLineColorProperties('ffc000', null, ChartColor::EXCEL_COLOR_TYPE_RGB); + // Build the dataseries $series = new DataSeries( DataSeries::TYPE_SCATTERCHART, // plotType @@ -79,8 +145,7 @@ $series = new DataSeries( $dataSeriesValues, // plotValues null, // plotDirection false, // smooth line - //DataSeries::STYLE_LINEMARKER // plotStyle - DataSeries::STYLE_MARKER // plotStyle + DataSeries::STYLE_SMOOTHMARKER // plotStyle ); // Set the series in the plot area @@ -103,6 +168,7 @@ $chart = new Chart( $yAxisLabel, // yAxisLabel // added xAxis for correct date display $xAxis, // xAxis + $yAxis, // yAxis ); // Set the position where the chart should appear in the worksheet diff --git a/samples/templates/32readwriteScatterChart8.xlsx b/samples/templates/32readwriteScatterChart8.xlsx new file mode 100644 index 00000000..fdd85b0e Binary files /dev/null and b/samples/templates/32readwriteScatterChart8.xlsx differ diff --git a/src/PhpSpreadsheet/Chart/Axis.php b/src/PhpSpreadsheet/Chart/Axis.php index 69a25d92..72438891 100644 --- a/src/PhpSpreadsheet/Chart/Axis.php +++ b/src/PhpSpreadsheet/Chart/Axis.php @@ -27,6 +27,9 @@ class Axis extends Properties 'numeric' => null, ]; + /** @var string */ + private $axisType = ''; + /** * Axis Options. * @@ -62,11 +65,11 @@ class Axis extends Properties * * @param mixed $format_code */ - public function setAxisNumberProperties($format_code, ?bool $numeric = null): void + public function setAxisNumberProperties($format_code, ?bool $numeric = null, int $sourceLinked = 0): void { $format = (string) $format_code; $this->axisNumber['format'] = $format; - $this->axisNumber['source_linked'] = 0; + $this->axisNumber['source_linked'] = $sourceLinked; if (is_bool($numeric)) { $this->axisNumber['numeric'] = $numeric; } elseif (in_array($format, self::NUMERIC_FORMAT, true)) { @@ -156,6 +159,22 @@ class Axis extends Properties $this->axisOptions['orientation'] = (string) $orientation; } + public function getAxisType(): string + { + return $this->axisType; + } + + public function setAxisType(string $type): self + { + if ($type === 'catAx' || $type === 'valAx') { + $this->axisType = $type; + } else { + $this->axisType = ''; + } + + return $this; + } + /** * Set Fill Property. * diff --git a/src/PhpSpreadsheet/Chart/ChartColor.php b/src/PhpSpreadsheet/Chart/ChartColor.php index 05c1bb9a..7f87e391 100644 --- a/src/PhpSpreadsheet/Chart/ChartColor.php +++ b/src/PhpSpreadsheet/Chart/ChartColor.php @@ -6,6 +6,8 @@ class ChartColor { const EXCEL_COLOR_TYPE_STANDARD = 'prstClr'; const EXCEL_COLOR_TYPE_SCHEME = 'schemeClr'; + const EXCEL_COLOR_TYPE_RGB = 'srgbClr'; + /** @deprecated 1.24 use EXCEL_COLOR_TYPE_RGB instead */ const EXCEL_COLOR_TYPE_ARGB = 'srgbClr'; const EXCEL_COLOR_TYPES = [ self::EXCEL_COLOR_TYPE_ARGB, @@ -22,6 +24,18 @@ class ChartColor /** @var ?int */ private $alpha; + /** + * @param string|string[] $value + */ + public function __construct($value = '', ?int $alpha = null, ?string $type = null) + { + if (is_array($value)) { + $this->setColorPropertiesArray($value); + } else { + $this->setColorProperties($value, $alpha, $type); + } + } + public function getValue(): string { return $this->value; @@ -61,10 +75,21 @@ class ChartColor /** * @param null|float|int|string $alpha */ - public function setColorProperties(?string $color, $alpha, ?string $type): self + public function setColorProperties(?string $color, $alpha = null, ?string $type = null): self { + if (empty($type) && !empty($color)) { + if (substr($color, 0, 1) === '*') { + $type = 'schemeClr'; + $color = substr($color, 1); + } elseif (substr($color, 0, 1) === '/') { + $type = 'prstClr'; + $color = substr($color, 1); + } elseif (preg_match('/^[0-9A-Fa-f]{6}$/', $color) === 1) { + $type = 'srgbClr'; + } + } if ($color !== null) { - $this->setValue($color); + $this->setValue("$color"); } if ($type !== null) { $this->setType($type); @@ -80,21 +105,16 @@ class ChartColor public function setColorPropertiesArray(array $color): self { - if (array_key_exists('value', $color) && is_string($color['value'])) { - $this->setValue($color['value']); - } - if (array_key_exists('type', $color) && is_string($color['type'])) { - $this->setType($color['type']); - } - if (array_key_exists('alpha', $color)) { - if ($color['alpha'] === null) { - $this->setAlpha(null); - } elseif (is_numeric($color['alpha'])) { - $this->setAlpha((int) $color['alpha']); - } - } + return $this->setColorProperties( + $color['value'] ?? '', + $color['alpha'] ?? null, + $color['type'] ?? null + ); + } - return $this; + public function isUsable(): bool + { + return $this->type !== '' && $this->value !== ''; } /** diff --git a/src/PhpSpreadsheet/Chart/DataSeries.php b/src/PhpSpreadsheet/Chart/DataSeries.php index dca1186e..d27db33e 100644 --- a/src/PhpSpreadsheet/Chart/DataSeries.php +++ b/src/PhpSpreadsheet/Chart/DataSeries.php @@ -257,8 +257,6 @@ class DataSeries $keys = array_keys($this->plotLabel); if (in_array($index, $keys)) { return $this->plotLabel[$index]; - } elseif (isset($keys[$index])) { - return $this->plotLabel[$keys[$index]]; } return false; @@ -339,8 +337,6 @@ class DataSeries $keys = array_keys($this->plotValues); if (in_array($index, $keys)) { return $this->plotValues[$index]; - } elseif (isset($keys[$index])) { - return $this->plotValues[$keys[$index]]; } return false; diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php index 0a2f5a85..d6a8dcca 100644 --- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -7,7 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -class DataSeriesValues +class DataSeriesValues extends Properties { const DATASERIES_TYPE_STRING = 'String'; const DATASERIES_TYPE_NUMBER = 'Number'; @@ -45,6 +45,12 @@ class DataSeriesValues */ private $pointMarker; + /** @var ChartColor */ + private $markerFillColor; + + /** @var ChartColor */ + private $markerBorderColor; + /** * Series Point Size. * @@ -69,23 +75,10 @@ class DataSeriesValues /** * Fill color (can be array with colors if dataseries have custom colors). * - * @var null|string|string[] + * @var null|ChartColor|ChartColor[] */ private $fillColor; - /** @var string */ - private $schemeClr = ''; - - /** @var string */ - private $prstClr = ''; - - /** - * Line Width. - * - * @var int - */ - private $lineWidth = 12700; - /** @var bool */ private $scatterLines = true; @@ -101,18 +94,23 @@ class DataSeriesValues * @param int $pointCount * @param mixed $dataValues * @param null|mixed $marker - * @param null|string|string[] $fillColor + * @param null|ChartColor|ChartColor[]|string|string[] $fillColor * @param string $pointSize */ public function __construct($dataType = self::DATASERIES_TYPE_NUMBER, $dataSource = null, $formatCode = null, $pointCount = 0, $dataValues = [], $marker = null, $fillColor = null, $pointSize = '3') { + parent::__construct(); + $this->markerFillColor = new ChartColor(); + $this->markerBorderColor = new ChartColor(); $this->setDataType($dataType); $this->dataSource = $dataSource; $this->formatCode = $formatCode; $this->pointCount = $pointCount; $this->dataValues = $dataValues; $this->pointMarker = $marker; - $this->fillColor = $fillColor; + if ($fillColor !== null) { + $this->setFillColor($fillColor); + } if (is_numeric($pointSize)) { $this->pointSize = (int) $pointSize; } @@ -198,6 +196,16 @@ class DataSeriesValues return $this; } + public function getMarkerFillColor(): ChartColor + { + return $this->markerFillColor; + } + + public function getMarkerBorderColor(): ChartColor + { + return $this->markerBorderColor; + } + /** * Get Point Size. */ @@ -252,37 +260,96 @@ class DataSeriesValues return $this->pointCount; } + /** + * Get fill color object. + * + * @return null|ChartColor|ChartColor[] + */ + public function getFillColorObject() + { + return $this->fillColor; + } + + private function stringToChartColor(string $fillString): ChartColor + { + $value = $type = ''; + if (substr($fillString, 0, 1) === '*') { + $type = 'schemeClr'; + $value = substr($fillString, 1); + } elseif (substr($fillString, 0, 1) === '/') { + $type = 'prstClr'; + $value = substr($fillString, 1); + } elseif ($fillString !== '') { + $type = 'srgbClr'; + $value = $fillString; + $this->validateColor($value); + } + + return new ChartColor($value, null, $type); + } + + private function chartColorToString(ChartColor $chartColor): string + { + $type = (string) $chartColor->getColorProperty('type'); + $value = (string) $chartColor->getColorProperty('value'); + if ($type === '' || $value === '') { + return ''; + } + if ($type === 'schemeClr') { + return "*$value"; + } + if ($type === 'prstClr') { + return "/$value"; + } + + return $value; + } + /** * Get fill color. * - * @return null|string|string[] HEX color or array with HEX colors + * @return string|string[] HEX color or array with HEX colors */ public function getFillColor() { - return $this->fillColor; + if ($this->fillColor === null) { + return ''; + } + if (is_array($this->fillColor)) { + $array = []; + foreach ($this->fillColor as $chartColor) { + $array[] = self::chartColorToString($chartColor); + } + + return $array; + } + + return self::chartColorToString($this->fillColor); } /** * Set fill color for series. * - * @param string|string[] $color HEX color or array with HEX colors + * @param ChartColor|ChartColor[]|string|string[] $color HEX color or array with HEX colors * * @return DataSeriesValues */ public function setFillColor($color) { if (is_array($color)) { - foreach ($color as $colorValue) { - if (substr($colorValue, 0, 1) !== '*' && substr($colorValue, 0, 1) !== '/') { - $this->validateColor($colorValue); + $this->fillColor = []; + foreach ($color as $fillString) { + if ($fillString instanceof ChartColor) { + $this->fillColor[] = $fillString; + } else { + $this->fillColor[] = self::stringToChartColor($fillString); } } - } else { - if (substr($color, 0, 1) !== '*' && substr($color, 0, 1) !== '/') { - $this->validateColor("$color"); - } + } elseif ($color instanceof ChartColor) { + $this->fillColor = $color; + } elseif (is_string($color)) { + $this->fillColor = self::stringToChartColor($color); } - $this->fillColor = $color; return $this; } @@ -306,24 +373,23 @@ class DataSeriesValues /** * Get line width for series. * - * @return int + * @return null|float|int */ public function getLineWidth() { - return $this->lineWidth; + return $this->lineStyleProperties['width']; } /** * Set line width for the series. * - * @param int $width + * @param null|float|int $width * * @return $this */ public function setLineWidth($width) { - $minWidth = 12700; - $this->lineWidth = max($minWidth, $width); + $this->lineStyleProperties['width'] = $width; return $this; } @@ -466,26 +532,33 @@ class DataSeriesValues return $this; } - public function getSchemeClr(): string + /** + * Smooth Line. + * + * @var bool + */ + private $smoothLine; + + /** + * Get Smooth Line. + * + * @return bool + */ + public function getSmoothLine() { - return $this->schemeClr; + return $this->smoothLine; } - public function setSchemeClr(string $schemeClr): self + /** + * Set Smooth Line. + * + * @param bool $smoothLine + * + * @return $this + */ + public function setSmoothLine($smoothLine) { - $this->schemeClr = $schemeClr; - - return $this; - } - - public function getPrstClr(): string - { - return $this->prstClr; - } - - public function setPrstClr(string $prstClr): self - { - $this->prstClr = $prstClr; + $this->smoothLine = $smoothLine; return $this; } diff --git a/src/PhpSpreadsheet/Chart/Properties.php b/src/PhpSpreadsheet/Chart/Properties.php index a64a826f..fdc3c12b 100644 --- a/src/PhpSpreadsheet/Chart/Properties.php +++ b/src/PhpSpreadsheet/Chart/Properties.php @@ -59,6 +59,8 @@ abstract class Properties const LINE_STYLE_COMPOUND_TRIPLE = 'tri'; const LINE_STYLE_DASH_SOLID = 'solid'; const LINE_STYLE_DASH_ROUND_DOT = 'sysDot'; + const LINE_STYLE_DASH_SQUARE_DOT = 'sysDash'; + /** @deprecated 1.24 use LINE_STYLE_DASH_SQUARE_DOT instead */ const LINE_STYLE_DASH_SQUERE_DOT = 'sysDash'; const LINE_STYPE_DASH_DASH = 'dash'; const LINE_STYLE_DASH_DASH_DOT = 'dashDot'; @@ -68,7 +70,7 @@ abstract class Properties const LINE_STYLE_CAP_SQUARE = 'sq'; const LINE_STYLE_CAP_ROUND = 'rnd'; const LINE_STYLE_CAP_FLAT = 'flat'; - const LINE_STYLE_JOIN_ROUND = 'bevel'; + const LINE_STYLE_JOIN_ROUND = 'round'; const LINE_STYLE_JOIN_MITER = 'miter'; const LINE_STYLE_JOIN_BEVEL = 'bevel'; const LINE_STYLE_ARROW_TYPE_NOARROW = null; @@ -643,30 +645,6 @@ abstract class Properties return $this; } - /** - * Set Shadow Color. - * - * @param string $color - * @param int $alpha - * @param string $colorType - * - * @return $this - */ - protected function setShadowColor($color, $alpha, $colorType) - { - if ($color !== null) { - $this->shadowProperties['color']['value'] = (string) $color; - } - if ($alpha !== null) { - $this->shadowProperties['color']['alpha'] = (int) $alpha; - } - if ($colorType !== null) { - $this->shadowProperties['color']['type'] = (string) $colorType; - } - - return $this; - } - /** * Set Shadow Blur. * @@ -766,6 +744,12 @@ abstract class Properties ], ]; + public function copyLineStyles(self $otherProperties): void + { + $this->lineStyleProperties = $otherProperties->lineStyleProperties; + $this->lineColor = $otherProperties->lineColor; + } + public function getLineColor(): ChartColor { return $this->lineColor; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index 2c060d07..df25dac7 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -14,7 +14,6 @@ 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; use PhpOffice\PhpSpreadsheet\Style\Font; use SimpleXMLElement; @@ -90,6 +89,7 @@ class Chart case 'plotArea': $plotAreaLayout = $XaxisLabel = $YaxisLabel = null; $plotSeries = $plotAttributes = []; + $catAxRead = false; foreach ($chartDetails as $chartDetailKey => $chartDetail) { switch ($chartDetailKey) { case 'layout': @@ -97,9 +97,11 @@ class Chart break; case 'catAx': + $catAxRead = true; if (isset($chartDetail->title)) { $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); } + $xAxis->setAxisType('catAx'); $this->readEffects($chartDetail, $xAxis); if (isset($chartDetail->spPr)) { $sppr = $chartDetail->spPr->children($this->aNamespace); @@ -122,16 +124,22 @@ class Chart $axPos = null; if (isset($chartDetail->axPos)) { $axPos = self::getAttribute($chartDetail->axPos, 'val', 'string'); - + } + if ($catAxRead) { + $whichAxis = $yAxis; + $yAxis->setAxisType($chartDetailKey); + } elseif (!empty($axPos)) { switch ($axPos) { case 't': case 'b': $whichAxis = $xAxis; + $xAxis->setAxisType($chartDetailKey); break; case 'r': case 'l': $whichAxis = $yAxis; + $yAxis->setAxisType($chartDetailKey); break; } @@ -373,14 +381,14 @@ class Chart case 'ser': $marker = null; $seriesIndex = ''; - $srgbClr = null; - $lineWidth = null; + $fillColor = null; $pointSize = null; $noFill = false; - $schemeClr = ''; - $prstClr = ''; $bubble3D = false; $dPtColors = []; + $markerFillColor = null; + $markerBorderColor = null; + $lineStyle = null; foreach ($seriesDetails as $seriesKey => $seriesDetail) { switch ($seriesKey) { case 'idx': @@ -399,12 +407,16 @@ class Chart case 'spPr': $children = $seriesDetail->children($this->aNamespace); $ln = $children->ln; - $lineWidth = self::getAttribute($ln, 'w', 'string'); - if (is_countable($ln->noFill) && count($ln->noFill) === 1) { - $noFill = true; + if (isset($children->ln)) { + $ln = $children->ln; + if (is_countable($ln->noFill) && count($ln->noFill) === 1) { + $noFill = true; + } + $lineStyle = new GridLines(); + $this->readLineStyle($seriesDetails, $lineStyle); } if (isset($children->solidFill)) { - $this->readColor($children->solidFill, $srgbClr, $schemeClr, $prstClr); + $fillColor = new ChartColor($this->readColor($children->solidFill)); } break; @@ -414,13 +426,7 @@ class Chart $children = $seriesDetail->spPr->children($this->aNamespace); if (isset($children->solidFill)) { $arrayColors = $this->readColor($children->solidFill); - if ($arrayColors['type'] === 'srgbClr') { - $dptColors[$dptIdx] = $arrayColors['value']; - } elseif ($arrayColors['type'] === 'prstClr') { - $dptColors[$dptIdx] = '/' . $arrayColors['value']; - } else { - $dptColors[$dptIdx] = '*' . $arrayColors['value']; - } + $dptColors[$dptIdx] = new ChartColor($arrayColors); } } @@ -429,10 +435,13 @@ class Chart $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string'); $pointSize = self::getAttribute($seriesDetail->size, 'val', 'string'); $pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null; - if (count($seriesDetail->spPr) === 1) { - $ln = $seriesDetail->spPr->children($this->aNamespace); - if (isset($ln->solidFill)) { - $this->readColor($ln->solidFill, $srgbClr, $schemeClr, $prstClr); + if (isset($seriesDetail->spPr)) { + $children = $seriesDetail->spPr->children($this->aNamespace); + if (isset($children->solidFill)) { + $markerFillColor = $this->readColor($children->solidFill); + } + if (isset($children->ln->solidFill)) { + $markerBorderColor = $this->readColor($children->ln->solidFill); } } @@ -446,19 +455,19 @@ class Chart break; case 'val': - $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize"); + $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'xVal': - $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize"); + $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'yVal': - $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize"); + $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'bubbleSize': - $seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize"); + $seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'bubble3D': @@ -478,36 +487,15 @@ class Chart $seriesValues[$seriesIndex]->setScatterLines(false); } } - if (is_numeric($lineWidth)) { + if ($lineStyle !== null) { if (isset($seriesLabel[$seriesIndex])) { - $seriesLabel[$seriesIndex]->setLineWidth((int) $lineWidth); + $seriesLabel[$seriesIndex]->copyLineStyles($lineStyle); } if (isset($seriesCategory[$seriesIndex])) { - $seriesCategory[$seriesIndex]->setLineWidth((int) $lineWidth); + $seriesCategory[$seriesIndex]->copyLineStyles($lineStyle); } if (isset($seriesValues[$seriesIndex])) { - $seriesValues[$seriesIndex]->setLineWidth((int) $lineWidth); - } - } - if ($schemeClr) { - if (isset($seriesLabel[$seriesIndex])) { - $seriesLabel[$seriesIndex]->setSchemeClr($schemeClr); - } - if (isset($seriesCategory[$seriesIndex])) { - $seriesCategory[$seriesIndex]->setSchemeClr($schemeClr); - } - if (isset($seriesValues[$seriesIndex])) { - $seriesValues[$seriesIndex]->setSchemeClr($schemeClr); - } - } elseif ($prstClr) { - if (isset($seriesLabel[$seriesIndex])) { - $seriesLabel[$seriesIndex]->setPrstClr($prstClr); - } - if (isset($seriesCategory[$seriesIndex])) { - $seriesCategory[$seriesIndex]->setPrstClr($prstClr); - } - if (isset($seriesValues[$seriesIndex])) { - $seriesValues[$seriesIndex]->setPrstClr($prstClr); + $seriesValues[$seriesIndex]->copyLineStyles($lineStyle); } } if ($bubble3D) { @@ -532,6 +520,39 @@ class Chart $seriesValues[$seriesIndex]->setFillColor($dptColors); } } + if ($markerFillColor !== null) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor); + } + } + if ($markerBorderColor !== null) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor); + } + } + if ($smoothLine) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setSmoothLine(true); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setSmoothLine(true); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setSmoothLine(true); + } + } } } /** @phpstan-ignore-next-line */ @@ -544,11 +565,11 @@ class Chart /** * @return mixed */ - private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?string $srgbClr = null, ?string $pointSize = null) + private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?ChartColor $fillColor = null, ?string $pointSize = null) { if (isset($seriesDetail->strRef)) { $seriesSource = (string) $seriesDetail->strRef->f; - $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $srgbClr, "$pointSize"); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->strRef->strCache)) { $seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's'); @@ -560,7 +581,7 @@ class Chart return $seriesValues; } elseif (isset($seriesDetail->numRef)) { $seriesSource = (string) $seriesDetail->numRef->f; - $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $srgbClr, "$pointSize"); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->numRef->numCache)) { $seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace)); $seriesValues @@ -571,7 +592,7 @@ class Chart return $seriesValues; } elseif (isset($seriesDetail->multiLvlStrRef)) { $seriesSource = (string) $seriesDetail->multiLvlStrRef->f; - $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $srgbClr, "$pointSize"); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) { $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's'); @@ -583,7 +604,7 @@ class Chart return $seriesValues; } elseif (isset($seriesDetail->multiLvlNumRef)) { $seriesSource = (string) $seriesDetail->multiLvlNumRef->f; - $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $srgbClr, "$pointSize"); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) { $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's'); @@ -698,8 +719,7 @@ class Chart $defaultLatin = null; $defaultEastAsian = null; $defaultComplexScript = null; - $defaultSrgbColor = ''; - $defaultSchemeColor = ''; + $defaultFontColor = null; if (isset($titleDetailPart->pPr->defRPr)) { /** @var ?int */ $defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer'); @@ -729,7 +749,7 @@ class Chart $defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string'); } if (isset($titleDetailPart->pPr->defRPr->solidFill)) { - $this->readColor($titleDetailPart->pPr->defRPr->solidFill, $defaultSrgbColor, $defaultSchemeClr); + $defaultFontColor = $this->readColor($titleDetailPart->pPr->defRPr->solidFill); } } foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) { @@ -755,8 +775,7 @@ class Chart $latinName = null; $eastAsian = null; $complexScript = null; - $fontSrgbClr = ''; - $fontSchemeClr = ''; + $fontColor = null; $underlineColor = null; if (isset($titleDetailElement->rPr)) { // not used now, not sure it ever was, grandfathering @@ -781,10 +800,8 @@ class Chart $fontSize = self::getAttribute($titleDetailElement->rPr, 'sz', 'integer'); // not used now, not sure it ever was, grandfathering - /** @var ?string */ - $fontSrgbClr = self::getAttribute($titleDetailElement->rPr, 'color', 'string'); if (isset($titleDetailElement->rPr->solidFill)) { - $this->readColor($titleDetailElement->rPr->solidFill, $fontSrgbClr, $fontSchemeClr); + $fontColor = $this->readColor($titleDetailElement->rPr->solidFill); } /** @var ?bool */ @@ -834,19 +851,15 @@ class Chart if (is_int($fontSize)) { $objText->getFont()->setSize(floor($fontSize / 100)); $fontFound = true; + } else { + $objText->getFont()->setSize(null, true); } - $fontSrgbClr = $fontSrgbClr ?? $defaultSrgbColor; - if (!empty($fontSrgbClr)) { - $objText->getFont()->setColor(new Color($fontSrgbClr)); + $fontColor = $fontColor ?? $defaultFontColor; + if (!empty($fontColor)) { + $objText->getFont()->setChartColor($fontColor); $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) { @@ -1059,7 +1072,7 @@ class Chart 'innerShdw', ]; - private function readColor(SimpleXMLElement $colorXml, ?string &$srgbClr = null, ?string &$schemeClr = null, ?string &$prstClr = null): array + private function readColor(SimpleXMLElement $colorXml): array { $result = [ 'type' => null, @@ -1070,13 +1083,6 @@ class Chart if (isset($colorXml->$type)) { $result['type'] = $type; $result['value'] = self::getAttribute($colorXml->$type, 'val', 'string'); - if ($type === Properties::EXCEL_COLOR_TYPE_ARGB) { - $srgbClr = $result['value']; - } elseif ($type === Properties::EXCEL_COLOR_TYPE_SCHEME) { - $schemeClr = $result['value']; - } elseif ($type === Properties::EXCEL_COLOR_TYPE_STANDARD) { - $prstClr = $result['value']; - } if (isset($colorXml->$type->alpha)) { /** @var string */ $alpha = self::getAttribute($colorXml->$type->alpha, 'val', 'string'); @@ -1092,10 +1098,7 @@ class Chart return $result; } - /** - * @param null|GridLines $chartObject may be extended to include other types - */ - private function readLineStyle(SimpleXMLElement $chartDetail, $chartObject): void + private function readLineStyle(SimpleXMLElement $chartDetail, ?Properties $chartObject): void { if (!isset($chartObject, $chartDetail->spPr)) { return; @@ -1164,6 +1167,13 @@ class Chart if (!isset($whichAxis)) { return; } + if (isset($chartDetail->numFmt)) { + $whichAxis->setAxisNumberProperties( + (string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'), + null, + (int) self::getAttribute($chartDetail->numFmt, 'sourceLinked', 'int') + ); + } if (isset($chartDetail->crossBetween)) { $whichAxis->setCrossBetween((string) self::getAttribute($chartDetail->crossBetween, 'val', 'string')); } diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php index 4dbe7272..19d67563 100644 --- a/src/PhpSpreadsheet/Style/Font.php +++ b/src/PhpSpreadsheet/Style/Font.php @@ -21,7 +21,7 @@ class Font extends Supervisor protected $name = 'Calibri'; /** - * The following 6 are used only for chart titles, I think. + * The following 7 are used only for chart titles, I think. * *@var string */ @@ -41,6 +41,9 @@ class Font extends Supervisor /** @var ?ChartColor */ private $underlineColor; + + /** @var ?ChartColor */ + private $chartColor; // end of chart title items /** @@ -371,7 +374,7 @@ class Font extends Supervisor * * @return $this */ - public function setSize($sizeInPoints) + public function setSize($sizeInPoints, bool $nullOk = false) { if (is_string($sizeInPoints) || is_int($sizeInPoints)) { $sizeInPoints = (float) $sizeInPoints; // $pValue = 0 if given string is not numeric @@ -380,7 +383,9 @@ class Font extends Supervisor // Size must be a positive floating point number // ECMA-376-1:2016, part 1, chapter 18.4.11 sz (Font Size), p. 1536 if (!is_float($sizeInPoints) || !($sizeInPoints > 0)) { - $sizeInPoints = 10.0; + if (!$nullOk || $sizeInPoints !== null) { + $sizeInPoints = 10.0; + } } if ($this->isSupervisor) { @@ -593,8 +598,7 @@ class Font extends Supervisor public function setUnderlineColor(array $colorArray): self { if (!$this->isSupervisor) { - $this->underlineColor = new ChartColor(); - $this->underlineColor->setColorPropertiesArray($colorArray); + $this->underlineColor = new ChartColor($colorArray); } else { // should never be true // @codeCoverageIgnoreStart @@ -606,6 +610,30 @@ class Font extends Supervisor return $this; } + public function getChartColor(): ?ChartColor + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getChartColor(); + } + + return $this->chartColor; + } + + public function setChartColor(array $colorArray): self + { + if (!$this->isSupervisor) { + $this->chartColor = new ChartColor($colorArray); + } else { + // should never be true + // @codeCoverageIgnoreStart + $styleArray = $this->getStyleArray(['chartColor' => $colorArray]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd + } + + return $this; + } + /** * Get Underline. * @@ -713,6 +741,18 @@ class Font extends Supervisor return $this; } + private function hashChartColor(?ChartColor $underlineColor): string + { + if ($this->underlineColor === null) { + return ''; + } + + return + $this->underlineColor->getValue() + . $this->underlineColor->getType() + . (string) $this->underlineColor->getAlpha(); + } + /** * Get hash code. * @@ -723,14 +763,6 @@ class Font extends Supervisor if ($this->isSupervisor) { return $this->getSharedComponent()->getHashCode(); } - if ($this->underlineColor === null) { - $underlineColor = ''; - } else { - $underlineColor = - $this->underlineColor->getValue() - . $this->underlineColor->getType() - . (string) $this->underlineColor->getAlpha(); - } return md5( $this->name . @@ -749,7 +781,8 @@ class Font extends Supervisor $this->eastAsian, $this->complexScript, $this->strikeType, - $underlineColor, + $this->hashChartColor($this->chartColor), + $this->hashChartColor($this->underlineColor), (string) $this->baseLine, ] ) . @@ -762,6 +795,7 @@ class Font extends Supervisor $exportedArray = []; $this->exportArray2($exportedArray, 'baseLine', $this->getBaseLine()); $this->exportArray2($exportedArray, 'bold', $this->getBold()); + $this->exportArray2($exportedArray, 'chartColor', $this->getChartColor()); $this->exportArray2($exportedArray, 'color', $this->getColor()); $this->exportArray2($exportedArray, 'complexScript', $this->getComplexScript()); $this->exportArray2($exportedArray, 'eastAsian', $this->getEastAsian()); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index acc6f3af..d242e602 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -419,7 +419,9 @@ class Chart extends WriterPart { // N.B. writeCategoryAxis may be invoked with the last parameter($yAxis) using $xAxis for ScatterChart, etc // In that case, xAxis is NOT a category. - if ($yAxis->getAxisIsNumericFormat()) { + if ($yAxis->getAxisType() !== '') { + $objWriter->startElement('c:' . $yAxis->getAxisType()); + } elseif ($yAxis->getAxisIsNumericFormat()) { $objWriter->startElement('c:valAx'); } else { $objWriter->startElement('c:catAx'); @@ -469,10 +471,6 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('a:p'); - $objWriter->startElement('a:pPr'); - $objWriter->startElement('a:defRPr'); - $objWriter->endElement(); - $objWriter->endElement(); $caption = $xAxisLabel->getCaption(); if (is_array($caption)) { @@ -622,7 +620,7 @@ class Chart extends WriterPart $objWriter->startElement('c:majorGridlines'); $objWriter->startElement('c:spPr'); - $this->writeGridlinesLn($objWriter, $majorGridlines); + $this->writeLineStyles($objWriter, $majorGridlines); $objWriter->startElement('a:effectLst'); $this->writeGlow($objWriter, $majorGridlines); @@ -637,7 +635,7 @@ class Chart extends WriterPart $objWriter->startElement('c:minorGridlines'); $objWriter->startElement('c:spPr'); - $this->writeGridlinesLn($objWriter, $minorGridlines); + $this->writeLineStyles($objWriter, $minorGridlines); $objWriter->startElement('a:effectLst'); $this->writeGlow($objWriter, $minorGridlines); @@ -661,10 +659,6 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('a:p'); - $objWriter->startElement('a:pPr'); - $objWriter->startElement('a:defRPr'); - $objWriter->endElement(); - $objWriter->endElement(); $caption = $yAxisLabel->getCaption(); if (is_array($caption)) { @@ -715,7 +709,7 @@ class Chart extends WriterPart $this->writeColor($objWriter, $xAxis->getFillColorObject()); - $this->writeGridlinesLn($objWriter, $xAxis); + $this->writeLineStyles($objWriter, $xAxis); $objWriter->startElement('a:effectLst'); $this->writeGlow($objWriter, $xAxis); @@ -849,40 +843,27 @@ class Chart extends WriterPart /** * Method writing plot series values. - * - * @param int $val value for idx (default: 3) - * @param string $fillColor hex color (default: FF9900) */ - private function writePlotSeriesValuesElement(XMLWriter $objWriter, $val = 3, $fillColor = 'FF9900'): void + private function writePlotSeriesValuesElement(XMLWriter $objWriter, int $val, ?ChartColor $fillColor): void { - if ($fillColor === '') { + if ($fillColor === null || !$fillColor->isUsable()) { return; } $objWriter->startElement('c:dPt'); + $objWriter->startElement('c:idx'); $objWriter->writeAttribute('val', $val); - $objWriter->endElement(); + $objWriter->endElement(); // c:idx $objWriter->startElement('c:bubble3D'); $objWriter->writeAttribute('val', 0); - $objWriter->endElement(); + $objWriter->endElement(); // c:bubble3D $objWriter->startElement('c:spPr'); - $objWriter->startElement('a:solidFill'); - if (substr($fillColor, 0, 1) === '*') { - $objWriter->startElement('a:schemeClr'); - $objWriter->writeAttribute('val', substr($fillColor, 1)); - } elseif (substr($fillColor, 0, 1) === '/') { - $objWriter->startElement('a:prstClr'); - $objWriter->writeAttribute('val', substr($fillColor, 1)); - } else { - $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $fillColor); - } - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); + $this->writeColor($objWriter, $fillColor); + $objWriter->endElement(); // c:spPr + + $objWriter->endElement(); // c:dPt } /** @@ -934,20 +915,6 @@ class Chart extends WriterPart foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) { $objWriter->startElement('c:ser'); - $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx); - if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) { - $fillColor = $plotLabel->getFillColor(); - if ($fillColor !== null && !is_array($fillColor)) { - $objWriter->startElement('c:spPr'); - $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $fillColor); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); - } - } - $objWriter->startElement('c:idx'); $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesIdx); $objWriter->endElement(); @@ -956,22 +923,35 @@ class Chart extends WriterPart $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesRef); $objWriter->endElement(); - // Values - $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef); + $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx); + $labelFill = null; + if ($plotLabel && $groupType === DataSeries::TYPE_LINECHART) { + $labelFill = $plotLabel->getFillColorObject(); + $labelFill = ($labelFill instanceof ChartColor) ? $labelFill : null; + } + if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) { + $fillColor = $plotLabel->getFillColorObject(); + if ($fillColor !== null && !is_array($fillColor) && $fillColor->isUsable()) { + $objWriter->startElement('c:spPr'); + $this->writeColor($objWriter, $fillColor); + $objWriter->endElement(); // c:spPr + } + } - if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) { - $fillColorValues = $plotSeriesValues->getFillColor(); + // Values + $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesIdx); + + if ($plotSeriesValues !== false && in_array($groupType, self::CUSTOM_COLOR_TYPES, true)) { + $fillColorValues = $plotSeriesValues->getFillColorObject(); if ($fillColorValues !== null && is_array($fillColorValues)) { foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) { - $this->writePlotSeriesValuesElement($objWriter, $dataKey, $fillColorValues[$dataKey] ?? ''); + $this->writePlotSeriesValuesElement($objWriter, $dataKey, $fillColorValues[$dataKey] ?? null); } - } else { - $this->writePlotSeriesValuesElement($objWriter); } } // Labels - $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesRef); + $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx); if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) { $objWriter->startElement('c:tx'); $objWriter->startElement('c:strRef'); @@ -982,77 +962,54 @@ class Chart extends WriterPart // Formatting for the points if ( - $groupType == DataSeries::TYPE_LINECHART - || $groupType == DataSeries::TYPE_STOCKCHART - || ($groupType === DataSeries::TYPE_SCATTERCHART && $plotSeriesValues !== false && !$plotSeriesValues->getScatterLines()) - || ($plotSeriesValues !== false && ($plotSeriesValues->getSchemeClr() || $plotSeriesValues->getPrstClr())) + $plotSeriesValues !== false ) { - $plotLineWidth = 12700; - if ($plotSeriesValues) { - $plotLineWidth = $plotSeriesValues->getLineWidth(); - } - $objWriter->startElement('c:spPr'); - $schemeClr = $typeClr = ''; - if ($plotLabel) { - $schemeClr = $plotLabel->getSchemeClr(); - if ($schemeClr) { - $typeClr = 'schemeClr'; - } else { - $schemeClr = $plotLabel->getPrstClr(); - if ($schemeClr) { - $typeClr = 'prstClr'; - } + $fillObject = $labelFill ?? $plotSeriesValues->getFillColorObject(); + $callLineStyles = true; + if ($fillObject instanceof ChartColor && $fillObject->isUsable()) { + if ($groupType === DataSeries::TYPE_LINECHART) { + $objWriter->startElement('a:ln'); + $callLineStyles = false; + } + $this->writeColor($objWriter, $fillObject); + if (!$callLineStyles) { + $objWriter->endElement(); // a:ln } } - if ($schemeClr) { - $objWriter->startElement('a:solidFill'); - $objWriter->startElement("a:$typeClr"); - $objWriter->writeAttribute('val', $schemeClr); - $objWriter->endElement(); - $objWriter->endElement(); + $nofill = $groupType == DataSeries::TYPE_STOCKCHART || ($groupType === DataSeries::TYPE_SCATTERCHART && !$plotSeriesValues->getScatterLines()); + if ($callLineStyles) { + $this->writeLineStyles($objWriter, $plotSeriesValues, $nofill); } - $objWriter->startElement('a:ln'); - $objWriter->writeAttribute('w', $plotLineWidth); - if ($groupType == DataSeries::TYPE_STOCKCHART || $groupType === DataSeries::TYPE_SCATTERCHART) { - $objWriter->startElement('a:noFill'); - $objWriter->endElement(); - } elseif ($plotLabel) { - $fillColor = $plotLabel->getFillColor(); - if (is_string($fillColor)) { - $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $fillColor); - $objWriter->endElement(); - $objWriter->endElement(); - } - } - $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->endElement(); // c:spPr } if ($plotSeriesValues) { $plotSeriesMarker = $plotSeriesValues->getPointMarker(); - if ($plotSeriesMarker) { + $markerFillColor = $plotSeriesValues->getMarkerFillColor(); + $fillUsed = $markerFillColor->IsUsable(); + $markerBorderColor = $plotSeriesValues->getMarkerBorderColor(); + $borderUsed = $markerBorderColor->isUsable(); + if ($plotSeriesMarker || $fillUsed || $borderUsed) { $objWriter->startElement('c:marker'); $objWriter->startElement('c:symbol'); - $objWriter->writeAttribute('val', $plotSeriesMarker); + if ($plotSeriesMarker) { + $objWriter->writeAttribute('val', $plotSeriesMarker); + } $objWriter->endElement(); if ($plotSeriesMarker !== 'none') { $objWriter->startElement('c:size'); $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointSize()); - $objWriter->endElement(); - $fillColor = $plotSeriesValues->getFillColor(); - if (is_string($fillColor) && $fillColor !== '') { - $objWriter->startElement('c:spPr'); - $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $fillColor); - $objWriter->endElement(); // srgbClr - $objWriter->endElement(); // solidFill - $objWriter->endElement(); // spPr + $objWriter->endElement(); // c:size + $objWriter->startElement('c:spPr'); + $this->writeColor($objWriter, $markerFillColor); + if ($borderUsed) { + $objWriter->startElement('a:ln'); + $this->writeColor($objWriter, $markerBorderColor); + $objWriter->endElement(); // a:ln } + $objWriter->endElement(); // spPr } $objWriter->endElement(); @@ -1066,7 +1023,7 @@ class Chart extends WriterPart } // Category Labels - $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesRef); + $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesIdx); if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) { $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries(); @@ -1112,7 +1069,7 @@ class Chart extends WriterPart $objWriter->endElement(); if ($groupType === DataSeries::TYPE_SCATTERCHART && $plotGroup->getPlotStyle() === 'smoothMarker') { $objWriter->startElement('c:smooth'); - $objWriter->writeAttribute('val', '1'); + $objWriter->writeAttribute('val', $plotSeriesValues->getSmoothLine() ? '1' : '0'); $objWriter->endElement(); } } @@ -1268,6 +1225,14 @@ class Chart extends WriterPart } } + private const CUSTOM_COLOR_TYPES = [ + DataSeries::TYPE_BARCHART, + DataSeries::TYPE_BARCHART_3D, + DataSeries::TYPE_PIECHART, + DataSeries::TYPE_PIECHART_3D, + DataSeries::TYPE_DONUTCHART, + ]; + /** * Write Bubble Chart Details. */ @@ -1384,8 +1349,8 @@ class Chart extends WriterPart $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); $objWriter->startElement('mc:Choice'); - $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart'); $objWriter->writeAttribute('Requires', 'c14'); + $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart'); $objWriter->startElement('c14:style'); $objWriter->writeAttribute('val', '102'); @@ -1509,12 +1474,7 @@ class Chart extends WriterPart $objWriter->endElement(); //end softEdge } - /** - * Write Line Style for Gridlines. - * - * @param Axis|GridLines $gridlines - */ - private function writeGridlinesLn(XMLWriter $objWriter, $gridlines): void + private function writeLineStyles(XMLWriter $objWriter, Properties $gridlines, bool $noFill = false): void { $objWriter->startElement('a:ln'); $widthTemp = $gridlines->getLineStyleProperty('width'); @@ -1523,7 +1483,12 @@ class Chart extends WriterPart } $this->writeNotEmpty($objWriter, 'cap', $gridlines->getLineStyleProperty('cap')); $this->writeNotEmpty($objWriter, 'cmpd', $gridlines->getLineStyleProperty('compound')); - $this->writeColor($objWriter, $gridlines->getLineColor()); + if ($noFill) { + $objWriter->startElement('a:noFill'); + $objWriter->endElement(); + } else { + $this->writeColor($objWriter, $gridlines->getLineColor()); + } $dash = $gridlines->getLineStyleProperty('dash'); if (!empty($dash)) { @@ -1544,16 +1509,16 @@ class Chart extends WriterPart if ($gridlines->getLineStyleProperty(['arrow', 'head', 'type'])) { $objWriter->startElement('a:headEnd'); $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'head', 'type'])); - $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowParameters('head', 'w')); - $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowParameters('head', 'len')); + $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('head')); + $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('head')); $objWriter->endElement(); } if ($gridlines->getLineStyleProperty(['arrow', 'end', 'type'])) { $objWriter->startElement('a:tailEnd'); $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'end', 'type'])); - $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowParameters('end', 'w')); - $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowParameters('end', 'len')); + $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('end')); + $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('end')); $objWriter->endElement(); } $objWriter->endElement(); //end ln diff --git a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php index 8a376df4..8b293bc1 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\Run; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; @@ -198,7 +199,7 @@ class StringTable extends WriterPart * @param RichText|string $richText text string or Rich text * @param string $prefix Optional Namespace prefix */ - public function writeRichTextForCharts(XMLWriter $objWriter, $richText = null, $prefix = null): void + public function writeRichTextForCharts(XMLWriter $objWriter, $richText = null, $prefix = ''): void { if (!$richText instanceof RichText) { $textRun = $richText; @@ -207,7 +208,7 @@ class StringTable extends WriterPart $run->setFont(null); } - if ($prefix !== null) { + if ($prefix !== '') { $prefix .= ':'; } @@ -249,27 +250,10 @@ class StringTable extends WriterPart } // Color - $objWriter->startElement($prefix . 'solidFill'); - $objWriter->startElement($prefix . 'srgbClr'); - $objWriter->writeAttribute('val', $element->getFont()->getColor()->getRGB()); - $objWriter->endElement(); // srgbClr - $objWriter->endElement(); // solidFill + $this->writeChartTextColor($objWriter, $element->getFont()->getChartColor(), $prefix); // Underscore Color - $underlineColor = $element->getFont()->getUnderlineColor(); - if ($underlineColor !== null) { - $type = $underlineColor->getType(); - $value = $underlineColor->getValue(); - if (!empty($type) && !empty($value)) { - $objWriter->startElement($prefix . 'uFill'); - $objWriter->startElement($prefix . 'solidFill'); - $objWriter->startElement($prefix . $type); - $objWriter->writeAttribute('val', $value); - $objWriter->endElement(); // schemeClr - $objWriter->endElement(); // solidFill - $objWriter->endElement(); // uFill - } - } + $this->writeChartTextColor($objWriter, $element->getFont()->getUnderlineColor(), $prefix, 'uFill'); // fontName if ($element->getFont()->getLatin()) { @@ -300,6 +284,33 @@ class StringTable extends WriterPart } } + private function writeChartTextColor(XMLWriter $objWriter, ?ChartColor $underlineColor, string $prefix, ?string $openTag = ''): void + { + if ($underlineColor !== null) { + $type = $underlineColor->getType(); + $value = $underlineColor->getValue(); + if (!empty($type) && !empty($value)) { + if ($openTag !== '') { + $objWriter->startElement($prefix . $openTag); + } + $objWriter->startElement($prefix . 'solidFill'); + $objWriter->startElement($prefix . $type); + $objWriter->writeAttribute('val', $value); + $alpha = $underlineColor->getAlpha(); + if (is_numeric($alpha)) { + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha)); + $objWriter->endElement(); + } + $objWriter->endElement(); // srgbClr/schemeClr/prstClr + $objWriter->endElement(); // solidFill + if ($openTag !== '') { + $objWriter->endElement(); // uFill + } + } + } + } + /** * Flip string table (for index searching). * diff --git a/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php b/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php index ad7fc776..0ed4a1b4 100644 --- a/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php +++ b/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php @@ -119,6 +119,7 @@ class AxisGlowTest extends AbstractFunctional $xAxis->setSoftEdges($softEdgesX); self::assertEquals($yGlowSize, $yAxis->getGlowProperty('size')); self::assertEquals($expectedGlowColor, $yAxis->getGlowProperty('color')); + self::assertSame($expectedGlowColor['value'], $yAxis->getGlowProperty(['color', 'value'])); self::assertEquals($softEdgesY, $yAxis->getSoftEdgesSize()); self::assertEquals($softEdgesX, $xAxis->getSoftEdgesSize()); diff --git a/tests/PhpSpreadsheetTests/Chart/BarChartCustomColorsTest.php b/tests/PhpSpreadsheetTests/Chart/BarChartCustomColorsTest.php new file mode 100644 index 00000000..824e9600 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/BarChartCustomColorsTest.php @@ -0,0 +1,162 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testBarchartColor(): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] + ); + // Custom colors for dataSeries (gray, blue, red, orange) + $colors = [ + 'cccccc', + '*accent1', // use schemeClr, was '00abb8', + '/green', // use prstClr, was 'b8292f', + 'eb8500', + ]; + + // 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 + $dataSeriesLabels1 = [ + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_STRING, + 'Worksheet!$C$1', + null, + 1 + ), // 2011 + ]; + // Set the X-Axis Labels + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $xAxisTickValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 + ]; + // 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 + // Custom Colors + $dataSeriesValues1Element = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4); + $dataSeriesValues1Element->setFillColor($colors); + $dataSeriesValues1 = [$dataSeriesValues1Element]; + + // Build the dataseries + $series1 = new DataSeries( + DataSeries::TYPE_PIECHART, // plotType + null, // plotGrouping (Pie charts don't have any grouping) + range(0, count($dataSeriesValues1) - 1), // plotOrder + $dataSeriesLabels1, // plotLabel + $xAxisTickValues1, // plotCategory + $dataSeriesValues1 // plotValues + ); + + // Set up a layout object for the Pie chart + $layout1 = new Layout(); + $layout1->setShowVal(true); + $layout1->setShowPercent(true); + + // Set the series in the plot area + $plotArea1 = new PlotArea($layout1, [$series1]); + // Set the chart legend + $legend1 = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); + + $title1 = new Title('Test Pie Chart'); + + // Create the chart + $chart1 = new Chart( + 'chart1', // name + $title1, // title + $legend1, // legend + $plotArea1, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + null // no Y-Axis for Pie Chart + ); + + // Set the position where the chart should appear in the worksheet + $chart1->setTopLeftPosition('A7'); + $chart1->setBottomRightPosition('H20'); + + // Add the chart to the worksheet + $worksheet->addChart($chart1); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $plotArea2 = $chart2->getPlotArea(); + $dataSeries2 = $plotArea2->getPlotGroup(); + self::assertCount(1, $dataSeries2); + $plotValues = $dataSeries2[0]->getPlotValues(); + self::assertCount(1, $plotValues); + $fillColors = $plotValues[0]->getFillColor(); + self::assertSame($colors, $fillColors); + + $writer = new XlsxWriter($reloadedSpreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart2); + self::assertSame(1, substr_count($data, '')); + self::assertSame(1, substr_count($data, '')); + self::assertSame(1, substr_count($data, '')); + self::assertSame(1, substr_count($data, '')); + self::assertSame(4, substr_count($data, '')); + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32ColoredAxisLabelTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32ColoredAxisLabelTest.php index 71cda504..b8041623 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32ColoredAxisLabelTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32ColoredAxisLabelTest.php @@ -58,7 +58,10 @@ class Charts32ColoredAxisLabelTest extends AbstractFunctional self::assertInstanceOf(Run::class, $run); $font = $run->getFont(); self::assertInstanceOf(Font::class, $font); - self::assertSame('00B050', $font->getColor()->getRGB()); + $chartColor = $font->getChartColor(); + self::assertNotNull($chartColor); + self::assertSame('00B050', $chartColor->getValue()); + self::assertSame('srgbClr', $chartColor->getType()); $yAxisLabel = $chart->getYAxisLabel(); $captionArray = $yAxisLabel->getCaption(); @@ -73,7 +76,10 @@ class Charts32ColoredAxisLabelTest extends AbstractFunctional self::assertInstanceOf(Run::class, $run); $font = $run->getFont(); self::assertInstanceOf(Font::class, $font); - self::assertSame('FF0000', $font->getColor()->getRGB()); + $chartColor = $font->getChartColor(); + self::assertNotNull($chartColor); + self::assertSame('FF0000', $chartColor->getValue()); + self::assertSame('srgbClr', $chartColor->getType()); $reloadedSpreadsheet->disconnectWorksheets(); } diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php index d79cba6f..38884eaf 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; +use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\Run; @@ -65,7 +66,10 @@ class Charts32ScatterTest extends AbstractFunctional self::assertFalse($font->getSubscript()); self::assertFalse($font->getStrikethrough()); self::assertSame('none', $font->getUnderline()); - self::assertSame('000000', $font->getColor()->getRGB()); + $chartColor = $font->getChartColor(); + self::assertNotNull($chartColor); + self::assertSame('000000', $chartColor->getValue()); + self::assertSame('srgbClr', $chartColor->getType()); $plotArea = $chart->getPlotArea(); $plotSeries = $plotArea->getPlotGroup(); @@ -75,19 +79,22 @@ class Charts32ScatterTest extends AbstractFunctional self::assertCount(3, $plotValues); $values = $plotValues[0]; self::assertFalse($values->getScatterLines()); - self::assertSame(28575, $values->getLineWidth()); + self::assertSame(28575 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); self::assertSame(3, $values->getPointSize()); self::assertSame('', $values->getFillColor()); $values = $plotValues[1]; self::assertFalse($values->getScatterLines()); - self::assertSame(28575, $values->getLineWidth()); + self::assertSame(28575 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); self::assertSame(3, $values->getPointSize()); self::assertSame('', $values->getFillColor()); $values = $plotValues[2]; self::assertFalse($values->getScatterLines()); - self::assertSame(28575, $values->getLineWidth()); + self::assertSame(28575 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); self::assertSame(7, $values->getPointSize()); - self::assertSame('FFFF00', $values->getFillColor()); + // Had been testing for Fill Color, but we actually + // meant to test for marker color, which is now distinct. + self::assertSame('FFFF00', $values->getMarkerFillColor()->getValue()); + self::assertSame('srgbClr', $values->getMarkerFillColor()->getType()); $reloadedSpreadsheet->disconnectWorksheets(); } @@ -135,7 +142,10 @@ class Charts32ScatterTest extends AbstractFunctional self::assertFalse($font->getSubscript()); self::assertFalse($font->getStrikethrough()); self::assertSame('none', $font->getUnderline()); - self::assertSame('000000', $font->getColor()->getRGB()); + $chartColor = $font->getChartColor(); + self::assertNotNull($chartColor); + self::assertSame('000000', $chartColor->getValue()); + self::assertSame('srgbClr', $chartColor->getType()); $run = $elements[1]; self::assertInstanceOf(Run::class, $run); @@ -149,7 +159,10 @@ class Charts32ScatterTest extends AbstractFunctional self::assertFalse($font->getSubscript()); self::assertFalse($font->getStrikethrough()); self::assertSame('single', $font->getUnderline()); - self::assertSame('00B0F0', $font->getColor()->getRGB()); + $chartColor = $font->getChartColor(); + self::assertNotNull($chartColor); + self::assertSame('00B0F0', $chartColor->getValue()); + self::assertSame('srgbClr', $chartColor->getType()); $run = $elements[2]; self::assertInstanceOf(Run::class, $run); @@ -163,7 +176,10 @@ class Charts32ScatterTest extends AbstractFunctional self::assertFalse($font->getSubscript()); self::assertFalse($font->getStrikethrough()); self::assertSame('none', $font->getUnderline()); - self::assertSame('000000', $font->getColor()->getRGB()); + $chartColor = $font->getChartColor(); + self::assertNotNull($chartColor); + self::assertSame('000000', $chartColor->getValue()); + self::assertSame('srgbClr', $chartColor->getType()); $plotArea = $chart->getPlotArea(); $plotSeries = $plotArea->getPlotGroup(); @@ -173,19 +189,22 @@ class Charts32ScatterTest extends AbstractFunctional self::assertCount(3, $plotValues); $values = $plotValues[0]; self::assertFalse($values->getScatterLines()); - self::assertSame(28575, $values->getLineWidth()); + self::assertSame(28575 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); self::assertSame(3, $values->getPointSize()); self::assertSame('', $values->getFillColor()); $values = $plotValues[1]; self::assertFalse($values->getScatterLines()); - self::assertSame(28575, $values->getLineWidth()); + self::assertSame(28575 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); self::assertSame(3, $values->getPointSize()); self::assertSame('', $values->getFillColor()); $values = $plotValues[2]; self::assertFalse($values->getScatterLines()); - self::assertSame(28575, $values->getLineWidth()); + self::assertSame(28575 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); self::assertSame(7, $values->getPointSize()); - self::assertSame('FFFF00', $values->getFillColor()); + // Had been testing for Fill Color, but we actually + // meant to test for marker color, which is now distinct. + self::assertSame('FFFF00', $values->getMarkerFillColor()->getValue()); + self::assertSame('srgbClr', $values->getMarkerFillColor()->getType()); $reloadedSpreadsheet->disconnectWorksheets(); } @@ -232,7 +251,10 @@ class Charts32ScatterTest extends AbstractFunctional self::assertFalse($font->getSubscript()); self::assertFalse($font->getStrikethrough()); self::assertSame('none', $font->getUnderline()); - self::assertSame('000000', $font->getColor()->getRGB()); + $chartColor = $font->getChartColor(); + self::assertNotNull($chartColor); + self::assertSame('000000', $chartColor->getValue()); + self::assertSame('srgbClr', $chartColor->getType()); $plotArea = $chart->getPlotArea(); $plotSeries = $plotArea->getPlotGroup(); @@ -242,17 +264,19 @@ class Charts32ScatterTest extends AbstractFunctional self::assertCount(3, $plotValues); $values = $plotValues[0]; self::assertTrue($values->getScatterLines()); - self::assertSame(12700, $values->getLineWidth()); + // the default value of 1 point is no longer written out + // when not explicitly specified. + self::assertNull($values->getLineWidth()); self::assertSame(3, $values->getPointSize()); self::assertSame('', $values->getFillColor()); $values = $plotValues[1]; self::assertTrue($values->getScatterLines()); - self::assertSame(12700, $values->getLineWidth()); + self::assertNull($values->getLineWidth()); self::assertSame(3, $values->getPointSize()); self::assertSame('', $values->getFillColor()); $values = $plotValues[2]; self::assertTrue($values->getScatterLines()); - self::assertSame(12700, $values->getLineWidth()); + self::assertNull($values->getLineWidth()); self::assertSame(3, $values->getPointSize()); self::assertSame('', $values->getFillColor()); @@ -303,7 +327,10 @@ class Charts32ScatterTest extends AbstractFunctional self::assertFalse($font->getSubscript()); self::assertFalse($font->getStrikethrough()); self::assertSame('none', $font->getUnderline()); - self::assertSame('000000', $font->getColor()->getRGB()); + $chartColor = $font->getChartColor(); + self::assertNotNull($chartColor); + self::assertSame('000000', $chartColor->getValue()); + self::assertSame('srgbClr', $chartColor->getType()); } $plotArea = $chart->getPlotArea(); @@ -314,19 +341,97 @@ class Charts32ScatterTest extends AbstractFunctional self::assertCount(3, $plotValues); $values = $plotValues[0]; self::assertFalse($values->getScatterLines()); - self::assertSame(28575, $values->getLineWidth()); + self::assertSame(28575 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); self::assertSame(3, $values->getPointSize()); self::assertSame('', $values->getFillColor()); $values = $plotValues[1]; self::assertFalse($values->getScatterLines()); - self::assertSame(28575, $values->getLineWidth()); + self::assertSame(28575 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); self::assertSame(3, $values->getPointSize()); self::assertSame('', $values->getFillColor()); $values = $plotValues[2]; self::assertFalse($values->getScatterLines()); - self::assertSame(28575, $values->getLineWidth()); + self::assertSame(28575 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); self::assertSame(7, $values->getPointSize()); - self::assertSame('FFFF00', $values->getFillColor()); + // Had been testing for Fill Color, but we actually + // meant to test for marker color, which is now distinct. + self::assertSame('FFFF00', $values->getMarkerFillColor()->getValue()); + self::assertSame('srgbClr', $values->getMarkerFillColor()->getType()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testScatter8(): void + { + $file = self::DIRECTORY . '32readwriteScatterChart8.xlsx'; + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame(1, $sheet->getChartCount()); + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + self::assertSame('Worksheet', $sheet->getTitle()); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + + $plotArea = $chart->getPlotArea(); + $plotSeries = $plotArea->getPlotGroup(); + self::assertCount(1, $plotSeries); + $dataSeries = $plotSeries[0]; + $plotValues = $dataSeries->getPlotValues(); + self::assertCount(3, $plotValues); + $values = $plotValues[0]; + self::assertSame(31750 / Properties::POINTS_WIDTH_MULTIPLIER, $values->getLineWidth()); + + self::assertSame('sq', $values->getLineStyleProperty('cap')); + self::assertSame('tri', $values->getLineStyleProperty('compound')); + self::assertSame('sysDash', $values->getLineStyleProperty('dash')); + self::assertSame('miter', $values->getLineStyleProperty('join')); + self::assertSame('arrow', $values->getLineStyleProperty(['arrow', 'head', 'type'])); + self::assertSame('med', $values->getLineStyleProperty(['arrow', 'head', 'w'])); + self::assertSame('sm', $values->getLineStyleProperty(['arrow', 'head', 'len'])); + self::assertSame('triangle', $values->getLineStyleProperty(['arrow', 'end', 'type'])); + self::assertSame('med', $values->getLineStyleProperty(['arrow', 'end', 'w'])); + self::assertSame('lg', $values->getLineStyleProperty(['arrow', 'end', 'len'])); + self::assertSame('accent1', $values->getLineColorProperty('value')); + self::assertSame('schemeClr', $values->getLineColorProperty('type')); + self::assertSame(40, $values->getLineColorProperty('alpha')); + self::assertSame('', $values->getFillColor()); + + self::assertSame(7, $values->getPointSize()); + self::assertSame('diamond', $values->getPointMarker()); + self::assertSame('0070C0', $values->getMarkerFillColor()->getValue()); + self::assertSame('srgbClr', $values->getMarkerFillColor()->getType()); + self::assertSame('002060', $values->getMarkerBorderColor()->getValue()); + self::assertSame('srgbClr', $values->getMarkerBorderColor()->getType()); + + $values = $plotValues[1]; + self::assertSame(7, $values->getPointSize()); + self::assertSame('square', $values->getPointMarker()); + self::assertSame('accent6', $values->getMarkerFillColor()->getValue()); + self::assertSame('schemeClr', $values->getMarkerFillColor()->getType()); + self::assertSame(3, $values->getMarkerFillColor()->getAlpha()); + self::assertSame('0FF000', $values->getMarkerBorderColor()->getValue()); + self::assertSame('srgbClr', $values->getMarkerBorderColor()->getType()); + self::assertNull($values->getMarkerBorderColor()->getAlpha()); + + $values = $plotValues[2]; + self::assertSame(7, $values->getPointSize()); + self::assertSame('triangle', $values->getPointMarker()); + self::assertSame('FFFF00', $values->getMarkerFillColor()->getValue()); + self::assertSame('srgbClr', $values->getMarkerFillColor()->getType()); + self::assertNull($values->getMarkerFillColor()->getAlpha()); + self::assertSame('accent4', $values->getMarkerBorderColor()->getValue()); + self::assertSame('schemeClr', $values->getMarkerBorderColor()->getType()); $reloadedSpreadsheet->disconnectWorksheets(); } diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php index 3123278f..6a4673fd 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php @@ -84,12 +84,16 @@ class Charts32XmlTest extends TestCase $chart = $charts[0]; self::assertNotNull($chart); $xAxis = $chart->getChartAxisX(); + $yAxis = $chart->getChartAxisY(); self::assertSame(Properties::FORMAT_CODE_GENERAL, $xAxis->getAxisNumberFormat()); if (is_bool($numeric)) { $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_GENERAL, true); } - $yAxis = $chart->getChartAxisY(); + self::assertSame('valAx', $yAxis->getAxisType()); + self::assertSame('valAx', $xAxis->getAxisType()); self::assertSame(Properties::FORMAT_CODE_GENERAL, $yAxis->getAxisNumberFormat()); + $xAxis->setAxisType(''); + $yAxis->setAxisType(''); if (is_bool($numeric)) { $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_GENERAL, $numeric); $yAxis->setAxisNumberProperties(Properties::FORMAT_CODE_GENERAL, $numeric); @@ -119,6 +123,34 @@ class Charts32XmlTest extends TestCase ]; } + public function testCatAxValAxFromRead(): void + { + $file = self::DIRECTORY . '32readwriteScatterChart1.xlsx'; + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + $xAxis = $chart->getChartAxisX(); + $yAxis = $chart->getChartAxisY(); + self::assertSame(Properties::FORMAT_CODE_GENERAL, $xAxis->getAxisNumberFormat()); + self::assertSame('valAx', $yAxis->getAxisType()); + self::assertSame('valAx', $xAxis->getAxisType()); + self::assertSame(Properties::FORMAT_CODE_GENERAL, $yAxis->getAxisNumberFormat()); + + $writer = new XlsxWriter($spreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); + $spreadsheet->disconnectWorksheets(); + + self::assertSame(0, substr_count($data, '')); + self::assertSame(2, substr_count($data, '')); + } + public function testAreaPrstClr(): void { $file = self::DIRECTORY . '32readwriteAreaChart4.xlsx'; diff --git a/tests/PhpSpreadsheetTests/Chart/ColorTest.php b/tests/PhpSpreadsheetTests/Chart/ColorTest.php new file mode 100644 index 00000000..e8326e52 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/ColorTest.php @@ -0,0 +1,32 @@ +getType()); + self::assertSame('800000', $color->getValue()); + $color->setColorProperties('*accent1'); + self::assertSame('schemeClr', $color->getType()); + self::assertSame('accent1', $color->getValue()); + $color->setColorProperties('/red'); + self::assertSame('prstClr', $color->getType()); + self::assertSame('red', $color->getValue()); + } + + public function testDataSeriesValues(): void + { + $dsv = new DataSeriesValues(); + $dsv->setFillColor([new ChartColor(), new ChartColor()]); + self::assertSame(['', ''], $dsv->getFillColor()); + $dsv->setFillColor('cccccc'); + self::assertSame('cccccc', $dsv->getFillColor()); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/DataSeriesValues2Test.php b/tests/PhpSpreadsheetTests/Chart/DataSeriesValues2Test.php new file mode 100644 index 00000000..a27397f9 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/DataSeriesValues2Test.php @@ -0,0 +1,172 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testDataSeriesValues(): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] + ); + + // 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), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 + ]; + // 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$5', null, 4), // Q1 to Q4 + ]; + // 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$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), + ]; + + // Build the dataseries + $series = new DataSeries( + null, // plotType + null, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues + ); + self::assertEmpty($series->getPlotType()); + self::assertEmpty($series->getPlotGrouping()); + self::assertFalse($series->getSmoothLine()); + $series->setPlotType(DataSeries::TYPE_AREACHART); + $series->setPlotGrouping(DataSeries::GROUPING_PERCENT_STACKED); + $series->setSmoothLine(true); + self::assertSame(DataSeries::TYPE_AREACHART, $series->getPlotType()); + self::assertSame(DataSeries::GROUPING_PERCENT_STACKED, $series->getPlotGrouping()); + self::assertTrue($series->getSmoothLine()); + + // Set the series in the plot area + $plotArea = new PlotArea(null, [$series]); + // Set the chart legend + $legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false); + + $title = new Title('Test %age-Stacked Area Chart'); + $yAxisLabel = new Title('Value ($k)'); + + // Create the chart + $chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // 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); + + self::assertSame(1, $chart->getPlotArea()->getPlotGroupCount()); + $plotValues = $chart->getPlotArea()->getPlotGroup()[0]->getPlotValues(); + self::assertCount(3, $plotValues); + self::assertSame([], $plotValues[1]->getDataValues()); + self::assertNull($plotValues[1]->getDataValue()); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $plotValues2 = $chart2->getPlotArea()->getPlotGroup()[0]->getPlotValues(); + self::assertCount(3, $plotValues2); + self::assertSame([15.0, 73.0, 61.0, 32.0], $plotValues2[1]->getDataValues()); + self::assertSame([15.0, 73.0, 61.0, 32.0], $plotValues2[1]->getDataValue()); + $labels2 = $chart->getPlotArea()->getPlotGroup()[0]->getPlotLabels(); + self::assertCount(3, $labels2); + self::assertSame(2010, $labels2[0]->getDataValue()); + $dataSeries = $chart->getPlotArea()->getPlotGroup()[0]; + self::assertFalse($dataSeries->getPlotValuesByIndex(99)); + self::assertNotFalse($dataSeries->getPlotValuesByIndex(0)); + self::assertSame([12, 56, 52, 30], $dataSeries->getPlotValuesByIndex(0)->getDataValues()); + self::assertSame(DataSeries::TYPE_AREACHART, $dataSeries->getPlotType()); + self::assertSame(DataSeries::GROUPING_PERCENT_STACKED, $dataSeries->getPlotGrouping()); + self::assertTrue($dataSeries->getSmoothLine()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testSomeProperties(): void + { + $dataSeriesValues = new DataSeriesValues(); + self::assertNull($dataSeriesValues->getDataSource()); + self::assertEmpty($dataSeriesValues->getPointMarker()); + self::assertSame(3, $dataSeriesValues->getPointSize()); + $dataSeriesValues->setDataSource('Worksheet!$B$1') + ->setPointMarker('square') + ->setPointSize(6); + self::assertSame('Worksheet!$B$1', $dataSeriesValues->getDataSource()); + self::assertSame('square', $dataSeriesValues->getPointMarker()); + self::assertSame(6, $dataSeriesValues->getPointSize()); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/DataSeriesValuesTest.php b/tests/PhpSpreadsheetTests/Chart/DataSeriesValuesTest.php index c34ca697..47c2fb89 100644 --- a/tests/PhpSpreadsheetTests/Chart/DataSeriesValuesTest.php +++ b/tests/PhpSpreadsheetTests/Chart/DataSeriesValuesTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; +use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Exception; use PHPUnit\Framework\TestCase; @@ -51,13 +52,14 @@ class DataSeriesValuesTest extends TestCase public function testGetLineWidth(): void { $testInstance = new DataSeriesValues(); - self::assertEquals(12700, $testInstance->getLineWidth(), 'should have default'); + // default has changed to null from 1 point (12700) + self::assertNull($testInstance->getLineWidth(), 'should have default'); - $testInstance->setLineWidth(40000); - self::assertEquals(40000, $testInstance->getLineWidth()); + $testInstance->setLineWidth(40000 / Properties::POINTS_WIDTH_MULTIPLIER); + self::assertEquals(40000 / Properties::POINTS_WIDTH_MULTIPLIER, $testInstance->getLineWidth()); $testInstance->setLineWidth(1); - self::assertEquals(12700, $testInstance->getLineWidth(), 'should enforce minimum width'); + self::assertEquals(12700 / Properties::POINTS_WIDTH_MULTIPLIER, $testInstance->getLineWidth(), 'should enforce minimum width'); } public function testFillColorCorrectInput(): void diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue589Test.php b/tests/PhpSpreadsheetTests/Chart/Issue589Test.php similarity index 96% rename from tests/PhpSpreadsheetTests/Writer/Xlsx/Issue589Test.php rename to tests/PhpSpreadsheetTests/Chart/Issue589Test.php index 82478fb9..747cba74 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue589Test.php +++ b/tests/PhpSpreadsheetTests/Chart/Issue589Test.php @@ -1,6 +1,6 @@ ', $actualXml); + self::assertXmlStringEqualsXmlString('', $actualXml); } } } @@ -153,7 +153,7 @@ class Issue589Test extends TestCase if ($actualXml === false) { self::fail('Failure saving the spPr element as xml string!'); } else { - self::assertXmlStringEqualsXmlString('', $actualXml); + self::assertXmlStringEqualsXmlString('', $actualXml); } } } diff --git a/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php b/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php index 58c024c1..e96d6c14 100644 --- a/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php +++ b/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php @@ -123,6 +123,35 @@ class ShadowPresetsTest extends TestCase } } + public function testPreset0(): void + { + $axis = new Axis(); + $axis->setShadowProperties(0); + $expectedShadow = [ + 'presets' => Properties::SHADOW_PRESETS_NOSHADOW, + 'effect' => null, + 'color' => [ + 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 40, + ], + 'size' => [ + 'sx' => null, + 'sy' => null, + 'kx' => null, + 'ky' => null, + ], + 'blur' => null, + 'direction' => null, + 'distance' => null, + 'algn' => null, + 'rotWithShape' => null, + ]; + foreach ($expectedShadow as $key => $value) { + self::assertEquals($value, $axis->getShadowProperty($key), $key); + } + } + public function testOutOfRangePresets(): void { $axis = new Axis();