Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graph custom colors #768

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).

### Added

- Possibility to specify custom colors of values for pie and donut charts (https://github.com/PHPOffice/PhpSpreadsheet/pull/768)
- Support page margin in mPDF - [#750](https://github.com/PHPOffice/PhpSpreadsheet/issues/750)

### Fixed
Expand Down
183 changes: 183 additions & 0 deletions samples/Chart/33_Chart_create_pie_custom_colors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php

use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\Layout;
use PhpOffice\PhpSpreadsheet\Chart\Legend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\IOFactory;
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, 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_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 Legend(Legend::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
0, // 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
0, // 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);
36 changes: 29 additions & 7 deletions src/PhpSpreadsheet/Chart/DataSeriesValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ class DataSeriesValues
private $dataValues = [];

/**
* Fill color.
* Fill color (can be array with colors if dataseries have custom colors).
*
* @var string
* @var string|string[]
*/
private $fillColor;

Expand All @@ -82,7 +82,7 @@ class DataSeriesValues
* @param int $pointCount
* @param mixed $dataValues
* @param null|mixed $marker
* @param null|string $fillColor
* @param null|string|string[] $fillColor
*/
public function __construct($dataType = self::DATASERIES_TYPE_NUMBER, $dataSource = null, $formatCode = null, $pointCount = 0, $dataValues = [], $marker = null, $fillColor = null)
{
Expand Down Expand Up @@ -214,7 +214,7 @@ public function getPointCount()
/**
* Get fill color.
*
* @return string HEX color
* @return string|string[] HEX color or array with HEX colors
*/
public function getFillColor()
{
Expand All @@ -224,20 +224,42 @@ public function getFillColor()
/**
* Set fill color for series.
*
* @param string $color HEX color
* @param string|string[] $color HEX color or array with HEX colors
*
* @return DataSeriesValues
*/
public function setFillColor($color)
{
if (!preg_match('/^[a-f0-9]{6}$/i', $color)) {
throw new Exception('Invalid hex color for chart series');
if (is_array($color)) {
foreach ($color as $colorValue) {
$this->validateColor($colorValue);
}
} else {
$this->validateColor($color);
}
$this->fillColor = $color;

return $this;
}

/**
* Method for validating hex color.
*
* @param string $color value for color
*
* @throws \Exception thrown if color is invalid
*
* @return bool true if validation was successful
*/
private function validateColor($color)
{
if (!preg_match('/^[a-f0-9]{6}$/i', $color)) {
throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color));
}

return true;
}

/**
* Get line width for series.
*
Expand Down
65 changes: 44 additions & 21 deletions src/PhpSpreadsheet/Writer/Xlsx/Chart.php
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,38 @@ private static function getChartType($plotArea)
return $chartType;
}

/**
* Method writing plot series values.
*
* @param XMLWriter $objWriter XML Writer
* @param int $val value for idx (default: 3)
* @param string $fillColor hex color (default: FF9900)
*
* @return XMLWriter XML Writer
*/
private function writePlotSeriesValuesElement($objWriter, $val = 3, $fillColor = 'FF9900')
{
$objWriter->startElement('c:dPt');
$objWriter->startElement('c:idx');
$objWriter->writeAttribute('val', $val);
$objWriter->endElement();

$objWriter->startElement('c:bubble3D');
$objWriter->writeAttribute('val', 0);
$objWriter->endElement();

$objWriter->startElement('c:spPr');
$objWriter->startElement('a:solidFill');
$objWriter->startElement('a:srgbClr');
$objWriter->writeAttribute('val', $fillColor);
$objWriter->endElement();
$objWriter->endElement();
$objWriter->endElement();
$objWriter->endElement();

return $objWriter;
}

/**
* Write Plot Group (series of related plots).
*
Expand Down Expand Up @@ -1078,7 +1110,7 @@ private function writePlotGroup($plotGroup, $groupType, $objWriter, &$catIsMulti
$plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);
if ($plotLabel) {
$fillColor = $plotLabel->getFillColor();
if ($fillColor !== null) {
if ($fillColor !== null && !is_array($fillColor)) {
$objWriter->startElement('c:spPr');
$objWriter->startElement('a:solidFill');
$objWriter->startElement('a:srgbClr');
Expand All @@ -1097,24 +1129,18 @@ private function writePlotGroup($plotGroup, $groupType, $objWriter, &$catIsMulti
$objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesRef);
$objWriter->endElement();

if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
$objWriter->startElement('c:dPt');
$objWriter->startElement('c:idx');
$objWriter->writeAttribute('val', 3);
$objWriter->endElement();

$objWriter->startElement('c:bubble3D');
$objWriter->writeAttribute('val', 0);
$objWriter->endElement();
// Values
$plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef);

$objWriter->startElement('c:spPr');
$objWriter->startElement('a:solidFill');
$objWriter->startElement('a:srgbClr');
$objWriter->writeAttribute('val', 'FF9900');
$objWriter->endElement();
$objWriter->endElement();
$objWriter->endElement();
$objWriter->endElement();
if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
$fillColorValues = $plotSeriesValues->getFillColor();
if ($fillColorValues !== null && is_array($fillColorValues)) {
foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) {
$this->writePlotSeriesValuesElement($objWriter, $dataKey, (isset($fillColorValues[$dataKey]) ? $fillColorValues[$dataKey] : 'FF9900'));
}
} else {
$this->writePlotSeriesValuesElement($objWriter);
}
}

// Labels
Expand All @@ -1127,9 +1153,6 @@ private function writePlotGroup($plotGroup, $groupType, $objWriter, &$catIsMulti
$objWriter->endElement();
}

// Values
$plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was moved to line 1100 ($plotSeriesValues is needed earlier).


// Formatting for the points
if (($groupType == DataSeries::TYPE_LINECHART) || ($groupType == DataSeries::TYPE_STOCKCHART)) {
$plotLineWidth = 12700;
Expand Down
26 changes: 26 additions & 0 deletions tests/PhpSpreadsheetTests/Chart/DataSeriesValuesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,30 @@ public function testGetLineWidth()
$testInstance->setLineWidth(1);
self::assertEquals(12700, $testInstance->getLineWidth(), 'should enforce minimum width');
}

public function testFillColorCorrectInput()
{
$testInstance = new DataSeriesValues();

self::assertEquals($testInstance, $testInstance->setFillColor('00abb8'));
self::assertEquals($testInstance, $testInstance->setFillColor(['00abb8', 'b8292f']));
}

public function testFillColorInvalidInput()
{
$testInstance = new DataSeriesValues();
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Invalid hex color for chart series');

$testInstance->setFillColor('WRONG COLOR');
}

public function testFillColorInvalidInputInArray()
{
$testInstance = new DataSeriesValues();
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Invalid hex color for chart series (color: "WRONG COLOR")');

$testInstance->setFillColor(['b8292f', 'WRONG COLOR']);
}
}