Repository: PHPOffice/PhpSpreadsheet (php)

Issue:
XLSX Charts reported as corrupt by Excel in PhpSpreadsheet.
In file PhpSpreadsheet/Writer/Xlsx/Chart.php lines 1600 and 1657 writing a "C:layout" parent element to the "c;manualLayout"  cause Excel to report "We found a problem with some content in 'xxxxxx.xlsx'. Do you want us to try to recover as much as we can?".   Clicking 'Yes' fixes the spreadsheet, so it is a minor annoyance but would be great if you can fix it.  Commenting out those two lines removes the message.

Pull request description:
Better Handling of Chart DisplayBlanksAs
Fix #4411. User copied some code from a PHPExcel program which set DisplayAsBlanks to `0`. This resulted in what Excel deemed a corrupt spreadsheet using PhpSpreadsheet. The only values allowed for that field are `gap`, `zero` (not `0`), and `span`. PHPExcel used `0` as a default, and got away with it because it ignored the value entirely when writing out the spreadsheet, using `gap` all the time.

I had to choose between throwing an exception and just using the default when an attempt is made to set that property to an invalid value. An exception just seems more punitive than helpful to me, especially if we want people to migrate from PHPExcel, which still seems to have a large user base. So I've gone with using `gap` in place of an invalid value. Note that, according to https://learn.microsoft.com/ru-ru/openspecs/office_standards/ms-oe376/b5c5c694-21d9-437c-9a4a-21e0e843eed8, `gap` is used as the default whenever it is permitted for the chart in question; and, when it isn't permitted, the chart will use its default method (which will always be `zero`).

There were no tests nor samples for this property. All the tests and samples which use it use only `gap`. I have added a small test, and a new sample to illustrate the difference between the 3 options.

This is:

- [x] a bugfix
- [ ] a new feature
- [ ] refactoring
- [ ] additional unit tests

Checklist:

- [x] Changes are covered by unit tests
  - [ ] Changes are covered by existing unit tests
  - [x] New unit tests have been added
- [x] Code style is respected
- [x] Commit message explains **why** the change is made (see https://github.com/erlang/otp/wiki/Writing-good-commit-messages)
- [ ] CHANGELOG.md contains a short summary of the change and a link to the pull request if applicable
- [ ] Documentation is updated as necessary




Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 19f3bf4f8..0c82d4896 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org).
 
 - Add ability to add custom functions to Calculation. [PR #4390](https://github.com/PHPOffice/PhpSpreadsheet/pull/4390)
 - Add FormulaRange to IgnoredErrors. [PR #4393](https://github.com/PHPOffice/PhpSpreadsheet/pull/4393)
-- Permit read to class which extends Spreadsheet. [Discussion #4402](https://github.com/PHPOffice/PhpSpreadsheet/discussions/4402) [PR #4404](https://github.com/PHPOffice/PhpSpreadsheet/pull/4404)
 
 ### Removed
 
@@ -36,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
 
 - BIN2DEC, OCT2DEC, and HEX2DEC return numbers rather than strings. [Issue #4383](https://github.com/PHPOffice/PhpSpreadsheet/issues/4383) [PR #4389](https://github.com/PHPOffice/PhpSpreadsheet/pull/4389)
 - Fix TREND_BEST_FIT_NO_POLY. [Issue #4400](https://github.com/PHPOffice/PhpSpreadsheet/issues/4400) [PR #4339](https://github.com/PHPOffice/PhpSpreadsheet/pull/4339)
+- Better handling of Chart DisplayBlanksAs. [Issue #4411](https://github.com/PHPOffice/PhpSpreadsheet/issues/4411) [PR #4414](https://github.com/PHPOffice/PhpSpreadsheet/pull/4414)
 
 ## 2025-03-02 - 4.1.0
 
diff --git a/samples/Chart33b/33_Chart_create_scatter7_blanks.php b/samples/Chart33b/33_Chart_create_scatter7_blanks.php
new file mode 100644
index 000000000..55305d741
--- /dev/null
+++ b/samples/Chart33b/33_Chart_create_scatter7_blanks.php
@@ -0,0 +1,144 @@
+<?php
+
+use PhpOffice\PhpSpreadsheet\Chart\Chart;
+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\Title;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+require __DIR__ . '/../Header.php';
+
+$spreadsheet = new Spreadsheet();
+$worksheet = $spreadsheet->getActiveSheet();
+$worksheet->fromArray(
+    [
+        ['', 2010, 2011, 2012],
+        ['Q1', 12, 15, 21],
+        ['Q2', 56, null, 86],
+        ['Q3', 52, 61, 69],
+        ['Q4', 30, 32, 0],
+    ],
+    strictNullComparison: true
+);
+
+// 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
+$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(
+    DataSeries::TYPE_SCATTERCHART, // plotType
+    null, // plotGrouping (Scatter charts don't have any grouping)
+    range(0, count($dataSeriesValues) - 1), // plotOrder
+    $dataSeriesLabels, // plotLabel
+    $xAxisTickValues, // plotCategory
+    $dataSeriesValues, // plotValues
+    null, // plotDirection
+    false, // smooth line
+    DataSeries::STYLE_LINEMARKER  // plotStyle
+);
+
+// Set the series in the plot area
+$plotArea = new PlotArea(null, [$series]);
+// Set the chart legend
+$legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
+
+$title1 = new Title('Test Scatter Chart Gap');
+$yAxisLabel1 = new Title('Value ($k)');
+// Create the chart
+$chart1 = new Chart(
+    'chart1', // name
+    $title1, // title
+    $legend, // legend
+    $plotArea, // plotArea
+    true, // plotVisibleOnly
+    DataSeries::EMPTY_AS_GAP, // displayBlanksAs
+    null, // xAxisLabel
+    $yAxisLabel1  // yAxisLabel
+);
+
+// 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);
+
+$helper->renderChart($chart1, __FILE__);
+
+$title2 = new Title('Test Scatter Chart Zero');
+$yAxisLabel2 = new Title('Value ($k)');
+// Create the chart
+$chart2 = new Chart(
+    'chart2', // name
+    $title2, // title
+    $legend, // legend
+    $plotArea, // plotArea
+    true, // plotVisibleOnly
+    DataSeries::EMPTY_AS_ZERO, // displayBlanksAs
+    null, // xAxisLabel
+    $yAxisLabel2  // yAxisLabel
+);
+
+// Set the position where the chart should appear in the worksheet
+$chart2->setTopLeftPosition('A22');
+$chart2->setBottomRightPosition('H35');
+
+// Add the chart to the worksheet
+$worksheet->addChart($chart2);
+
+$helper->renderChart($chart2, __FILE__);
+
+$title3 = new Title('Test Scatter Chart Span');
+$yAxisLabel3 = new Title('Value ($k)');
+
+// Create the chart
+$chart3 = new Chart(
+    'chart3', // name
+    $title3, // title
+    $legend, // legend
+    $plotArea, // plotArea
+    true, // plotVisibleOnly
+    DataSeries::EMPTY_AS_SPAN, // displayBlanksAs
+    null, // xAxisLabel
+    $yAxisLabel3  // yAxisLabel
+);
+
+// Set the position where the chart should appear in the worksheet
+$chart3->setTopLeftPosition('A37');
+$chart3->setBottomRightPosition('H50');
+
+// Add the chart to the worksheet
+$worksheet->addChart($chart3);
+
+$helper->renderChart($chart3, __FILE__);
+
+// Save Excel 2007 file
+$helper->write($spreadsheet, __FILE__, ['Xlsx'], true);
diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php
index 6bcae28c8..6b41e367f 100644
--- a/src/PhpSpreadsheet/Chart/Chart.php
+++ b/src/PhpSpreadsheet/Chart/Chart.php
@@ -128,7 +128,7 @@ class Chart
      * Create a new Chart.
      * majorGridlines and minorGridlines are deprecated, moved to Axis.
      */
-    public function __construct(string $name, ?Title $title = null, ?Legend $legend = null, ?PlotArea $plotArea = null, bool $plotVisibleOnly = true, string $displayBlanksAs = DataSeries::EMPTY_AS_GAP, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null)
+    public function __construct(string $name, ?Title $title = null, ?Legend $legend = null, ?PlotArea $plotArea = null, bool $plotVisibleOnly = true, string $displayBlanksAs = DataSeries::DEFAULT_EMPTY_AS, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null)
     {
         $this->name = $name;
         $this->title = $title;
@@ -137,7 +137,7 @@ class Chart
         $this->yAxisLabel = $yAxisLabel;
         $this->plotArea = $plotArea;
         $this->plotVisibleOnly = $plotVisibleOnly;
-        $this->displayBlanksAs = $displayBlanksAs;
+        $this->setDisplayBlanksAs($displayBlanksAs);
         $this->xAxis = $xAxis ?? new Axis();
         $this->yAxis = $yAxis ?? new Axis();
         if ($majorGridlines !== null) {
@@ -318,7 +318,8 @@ class Chart
      */
     public function setDisplayBlanksAs(string $displayBlanksAs): static
     {
-        $this->displayBlanksAs = $displayBlanksAs;
+        $displayBlanksAs = strtolower($displayBlanksAs);
+        $this->displayBlanksAs = in_array($displayBlanksAs, DataSeries::VALID_EMPTY_AS, true) ? $displayBlanksAs : DataSeries::DEFAULT_EMPTY_AS;
 
         return $this;
     }
diff --git a/src/PhpSpreadsheet/Chart/DataSeries.php b/src/PhpSpreadsheet/Chart/DataSeries.php
index dd392005a..06f1c3c7f 100644
--- a/src/PhpSpreadsheet/Chart/DataSeries.php
+++ b/src/PhpSpreadsheet/Chart/DataSeries.php
@@ -43,6 +43,8 @@ class DataSeries
     const EMPTY_AS_GAP = 'gap';
     const EMPTY_AS_ZERO = 'zero';
     const EMPTY_AS_SPAN = 'span';
+    const DEFAULT_EMPTY_AS = self::EMPTY_AS_GAP;
+    const VALID_EMPTY_AS = [self::EMPTY_AS_GAP, self::EMPTY_AS_ZERO, self::EMPTY_AS_SPAN];
 
     /**
      * Series Plot Type.
diff --git a/src/PhpSpreadsheet/Reader/BaseReader.php b/src/PhpSpreadsheet/Reader/BaseReader.php
index 49707f3de..de80834d1 100644
--- a/src/PhpSpreadsheet/Reader/BaseReader.php
+++ b/src/PhpSpreadsheet/Reader/BaseReader.php
@@ -257,9 +257,4 @@ abstract class BaseReader implements IReader
 
         return $this;
     }
-
-    protected function newSpreadsheet(): Spreadsheet
-    {
-        return new Spreadsheet();
-    }
 }
diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php
index da4cfb4ee..cd69f2ebe 100644
--- a/src/PhpSpreadsheet/Reader/Csv.php
+++ b/src/PhpSpreadsheet/Reader/Csv.php
@@ -257,7 +257,8 @@ class Csv extends BaseReader
      */
     protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
     {
-        $spreadsheet = $this->newSpreadsheet();
+        // Create new Spreadsheet
+        $spreadsheet = new Spreadsheet();
         $spreadsheet->setValueBinder($this->valueBinder);
 
         // Load into this instance
@@ -269,7 +270,8 @@ class Csv extends BaseReader
      */
     public function loadSpreadsheetFromString(string $contents): Spreadsheet
     {
-        $spreadsheet = $this->newSpreadsheet();
+        // Create new Spreadsheet
+        $spreadsheet = new Spreadsheet();
         $spreadsheet->setValueBinder($this->valueBinder);
 
         // Load into this instance
diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php
index 35122c4d9..5b2c1a52e 100644
--- a/src/PhpSpreadsheet/Reader/Gnumeric.php
+++ b/src/PhpSpreadsheet/Reader/Gnumeric.php
@@ -231,7 +231,8 @@ class Gnumeric extends BaseReader
      */
     protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
     {
-        $spreadsheet = $this->newSpreadsheet();
+        // Create new Spreadsheet
+        $spreadsheet = new Spreadsheet();
         $spreadsheet->setValueBinder($this->valueBinder);
         $spreadsheet->removeSheetByIndex(0);
 
diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php
index 6253c6785..a733e0db1 100644
--- a/src/PhpSpreadsheet/Reader/Html.php
+++ b/src/PhpSpreadsheet/Reader/Html.php
@@ -210,7 +210,8 @@ class Html extends BaseReader
      */
     public function loadSpreadsheetFromFile(string $filename): Spreadsheet
     {
-        $spreadsheet = $this->newSpreadsheet();
+        // Create new Spreadsheet
+        $spreadsheet = new Spreadsheet();
         $spreadsheet->setValueBinder($this->valueBinder);
 
         // Load into this instance
@@ -825,7 +826,7 @@ class Html extends BaseReader
         if ($loaded === false) {
             throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null);
         }
-        $spreadsheet = $spreadsheet ?? $this->newSpreadsheet();
+        $spreadsheet = $spreadsheet ?? new Spreadsheet();
         $spreadsheet->setValueBinder($this->valueBinder);
         self::loadProperties($dom, $spreadsheet);
 
@@ -1224,7 +1225,7 @@ class Html extends BaseReader
     public function listWorksheetInfo(string $filename): array
     {
         $info = [];
-        $spreadsheet = $this->newSpreadsheet();
+        $spreadsheet = new Spreadsheet();
         $this->loadIntoExisting($filename, $spreadsheet);
         foreach ($spreadsheet->getAllSheets() as $sheet) {
             $newEntry = ['worksheetName' => $sheet->getTitle()];
diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php
index 38a5ced19..77e073995 100644
--- a/src/PhpSpreadsheet/Reader/Ods.php
+++ b/src/PhpSpreadsheet/Reader/Ods.php
@@ -229,7 +229,8 @@ class Ods extends BaseReader
      */
     protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
     {
-        $spreadsheet = $this->newSpreadsheet();
+        // Create new Spreadsheet
+        $spreadsheet = new Spreadsheet();
         $spreadsheet->setValueBinder($this->valueBinder);
         $spreadsheet->removeSheetByIndex(0);
 
diff --git a/src/PhpSpreadsheet/Reader/Slk.php b/src/PhpSpreadsheet/Reader/Slk.php
index 21c069ef5..10995c5b4 100644
--- a/src/PhpSpreadsheet/Reader/Slk.php
+++ b/src/PhpSpreadsheet/Reader/Slk.php
@@ -144,7 +144,8 @@ class Slk extends BaseReader
      */
     protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
     {
-        $spreadsheet = $this->newSpreadsheet();
+        // Create new Spreadsheet
+        $spreadsheet = new Spreadsheet();
         $spreadsheet->setValueBinder($this->valueBinder);
 
         // Load into this instance
diff --git a/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php b/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php
index e94fbccc2..4683ec224 100644
--- a/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php
+++ b/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php
@@ -26,7 +26,7 @@ class LoadSpreadsheet extends Xls
         $xls->loadOLE($filename);
 
         // Initialisations
-        $xls->spreadsheet = $this->newSpreadsheet();
+        $xls->spreadsheet = new Spreadsheet();
         $xls->spreadsheet->setValueBinder($this->valueBinder);
         $xls->spreadsheet->removeSheetByIndex(0); // remove 1st sheet
         if (!$xls->readDataOnly) {
diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php
index f23df1344..2e1ea196b 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -400,7 +400,7 @@ class Xlsx extends BaseReader
         File::assertFile($filename, self::INITIAL_FILE);
 
         // Initialisations
-        $excel = $this->newSpreadsheet();
+        $excel = new Spreadsheet();
         $excel->setValueBinder($this->valueBinder);
         $excel->removeSheetByIndex(0);
         $addingFirstCellStyleXf = true;
diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php
index 76cd8a440..df370b2f7 100644
--- a/src/PhpSpreadsheet/Reader/Xml.php
+++ b/src/PhpSpreadsheet/Reader/Xml.php
@@ -243,7 +243,8 @@ class Xml extends BaseReader
      */
     public function loadSpreadsheetFromString(string $contents): Spreadsheet
     {
-        $spreadsheet = $this->newSpreadsheet();
+        // Create new Spreadsheet
+        $spreadsheet = new Spreadsheet();
         $spreadsheet->setValueBinder($this->valueBinder);
         $spreadsheet->removeSheetByIndex(0);
 
@@ -256,7 +257,8 @@ class Xml extends BaseReader
      */
     protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
     {
-        $spreadsheet = $this->newSpreadsheet();
+        // Create new Spreadsheet
+        $spreadsheet = new Spreadsheet();
         $spreadsheet->setValueBinder($this->valueBinder);
         $spreadsheet->removeSheetByIndex(0);
 
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
index afb901f78..8172fa22c 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
@@ -96,11 +96,9 @@ class Chart extends WriterPart
         $objWriter->writeAttribute('val', (string) (int) $chart->getPlotVisibleOnly());
         $objWriter->endElement();
 
-        if ($chart->getDisplayBlanksAs() !== '') {
-            $objWriter->startElement('c:dispBlanksAs');
-            $objWriter->writeAttribute('val', $chart->getDisplayBlanksAs());
-            $objWriter->endElement();
-        }
+        $objWriter->startElement('c:dispBlanksAs');
+        $objWriter->writeAttribute('val', $chart->getDisplayBlanksAs());
+        $objWriter->endElement();
 
         $objWriter->startElement('c:showDLblsOverMax');
         $objWriter->writeAttribute('val', '0');


Test patch:
diff --git a/tests/PhpSpreadsheetTests/Chart/DisplayBlanksAsTest.php b/tests/PhpSpreadsheetTests/Chart/DisplayBlanksAsTest.php
new file mode 100644
index 000000000..a1322c09a
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Chart/DisplayBlanksAsTest.php
@@ -0,0 +1,106 @@
+<?php
+
+declare(strict_types=1);
+
+namespace PhpOffice\PhpSpreadsheetTests\Chart;
+
+use PhpOffice\PhpSpreadsheet\Chart\Chart;
+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\Title;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PHPUnit\Framework\TestCase;
+
+class DisplayBlanksAsTest extends TestCase
+{
+    public function testDisplayBlanksAs(): 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],
+            ]
+        );
+
+        $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
+        ];
+
+        $xAxisTickValues = [
+            new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4
+        ];
+
+        $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(
+            DataSeries::TYPE_AREACHART, // plotType
+            DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping
+            range(0, count($dataSeriesValues) - 1), // plotOrder
+            $dataSeriesLabels, // plotLabel
+            $xAxisTickValues, // plotCategory
+            $dataSeriesValues          // plotValues
+        );
+
+        $plotArea = new PlotArea(null, [$series]);
+        $legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
+
+        $title = new Title('Test %age-Stacked Area Chart');
+        $yAxisLabel = new Title('Value ($k)');
+
+        $chart1 = new Chart(
+            'chart1', // name
+            $title, // title
+            $legend, // legend
+            $plotArea, // plotArea
+            true, // plotVisibleOnly
+            DataSeries::EMPTY_AS_GAP, // displayBlanksAs
+            null, // xAxisLabel
+            $yAxisLabel  // yAxisLabel
+        );
+        self::assertSame(DataSeries::EMPTY_AS_GAP, $chart1->getDisplayBlanksAs());
+        $chart1->setDisplayBlanksAs(DataSeries::EMPTY_AS_ZERO);
+        self::assertSame(DataSeries::EMPTY_AS_ZERO, $chart1->getDisplayBlanksAs());
+        $chart1->setDisplayBlanksAs('0');
+        self::assertSame(DataSeries::EMPTY_AS_GAP, $chart1->getDisplayBlanksAs(), 'invalid setting converted to default');
+
+        $chart2 = new Chart(
+            'chart2', // name
+            $title, // title
+            $legend, // legend
+            $plotArea, // plotArea
+            true, // plotVisibleOnly
+            DataSeries::EMPTY_AS_SPAN, // displayBlanksAs
+            null, // xAxisLabel
+            $yAxisLabel  // yAxisLabel
+        );
+        self::assertSame(DataSeries::EMPTY_AS_SPAN, $chart2->getDisplayBlanksAs());
+
+        $chart3 = new Chart(
+            'chart3', // name
+            $title, // title
+            $legend, // legend
+            $plotArea, // plotArea
+            true, // plotVisibleOnly
+            '0', // displayBlanksAs, PHPExcel default
+            null, // xAxisLabel
+            $yAxisLabel  // yAxisLabel
+        );
+        self::assertSame(DataSeries::EMPTY_AS_GAP, $chart3->getDisplayBlanksAs(), 'invalid setting converted to default');
+
+        $spreadsheet->disconnectWorksheets();
+    }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/MySpreadsheet.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/MySpreadsheet.php
deleted file mode 100644
index b41122797..000000000
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/MySpreadsheet.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
-
-use PhpOffice\PhpSpreadsheet\Spreadsheet;
-
-class MySpreadsheet extends Spreadsheet
-{
-    public function calcSquare(string $cellAddress): float|int|string
-    {
-        $value = $this->getActiveSheet()
-            ->getCell($cellAddress)
-            ->getValue();
-        if (is_numeric($value)) {
-            return $value * $value;
-        }
-
-        return '#VALUE!';
-    }
-}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/MyXlsxReader.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/MyXlsxReader.php
deleted file mode 100644
index c537f7863..000000000
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/MyXlsxReader.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
-
-use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
-use PhpOffice\PhpSpreadsheet\Spreadsheet;
-
-class MyXlsxReader extends XlsxReader
-{
-    protected function newSpreadsheet(): Spreadsheet
-    {
-        return new MySpreadsheet();
-    }
-}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/MyXlsxTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/MyXlsxTest.php
deleted file mode 100644
index ed726311b..000000000
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/MyXlsxTest.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
-
-use PHPUnit\Framework\TestCase;
-
-class MyXlsxTest extends TestCase
-{
-    public function testCustomSpreadsheetCustomLoader(): void
-    {
-        $reader = new MyXlsxReader();
-        $infile = 'tests/data/Reader/XLSX/colorscale.xlsx';
-        /** @var MySpreadsheet */
-        $mySpreadsheet = $reader->load($infile);
-        self::assertSame(64, $mySpreadsheet->calcSquare('A3'));
-        $mySpreadsheet->disconnectWorksheets();
-    }
-}