Chart DataTable

Fix #413, which went stale in 2018 and is now reopened. A DataTable can be used on a chart in place of a Legend (or in addition to one, but it doesn't really make sense to have both). The fix was substantially developed by @topaDev before the issue went stale. This PR provides basic support for reading and writing DataTables on charts. The new sample on this PR shows them in action. They have obscure features which are not implemented (e.g. `glow`); those will have to wait for another day.
This commit is contained in:
oleibman
2026-06-11 06:43:42 -07:00
parent fd5f72edd7
commit dbea64cb03
6 changed files with 419 additions and 1 deletions
+108
View File
@@ -0,0 +1,108 @@
<?php
use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\DataTable;
//use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Theme as SpreadsheetTheme;
require __DIR__ . '/../Header.php';
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
$spreadsheet = new Spreadsheet();
// same as 33_Chart_create_area, but with 2013+ schemes
$spreadsheet->getTheme()->setThemeColorName(SpreadsheetTheme::COLOR_SCHEME_2013_2022_NAME);
$worksheet = $spreadsheet->getActiveSheet();
$sheet2 = $spreadsheet->createSheet();
$sheet2->setTitle('Sheet2');
$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(
DataSeries::TYPE_AREACHART, // plotType
DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping
range(0, count($dataSeriesValues) - 1), // plotOrder
$dataSeriesLabels, // plotLabel
$xAxisTickValues, // plotCategory
$dataSeriesValues // plotValues
);
// Set the series in the plot area
$plotArea = new PlotArea(null, [$series]);
$plotArea->setDataTable(new DataTable());
// No need for Legend if using DataTable
$legend = null; //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('A1');
$chart->setBottomRightPosition('H18');
// Add the chart to the worksheet
$sheet2->addChart($chart);
$spreadsheet->setActiveSheetIndex(1);
$helper->renderChart($chart, __FILE__);
// Save Excel 2007 file
$helper->write($spreadsheet, __FILE__, ['Xlsx'], true, null, false);
+62
View File
@@ -0,0 +1,62 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Chart;
class DataTable
{
private bool $showHorizontalBorder = true;
private bool $showVerticalBorder = true;
private bool $showOutline = true;
private bool $showKeys = true;
public function getShowHorizontalBorder(): bool
{
return $this->showHorizontalBorder;
}
public function getShowVerticalBorder(): bool
{
return $this->showVerticalBorder;
}
public function getShowOutline(): bool
{
return $this->showOutline;
}
public function getShowKeys(): bool
{
return $this->showKeys;
}
public function setShowHorizontalBorder(bool $showHorizontalBorder): self
{
$this->showHorizontalBorder = $showHorizontalBorder;
return $this;
}
public function setShowVerticalBorder(bool $showVerticalBorder): self
{
$this->showVerticalBorder = $showVerticalBorder;
return $this;
}
public function setShowOutline(bool $showOutline): self
{
$this->showOutline = $showOutline;
return $this;
}
public function setShowKeys(bool $showKeys): self
{
$this->showKeys = $showKeys;
return $this;
}
}
+14
View File
@@ -38,6 +38,8 @@ class PlotArea
*/
private array $plotSeries;
private ?DataTable $dataTable = null;
/**
* Create a new PlotArea.
*
@@ -195,6 +197,18 @@ class PlotArea
return $this;
}
public function setDataTable(DataTable $dataTable): self
{
$this->dataTable = $dataTable;
return $this;
}
public function getDataTable(): ?DataTable
{
return $this->dataTable;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
+34 -1
View File
@@ -8,6 +8,7 @@ use PhpOffice\PhpSpreadsheet\Chart\AxisText;
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\DataTable;
use PhpOffice\PhpSpreadsheet\Chart\GridLines;
use PhpOffice\PhpSpreadsheet\Chart\Layout;
use PhpOffice\PhpSpreadsheet\Chart\Legend;
@@ -137,7 +138,7 @@ class Chart
break;
case 'plotArea':
$plotAreaLayout = $XaxisLabel = $YaxisLabel = null;
$plotAreaLayout = $XaxisLabel = $YaxisLabel = $dataTable = null;
$plotSeries = $plotAttributes = [];
$catAxRead = false;
$plotNoFill = false;
@@ -358,6 +359,10 @@ class Chart
}
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'dTable':
$dataTable = $this->readDataTable($chartDetail);
break;
}
}
@@ -365,6 +370,11 @@ class Chart
$plotAreaLayout = new Layout();
}
$plotArea = new PlotArea($plotAreaLayout, $plotSeries);
if ($dataTable !== null) {
$plotArea->setDataTable(
$dataTable
);
}
$this->setChartAttributes($plotAreaLayout, $plotAttributes);
if ($plotNoFill) {
$plotArea->setNoFill(true);
@@ -1295,6 +1305,29 @@ class Chart
return $plotAttributes;
}
private function readDataTable(SimpleXMLElement $chartDetail): DataTable
{
$dataTable = new DataTable();
$temp = self::getAttributeBoolean($chartDetail->showHorzBorder, 'val');
if ($temp !== null) {
$dataTable->setShowHorizontalBorder($temp);
}
$temp = self::getAttributeBoolean($chartDetail->showVertBorder, 'val');
if ($temp !== null) {
$dataTable->setShowVerticalBorder($temp);
}
$temp = self::getAttributeBoolean($chartDetail->showOutline, 'val');
if ($temp !== null) {
$dataTable->setShowOutline($temp);
}
$temp = self::getAttributeBoolean($chartDetail->showKeys, 'val');
if ($temp !== null) {
$dataTable->setShowKeys($temp);
}
return $dataTable;
}
/** @param array<mixed> $plotAttributes */
private function setChartAttributes(Layout $plotArea, array $plotAttributes): void
{
+28
View File
@@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Chart\Chart as SpreadsheetChart;
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\DataTable;
use PhpOffice\PhpSpreadsheet\Chart\Layout;
use PhpOffice\PhpSpreadsheet\Chart\Legend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
@@ -444,6 +445,10 @@ class Chart extends WriterPart
$this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id2, $id1, $catIsMultiLevelSeries, $xAxis ?? new Axis());
} else {
$this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $xAxis ?? new Axis());
$dataTable = $plotArea->getDataTable();
if ($dataTable !== null) {
$this->writeDataTable($objWriter, $dataTable);
}
}
$this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $yAxis ?? new Axis());
@@ -482,6 +487,29 @@ class Chart extends WriterPart
$objWriter->endElement(); // c:plotArea
}
private function writeDataTable(XMLWriter $objWriter, DataTable $dataTable): void
{
$objWriter->startElement('c:dTable');
$objWriter->startElement('c:showHorzBorder');
$objWriter->writeAttribute('val', $dataTable->getShowHorizontalBorder() ? '1' : '0');
$objWriter->endElement();
$objWriter->startElement('c:showVertBorder');
$objWriter->writeAttribute('val', $dataTable->getShowVerticalBorder() ? '1' : '0');
$objWriter->endElement();
$objWriter->startElement('c:showOutline');
$objWriter->writeAttribute('val', $dataTable->getShowOutline() ? '1' : '0');
$objWriter->endElement();
$objWriter->startElement('c:showKeys');
$objWriter->writeAttribute('val', $dataTable->getShowKeys() ? '1' : '0');
$objWriter->endElement();
$objWriter->endElement(); // c:dTable
}
private function writeDataLabelsBool(XMLWriter $objWriter, string $name, ?bool $value): void
{
if ($value !== null) {
@@ -0,0 +1,173 @@
<?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\DataTable;
//use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Theme as SpreadsheetTheme;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
class DataTableTest extends AbstractFunctional
{
public function testSetDataTable(): void
{
$dataTable = new DataTable();
self::assertTrue($dataTable->getShowHorizontalBorder());
self::assertTrue($dataTable->getShowVerticalBorder());
self::assertTrue($dataTable->getShowOutline());
self::assertTrue($dataTable->getShowKeys());
$dataTable->setShowHorizontalBorder(false)
->setShowVerticalBorder(false)
->setShowOutline(false)
->setShowKeys(false);
self::assertFalse($dataTable->getShowHorizontalBorder());
self::assertFalse($dataTable->getShowVerticalBorder());
self::assertFalse($dataTable->getShowOutline());
self::assertFalse($dataTable->getShowKeys());
}
private function readCharts(XlsxReader $reader): void
{
$reader->setIncludeCharts(true);
}
private function writeCharts(XlsxWriter $writer): void
{
$writer->setIncludeCharts(true);
}
public function testCopyDataTable(): void
{
$spreadsheet = new Spreadsheet();
// based on 33_Chart_create_area3.
$spreadsheet->getTheme()
->setThemeColorName(
SpreadsheetTheme::COLOR_SCHEME_2013_2022_NAME
);
$worksheet = $spreadsheet->getActiveSheet();
$sheet2 = $spreadsheet->createSheet();
$sheet2->setTitle('Sheet2');
$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(
DataSeries::TYPE_AREACHART, // plotType
DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping
range(0, count($dataSeriesValues) - 1), // plotOrder
$dataSeriesLabels, // plotLabel
$xAxisTickValues, // plotCategory
$dataSeriesValues // plotValues
);
// Set the series in the plot area
$plotArea = new PlotArea(null, [$series]);
$plotArea->setDataTable(new DataTable());
// No need for Legend if using DataTable
$legend = null; //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('A1');
$chart->setBottomRightPosition('H18');
// Add the chart to the worksheet
$sheet2->addChart($chart);
$spreadsheet->setActiveSheetIndex(1);
// Save Excel 2007 file
/** @var callable */
$callableReader = [$this, 'readCharts'];
/** @var callable */
$callableWriter = [$this, 'writeCharts'];
$reloadedSpreadsheet = $this->writeAndReload(
$spreadsheet,
'Xlsx',
$this->readCharts(...),
$this->writeCharts(...)
);
$spreadsheet->disconnectWorksheets();
$sheet = $reloadedSpreadsheet->getActiveSheet();
$charts2 = $sheet->getChartCollection();
self::assertCount(1, $charts2);
$chart2 = $charts2[0];
self::assertNotNull($chart2);
$plotArea = $chart2->getPlotArea();
self::assertNotNull($plotArea);
$dtab = $plotArea->getDataTable();
self::assertNotNull($dtab);
self::assertTrue($dtab->getShowHorizontalBorder());
self::assertTrue($dtab->getShowVerticalBorder());
self::assertTrue($dtab->getShowOutline());
self::assertTrue($dtab->getShowKeys());
$reloadedSpreadsheet->disconnectWorksheets();
}
}