mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-08-30 03:59:11 +00:00
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:
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user