From a89572107a655ac3fe8107034c7c1a3acb7b4740 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 25 Jun 2022 22:08:32 -0700 Subject: [PATCH 1/3] Handling of #REF! Errors in Subtotal, and More (#2902) * Handling of #REF! Errors in Subtotal, and More This PR derives from, and supersedes, PR #2870, submitted by @ndench. The problem reported in the original is that SUBTOTAL does not handle #REF! errors in its arguments properly; however, my investigation has enlarged the scope. The main problem is in Calculation, and it has a simple fix. When the calculation engine finds a reference to an uninitialized cell, it uses `null` as the value. This is appropriate when the cell belongs to a defined sheet; however, for an undefined sheet, #REF! is more appropriate. With that fix in place, SUBTOTAL still needs a small fix of its own. It tries to parse its cell reference arguments into an array, but, if the reference does not match the expected format (as #REF! will not), this results in referencing undefined array indexes, with attendant messages. That assignment is changed to be more flexible, eliminating the problem and the messages. Those 2 fixes are sufficient to ensure that the original problem is resolved. It also resolves a similar problem with some other functions (e.g. SUM). However, it does not resolve it for all functions. Or, to be more particular, many functions will return #VALUE! rather than #REF! if this arises, and the same is true for other errors in the function arguments, e.g. #DIV/0!. This PR does not attempt to address all functions; I need to think of a systematic way to pursue that. However, at least for most MathTrig functions, which validate their arguments using a common method, it is relatively easy to get the function to propagate the proper error result. * Arrange Array The Way call_user_func_array Wants Problem with Php8.0+ - array passed to call_user_func_array must have int keys before string keys, otherwise Php thinks we are passing positional parameters after keyword parameters. 7 other functions use flattenArrayIndexed, but Subtotal is the only one which uses that result to subsequently pass arguments to call_user_func_array. So the others should not require a change. A specific test is added for SUM to validate that conclusion. * Change Needed for Hidden Row Filter Same as change made to Formula Args filter. --- .../Calculation/Calculation.php | 2 +- .../Calculation/Information/ExcelError.php | 8 +++ .../Calculation/MathTrig/Helpers.php | 4 +- .../Calculation/MathTrig/Operations.php | 2 +- .../Calculation/MathTrig/Subtotal.php | 27 ++++++++- .../Functions/MathTrig/SubTotalTest.php | 28 +++++++++ .../Calculation/RefErrorTest.php | 58 +++++++++++++++++++ 7 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Calculation/RefErrorTest.php diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 516faa86..4e3a7c53 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -4834,7 +4834,7 @@ class Calculation $cell->attach($pCellParent); } else { $cellRef = ($cellSheet !== null) ? "'{$matches[2]}'!{$cellRef}" : $cellRef; - $cellValue = null; + $cellValue = ($cellSheet !== null) ? null : Information\ExcelError::REF(); } } else { return $this->raiseFormulaError('Unable to access Cell Reference'); diff --git a/src/PhpSpreadsheet/Calculation/Information/ExcelError.php b/src/PhpSpreadsheet/Calculation/Information/ExcelError.php index 88de7e54..6305e502 100644 --- a/src/PhpSpreadsheet/Calculation/Information/ExcelError.php +++ b/src/PhpSpreadsheet/Calculation/Information/ExcelError.php @@ -25,6 +25,14 @@ class ExcelError 'spill' => '#SPILL!', ]; + /** + * @param mixed $value + */ + public static function throwError($value): string + { + return in_array($value, self::$errorCodes, true) ? $value : self::$errorCodes['value']; + } + /** * ERROR_TYPE. * diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php b/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php index 348aa5b3..f34f159b 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php @@ -38,7 +38,7 @@ class Helpers return 0 + $number; } - throw new Exception(ExcelError::VALUE()); + throw new Exception(ExcelError::throwError($number)); } /** @@ -59,7 +59,7 @@ class Helpers return 0 + $number; } - throw new Exception(ExcelError::VALUE()); + throw new Exception(ExcelError::throwError($number)); } /** diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php b/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php index f26da389..06258451 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php @@ -118,7 +118,7 @@ class Operations if (is_numeric($arg)) { $returnValue *= $arg; } else { - return ExcelError::VALUE(); + return ExcelError::throwError($arg); } } diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php b/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php index 336bc690..6d8f4723 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php @@ -18,7 +18,11 @@ class Subtotal return array_filter( $args, function ($index) use ($cellReference) { - [, $row, ] = explode('.', $index); + $explodeArray = explode('.', $index); + $row = $explodeArray[1] ?? ''; + if (!is_numeric($row)) { + return true; + } return $cellReference->getWorksheet()->getRowDimension($row)->getVisible(); }, @@ -35,7 +39,9 @@ class Subtotal return array_filter( $args, function ($index) use ($cellReference) { - [, $row, $column] = explode('.', $index); + $explodeArray = explode('.', $index); + $row = $explodeArray[1] ?? ''; + $column = $explodeArray[2] ?? ''; $retVal = true; if ($cellReference->getWorksheet()->cellExists($column . $row)) { //take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula @@ -87,7 +93,22 @@ class Subtotal public static function evaluate($functionType, ...$args) { $cellReference = array_pop($args); - $aArgs = Functions::flattenArrayIndexed($args); + $bArgs = Functions::flattenArrayIndexed($args); + $aArgs = []; + // int keys must come before string keys for PHP 8.0+ + // Otherwise, PHP thinks positional args follow keyword + // in the subsequent call to call_user_func_array. + // Fortunately, order of args is unimportant to Subtotal. + foreach ($bArgs as $key => $value) { + if (is_int($key)) { + $aArgs[$key] = $value; + } + } + foreach ($bArgs as $key => $value) { + if (!is_int($key)) { + $aArgs[$key] = $value; + } + } try { $subtotal = (int) Helpers::validateNumericNullBool($functionType); diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php index cf79ac0b..43156182 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php @@ -127,4 +127,32 @@ class SubTotalTest extends AllSetupTeardown $sheet->getCell('H1')->setValue("=SUBTOTAL(9, A1:$maxCol$maxRow)"); self::assertEquals(362, $sheet->getCell('H1')->getCalculatedValue()); } + + public function testRefError(): void + { + $sheet = $this->getSheet(); + $sheet->getCell('A1')->setValue('=SUBTOTAL(9, #REF!)'); + self::assertEquals('#REF!', $sheet->getCell('A1')->getCalculatedValue()); + } + + public function testSecondaryRefError(): void + { + $sheet = $this->getSheet(); + $sheet->getCell('A1')->setValue('=SUBTOTAL(9, B1:B9,#REF!,C1:C9)'); + self::assertEquals('#REF!', $sheet->getCell('A1')->getCalculatedValue()); + } + + public function testNonStringSingleCellRefError(): void + { + $sheet = $this->getSheet(); + $sheet->getCell('A1')->setValue('=SUBTOTAL(9, 1, C1, Sheet99!A11)'); + self::assertEquals('#REF!', $sheet->getCell('A1')->getCalculatedValue()); + } + + public function testNonStringCellRangeRefError(): void + { + $sheet = $this->getSheet(); + $sheet->getCell('A1')->setValue('=SUBTOTAL(9, Sheet99!A1)'); + self::assertEquals('#REF!', $sheet->getCell('A1')->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/RefErrorTest.php b/tests/PhpSpreadsheetTests/Calculation/RefErrorTest.php new file mode 100644 index 00000000..ffdfc2fa --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/RefErrorTest.php @@ -0,0 +1,58 @@ +getActiveSheet(); + $sheet1->setTitle('Sheet1'); + $sheet2 = $spreadsheet->createSheet(); + $sheet2->setTitle('Sheet2'); + $sheet2->getCell('A1')->setValue(5); + $sheet1->getCell('A1')->setValue(9); + $sheet1->getCell('A2')->setValue(2); + $sheet1->getCell('A3')->setValue(4); + $sheet1->getCell('A4')->setValue(6); + $sheet1->getCell('A5')->setValue(7); + $sheet1->getRowDimension(5)->setVisible(false); + $sheet1->getCell('B1')->setValue('=1/0'); + $sheet1->getCell('C1')->setValue('=Sheet99!A1'); + $sheet1->getCell('C2')->setValue('=Sheet2!A1'); + $sheet1->getCell('C3')->setValue('=Sheet2!A2'); + $sheet1->getCell('H1')->setValue($formula); + self::assertSame($expected, $sheet1->getCell('H1')->getCalculatedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function providerRefError(): array + { + return [ + 'Subtotal9 Ok' => [12, '=SUBTOTAL(A1,A2:A4)'], + 'Subtotal9 REF' => ['#REF!', '=SUBTOTAL(A1,A2:A4,C1)'], + 'Subtotal9 with literal and cells' => [111, '=SUBTOTAL(A1,A2:A4,99)'], + 'Subtotal9 with literal no rows hidden' => [111, '=SUBTOTAL(109,A2:A4,99)'], + 'Subtotal9 with literal ignoring hidden row' => [111, '=SUBTOTAL(109,A2:A5,99)'], + 'Subtotal9 with literal using hidden row' => [118, '=SUBTOTAL(9,A2:A5,99)'], + 'Subtotal9 with Null same sheet' => [12, '=SUBTOTAL(A1,A2:A4,A99)'], + 'Subtotal9 with Null Different sheet' => [12, '=SUBTOTAL(A1,A2:A4,C3)'], + 'Subtotal9 with NonNull Different sheet' => [17, '=SUBTOTAL(A1,A2:A4,C2)'], + 'Product DIV0' => ['#DIV/0!', '=PRODUCT(2, 3, B1)'], + 'Sqrt REF' => ['#REF!', '=SQRT(C1)'], + 'Sum NUM' => ['#NUM!', '=SUM(SQRT(-1), A2:A4)'], + 'Sum with literal and cells' => [111, '=SUM(A2:A4, 99)'], + 'Sum REF' => ['#REF!', '=SUM(A2:A4, C1)'], + 'Tan DIV0' => ['#DIV/0!', '=TAN(B1)'], + ]; + } +} From b5b83abc0e8d516474bac75c83c3cfc041c0497b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 29 Jun 2022 09:20:33 -0700 Subject: [PATCH 2/3] Adjust Both Coordinates for Two-Cell Anchors (#2909) Fix #2908. When support for two-cell anchors was added for drawings, we neglected to adjust the second cell address when rows or columns are added or deleted. It also appears that "twoCell" and "oneCell" were introduced as lower-case literals when support for the editAs attribute was subsequently introduced. --- src/PhpSpreadsheet/ReferenceHelper.php | 6 +++ src/PhpSpreadsheet/Worksheet/BaseDrawing.php | 6 +-- .../Writer/Xlsx/DrawingsInsertRowsTest.php | 45 ++++++++++++++++++ .../Writer/Xlsx/DrawingsTest.php | 8 ++-- tests/data/Writer/XLSX/issue.2908.xlsx | Bin 0 -> 120924 bytes 5 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsInsertRowsTest.php create mode 100644 tests/data/Writer/XLSX/issue.2908.xlsx diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index 8caaab18..59247f89 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -525,6 +525,12 @@ class ReferenceHelper if ($objDrawing->getCoordinates() != $newReference) { $objDrawing->setCoordinates($newReference); } + if ($objDrawing->getCoordinates2() !== '') { + $newReference = $this->updateCellReference($objDrawing->getCoordinates2()); + if ($objDrawing->getCoordinates2() != $newReference) { + $objDrawing->setCoordinates2($newReference); + } + } } // Update workbook: define names diff --git a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php index 815536b5..369e4162 100644 --- a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php +++ b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -9,8 +9,8 @@ use PhpOffice\PhpSpreadsheet\IComparable; class BaseDrawing implements IComparable { const EDIT_AS_ABSOLUTE = 'absolute'; - const EDIT_AS_ONECELL = 'onecell'; - const EDIT_AS_TWOCELL = 'twocell'; + const EDIT_AS_ONECELL = 'oneCell'; + const EDIT_AS_TWOCELL = 'twoCell'; private const VALID_EDIT_AS = [ self::EDIT_AS_ABSOLUTE, self::EDIT_AS_ONECELL, @@ -530,6 +530,6 @@ class BaseDrawing implements IComparable public function validEditAs(): bool { - return in_array($this->editAs, self::VALID_EDIT_AS); + return in_array($this->editAs, self::VALID_EDIT_AS, true); } } diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsInsertRowsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsInsertRowsTest.php new file mode 100644 index 00000000..e2cbbff3 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsInsertRowsTest.php @@ -0,0 +1,45 @@ +load($inputFilename); + $sheet = $spreadsheet->getActiveSheet(); + $drawingCollection = $sheet->getDrawingCollection(); + self::assertCount(1, $drawingCollection); + $drawing = $drawingCollection[0]; + self::assertNotNull($drawing); + self::assertSame('D10', $drawing->getCoordinates()); + self::assertSame('F11', $drawing->getCoordinates2()); + self::assertSame('oneCell', $drawing->getEditAs()); + + $sheet->insertNewRowBefore(5); + $sheet->insertNewRowBefore(6); + + // Save spreadsheet to file and read it back + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + $rsheet = $reloadedSpreadsheet->getActiveSheet(); + $drawingCollection2 = $rsheet->getDrawingCollection(); + self::assertCount(1, $drawingCollection2); + $drawing2 = $drawingCollection2[0]; + self::assertNotNull($drawing2); + self::assertSame('D12', $drawing2->getCoordinates()); + self::assertSame('F13', $drawing2->getCoordinates2()); + self::assertSame('oneCell', $drawing2->getEditAs()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php index f089b9ee..14b816d2 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php @@ -554,10 +554,10 @@ class DrawingsTest extends AbstractFunctional { return [ 'absolute' => ['absolute'], - 'onecell' => ['onecell'], - 'twocell' => ['twocell'], - 'unset (will be treated as twocell)' => [''], - 'unknown (will be treated as twocell)' => ['unknown', ''], + 'onecell' => ['oneCell'], + 'twocell' => ['twoCell'], + 'unset (will be treated as twoCell)' => [''], + 'unknown (will be treated as twoCell)' => ['unknown', ''], ]; } diff --git a/tests/data/Writer/XLSX/issue.2908.xlsx b/tests/data/Writer/XLSX/issue.2908.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3dcd7923f179933b1304d050116c9defb8dcd28d GIT binary patch literal 120924 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

EuXj^5awS+6XJ`$~_?ng}f9%@w; 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

-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 Date: Wed, 29 Jun 2022 17:52:09 -0700 Subject: [PATCH 3/3] Additional Support for Chart DataSeriesValues (#2906) * Additional Support for Chart DataSeriesValues Fix #2863. DataSeriesValues now extends Properties, allowing it to share code in common with Axis and Gridlines. This causes some minor breakages; in particular line width is now initialized to null instead of Excel's default value, and is specified in points, as the user would expect from Excel, rather than the value stored in Xml. This change: - adds support for 1 or 2 marker colors. - adds support for `smoothLine` to DataSeriesValues. - will determine `catAx` or `valAx` for Axis based on what is read from the Xml when available, rather than guessing based on format. (Another minor break.) - reads `formatCode` and `sourceLinked` for Axis. - correct 2 uses of `$plotSeriesRef` to `$plotSeriesIndex` in Writer/Xlsx/Chart. - pushes coverage over 90% for Chart (88.70% overall). * Update Change Log I had updated previously but forgot to stage the member. * Adopt Some Suggestions Incorporate some changes suggested by @bridgeplayr. * Use ChartColor for DSV Fill And Font Text DataSeriesValues Fill could be a scalar or an array, so I saved it till last. * Some Final Cleanup No code changes. Illustrate even more of the new features in existing sample files. Deprecate *_ARGB in Properties/ChartColors in favor of *_RGB, because it uses only 6 hex digits. The alpha value is stored separately. --- CHANGELOG.md | 2 +- phpstan-baseline.neon | 12 +- .../33_Chart_create_bar_custom_colors.php | 183 +++++++++++++++ samples/Chart/33_Chart_create_line.php | 4 +- samples/Chart/33_Chart_create_scatter2.php | 70 +++++- .../templates/32readwriteScatterChart8.xlsx | Bin 0 -> 12409 bytes src/PhpSpreadsheet/Chart/Axis.php | 23 +- src/PhpSpreadsheet/Chart/ChartColor.php | 52 +++-- src/PhpSpreadsheet/Chart/DataSeries.php | 4 - src/PhpSpreadsheet/Chart/DataSeriesValues.php | 171 ++++++++++---- src/PhpSpreadsheet/Chart/Properties.php | 34 +-- src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 178 ++++++++------- src/PhpSpreadsheet/Style/Font.php | 62 +++-- src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 213 ++++++++---------- .../Writer/Xlsx/StringTable.php | 53 +++-- .../Chart/AxisGlowTest.php | 1 + .../Chart/BarChartCustomColorsTest.php | 162 +++++++++++++ .../Chart/Charts32ColoredAxisLabelTest.php | 10 +- .../Chart/Charts32ScatterTest.php | 147 ++++++++++-- .../Chart/Charts32XmlTest.php | 34 ++- tests/PhpSpreadsheetTests/Chart/ColorTest.php | 32 +++ .../Chart/DataSeriesValues2Test.php | 172 ++++++++++++++ .../Chart/DataSeriesValuesTest.php | 10 +- .../{Writer/Xlsx => Chart}/Issue589Test.php | 6 +- .../Chart/ShadowPresetsTest.php | 29 +++ 25 files changed, 1279 insertions(+), 385 deletions(-) create mode 100644 samples/Chart/33_Chart_create_bar_custom_colors.php create mode 100644 samples/templates/32readwriteScatterChart8.xlsx create mode 100644 tests/PhpSpreadsheetTests/Chart/BarChartCustomColorsTest.php create mode 100644 tests/PhpSpreadsheetTests/Chart/ColorTest.php create mode 100644 tests/PhpSpreadsheetTests/Chart/DataSeriesValues2Test.php rename tests/PhpSpreadsheetTests/{Writer/Xlsx => Chart}/Issue589Test.php (96%) 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 0000000000000000000000000000000000000000..fdd85b0ed3bf9fbac400a46a350da93f74e5d9a4 GIT binary patch literal 12409 zcmeHt1y>yD)-|q;1P$&GBuMZ8!L@-v(BSUwuEE_scyPDi5Znpw?(QD=I+?jQGt7Ly z;Jv+8b^l313=k3u0}KWX4h#&81Z;f9o+$(z4D1FP3=AC%4nkGX(!x&P z!cJ4(*-GD5o!-gZj5rGl;#CG11nB$!d;K3?f#TQ!i*Jmm!q;(kC{c9^Iy+f~G!P;0 z;$Nd(KuU6>hIH0)ckCZ?F@Y;!QV5g>#mv}_q*d_vey=T!W`$xI-pWe96oRO=nwt@( zZ^PC@j9AnbVo|Nkw~$TlsTRqYUlZNO`0N zCa?zVc4nWa@7qTRAT=wl4u_L(y^2C?ljZI8Qs1C*RMXKkYrfj6V_LaaI@vbnmqcQF zTefmu4qb%?y8w5v+zP0ShJH6jNdP+WTBl5J%pe%128?-CKgS!~jxi^3~6r9S>0JKNX>wjY-p zYC2QosdKa6t8s9lIR&pedRL4?(gYf>ſT|-C@Q%5J6zH=;CdiOT&i>jH(7VU)a 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 z zAv*;Pj!a0CA}E{FfbYSRlLzxHgf^Q?q0d)h3wTDPR2%zpSe+jXhC5Du9&FjCxrHFA zW|fN$dnw)~R#g2_s_$Iqvqlto2BU=hR9mbWN#sXo54Naq;Qs1&RaipSPvmct+HzB( zS+mw-Ve0)=KFP`_iqV&pL5u7tO~BW`pv~aa)m4E+ppY3&)82Ctt*n!;tg}mRF+qyW zk>N*zfgBRS;W2sPM(}86l!UHdV!f~P<;T~&B?$+;vPa|wY&I!olVFEX4o?wHqNDV= z`~tQ{b#dZNA@Oir+E-K;PN8`xw~ydxjW#HxBs_4{Eo3gJ(@oyyy3GhtI2kk9#A0Ox z-97tvFn;-^LTv#z$v~t%gL%$}QtDayFL+?`XxV2LOt4$IUz=42#gnSI-_LUB&-#RM zHg0rv&C@Sscw#rxPTYq)ttMiIq@I$l-(Ci-w`Z7nrtm3$Y(*gnFgigW0QKbl#Zf0i z^sd7|j!FgU^`QRMQMIkC{^zHlCHwQ08r}Z0cHn;ud4=qM6qZ{0%AZeBHs`gA@^>)t zMrNw<3Ym{@$fv}vE^^H$?B~)P$ZpYF-$ltQ1$65ZbnE#tblOCP0Gi=*FYg7o508dD zg6Uq|psAfG2a-`*=eByvrMy%L)nn2(Ta_a*$#S63f5XZaiACqCScr;8EWNJbUswra z>EmoZ8pAzu4h(`7(A06BlJ?iJQ?tKl{i4svqv^6lpb9g!*I=H;%J%^Lp(O>NmXBQ| z1iYo#&eCakojh%{n{P|8T`-4h_0>RE-5Z#{A91a^H;olZuorc>$mAHs73mx zIQh>I*mL8*PtW`|1p`}#|7HBgKmNxo&2yaRBNe}q&;bAa5&sylc#iVCulpNi98@3y z-OBSW?{k3XMfcwTj#$3{ewW~%n?A2i{x%K4`NQ;irSdt#^BUl91by6J2>(?=f7L+G zQJ&X)exo$v|3djk1?V}#bA9?7L6-a%!q46Ob1(i?tv&~QF6w>*np6D!#{NGN?>XRe zq4XQjgz^{QGnw>UM*Wga&q4pLcYZ^HCjUX8|4={A&Ho;S{%W2{{TK5;W0DLI8bq$2 QbCxJzKW7y5=zf0se<{9 literal 0 HcmV?d00001 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();