From 3861f7e37e9453a9babc30d09ed08b361bb219bb Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 24 Aug 2022 19:31:55 -0700 Subject: [PATCH] Charts - Add Support for Date Axis (#3018) * Charts - Add Support for Date Axis Fix #2967. Fix #2969 (which had already been fixed prior to opening the issue, but had added urgency for Date Axes). Add ability to set axis type to date axis, in addition to original possiblities of value axis and category axis. * Update 33_Chart_create_line_dateaxis.php No idea why php-cs-fixer is complaining. It didn't do so when I first uploaded. I can't duplicate problem on my own system. Not enough detail in error message for me to act. Grasping at straws, I have moved the function definition (which is the only use of braces in the entire script) from the end of the script to the beginning. * Update 33_Chart_create_line_dateaxis.php Some comments were mis-aligned. This may be related to the reasons behind PR #3025, which didn't take care of this because this script had not yet been merged. --- .../Chart/33_Chart_create_line_dateaxis.php | 375 ++++++++++++++++++ .../33_Chart_create_scatter5_trendlines.php | 1 + .../32readwriteLineDateAxisChart1.xlsx | Bin 0 -> 12114 bytes src/PhpSpreadsheet/Chart/Axis.php | 26 +- src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 22 +- src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 42 +- .../Chart/AxisPropertiesTest.php | 10 +- .../Chart/Charts32CatAxValAxTest.php | 8 +- .../Chart/Charts32XmlTest.php | 41 ++ 9 files changed, 500 insertions(+), 25 deletions(-) create mode 100644 samples/Chart/33_Chart_create_line_dateaxis.php create mode 100644 samples/templates/32readwriteLineDateAxisChart1.xlsx diff --git a/samples/Chart/33_Chart_create_line_dateaxis.php b/samples/Chart/33_Chart_create_line_dateaxis.php new file mode 100644 index 00000000..1a47e5aa --- /dev/null +++ b/samples/Chart/33_Chart_create_line_dateaxis.php @@ -0,0 +1,375 @@ +getActiveSheet(); +$dataSheet->setTitle('Data'); +// changed data to simulate a trend chart - Xaxis are dates; Yaxis are 3 meausurements from each date +// Dates changed not to fall on exact quarter start +$dataSheet->fromArray( + [ + ['', 'date', 'metric1', 'metric2', 'metric3'], + ['=DATEVALUE(B2)', '2021-01-10', 12.1, 15.1, 21.1], + ['=DATEVALUE(B3)', '2021-04-21', 56.2, 73.2, 86.2], + ['=DATEVALUE(B4)', '2021-07-31', 52.2, 61.2, 69.2], + ['=DATEVALUE(B5)', '2021-10-11', 30.2, 22.2, 0.2], + ['=DATEVALUE(B6)', '2022-01-21', 40.1, 38.1, 65.1], + ['=DATEVALUE(B7)', '2022-04-11', 45.2, 44.2, 96.2], + ['=DATEVALUE(B8)', '2022-07-01', 52.2, 51.2, 55.2], + ['=DATEVALUE(B9)', '2022-10-31', 41.2, 72.2, 56.2], + ] +); + +$dataSheet->getStyle('A2:A9')->getNumberFormat()->setFormatCode(Properties::FORMAT_CODE_DATE_ISO8601); +$dataSheet->getColumnDimension('A')->setAutoSize(true); +$dataSheet->getColumnDimension('B')->setAutoSize(true); +$dataSheet->setSelectedCells('A1'); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$C$1', null, 1), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$D$1', null, 1), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$E$1', null, 1), +]; +// Set the X-Axis Labels +// NUMBER, not STRING +// added x-axis values for each of the 3 metrics +// added FORMATE_CODE_NUMBER +// Number of datapoints in series +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), +]; +// 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 +// Data Marker Color fill/[fill,Border] +// Data Marker size +// Color(s) added +// added FORMAT_CODE_NUMBER +$dataSeriesValues = [ + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_NUMBER, + 'Data!$C$2:$C$9', + Properties::FORMAT_CODE_NUMBER, + 8, + null, + 'diamond', + null, + 5 + ), + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_NUMBER, + 'Data!$D$2:$D$9', + Properties::FORMAT_CODE_NUMBER, + 8, + null, + 'square', + '*accent1', + 6 + ), + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_NUMBER, + 'Data!$E$2:$E$9', + Properties::FORMAT_CODE_NUMBER, + 8, + null, + null, + null, + 7 + ), // let Excel choose marker shape +]; +// series 1 - metric1 +// marker details +$dataSeriesValues[0] + ->getMarkerFillColor() + ->setColorProperties('0070C0', null, ChartColor::EXCEL_COLOR_TYPE_ARGB); +$dataSeriesValues[0] + ->getMarkerBorderColor() + ->setColorProperties('002060', null, ChartColor::EXCEL_COLOR_TYPE_ARGB); + +// line details - dashed, smooth line (Bezier) with arrows, 40% transparent +$dataSeriesValues[0] + ->setSmoothLine(true) + ->setScatterLines(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 - metric2, straight line - no special effects, connected +$dataSeriesValues[1] // square marker border color + ->getMarkerBorderColor() + ->setColorProperties('accent6', 3, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[1] // square marker fill color + ->getMarkerFillColor() + ->setColorProperties('0FFF00', null, ChartColor::EXCEL_COLOR_TYPE_ARGB); +$dataSeriesValues[1] + ->setScatterLines(true) + ->setSmoothLine(false) + ->setLineColorProperties('FF0000', 80, ChartColor::EXCEL_COLOR_TYPE_ARGB); +$dataSeriesValues[1]->setLineWidth(2.0); + +// series 3 - metric3, markers, no line +$dataSeriesValues[2] // triangle? fill + //->setPointMarker('triangle') // let Excel choose shape, which is predicted to be a triangle + ->getMarkerFillColor() + ->setColorProperties('FFFF00', null, ChartColor::EXCEL_COLOR_TYPE_ARGB); +$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_ISO8601); + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_SCATTERCHART, // plotType + null, // plotGrouping (Scatter charts don't have grouping) + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues, // plotValues + null, // plotDirection + null, // smooth line + DataSeries::STYLE_SMOOTHMARKER // 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); + +$title = new Title('Test Scatter Chart'); +$yAxisLabel = new Title('Value ($k)'); +$yAxis = new Axis(); +$yAxis->setMajorGridlines(new GridLines()); + +// 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 + // added xAxis for correct date display + $xAxis, // xAxis + $yAxis, // yAxis +); + +// Set the position of the chart in the chart sheet +$chart->setTopLeftPosition('A1'); +$chart->setBottomRightPosition('P12'); + +// create a 'Chart' worksheet, add $chart to it +$spreadsheet->createSheet(); +$chartSheet = $spreadsheet->getSheet(1); +$chartSheet->setTitle('Scatter+Line Chart'); + +$chartSheet = $spreadsheet->getSheetByName('Scatter+Line Chart'); +// Add the chart to the worksheet +$chartSheet->addChart($chart); + +// ------- Demonstrate Date Xaxis in Line Chart, not possible using Scatter Chart ------------ + +// Set the Labels (Column header) for each data series we want to plot +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$E$1', null, 1), +]; + +// Set the X-Axis Labels - dates, N.B. 01/10/2021 === Jan 10, NOT Oct 1 !! +// x-axis values are the Excel numeric representation of the date - so set +// formatCode=General for the xAxis VALUES, but we want the labels to be +// DISPLAYED as 'yyyy-mm-dd' That is, read a number, display a date. +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), +]; + +// X axis (date) settings +$xAxisLabel = new Title('Date'); +$xAxis = new Axis(); +$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601); // yyyy-mm-dd + +// Set the Data values for each data series we want to plot +$dataSeriesValues = [ + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_NUMBER, + 'Data!$E$2:$E$9', + Properties::FORMAT_CODE_NUMBER, + 8, + null, + 'triangle', + null, + 7 + ), +]; + +// series - metric3, markers, no line +$dataSeriesValues[0] + ->setScatterlines(false); // disable connecting lines +$dataSeriesValues[0] + ->getMarkerFillColor() + ->setColorProperties('FFFF00', null, ChartColor::EXCEL_COLOR_TYPE_ARGB); +$dataSeriesValues[0] + ->getMarkerBorderColor() + ->setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); + +// Build the dataseries +// must now use LineChart instead of ScatterChart, since ScatterChart does not +// support "dateAx" axis type. +$series = new DataSeries( + DataSeries::TYPE_LINECHART, // plotType + 'standard', // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues, // plotValues + null, // plotDirection + false, // smooth line + DataSeries::STYLE_LINEMARKER // plotStyle + // DataSeries::STYLE_SMOOTHMARKER // plotStyle +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); + +$title = new Title('Test Line-Chart with Date Axis - metric3 values'); + +// X axis (date) settings +$xAxisLabel = new Title('Game Date'); +$xAxis = new Axis(); +// date axis values are Excel numbers, not yyyy-mm-dd Date strings +$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601); + +$xAxis->setAxisType('dateAx'); // dateAx available ONLY for LINECHART, not SCATTERCHART + +// measure the time span in Quarters, of data. +$dateMinMax = dateRange(8, $spreadsheet); // array 'min'=>earliest date of first Q, 'max'=>latest date of final Q +// change xAxis tick marks to match Qtr boundaries + +$nQtrs = sprintf('%3.2f', (($dateMinMax['max'] - $dateMinMax['min']) / 30.5) / 4); +$tickMarkInterval = ($nQtrs > 20) ? 6 : 3; // tick marks every ? months + +$xAxis->setAxisOptionsProperties( + Properties::AXIS_LABELS_NEXT_TO, // axis_label pos + null, // horizontalCrossesValue + null, // horizontalCrosses + null, // axisOrientation + 'in', // major_tick_mark + null, // minor_tick_mark + $dateMinMax['min'], // minimum calculate this from the earliest data: 'Data!$A$2' + $dateMinMax['max'], // maximum calculate this from the last data: 'Data!$A$'.($nrows+1) + $tickMarkInterval, // majorUnit determines tickmarks & Gridlines ? + null, // minorUnit + null, // textRotation + null, // hidden + 'days', // baseTimeUnit + 'months', // majorTimeUnit, + 'months', // minorTimeUnit +); + +$yAxisLabel = new Title('Value ($k)'); +$yAxis = new Axis(); +$yAxis->setMajorGridlines(new GridLines()); +$xAxis->setMajorGridlines(new GridLines()); +$minorGridLines = new GridLines(); +$minorGridLines->activateObject(); +$xAxis->setMinorGridlines($minorGridLines); + +// Create the chart +$chart = new Chart( + 'chart2', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel, // yAxisLabel + // added xAxis for correct date display + $xAxis, // xAxis + $yAxis, // yAxis +); + +// Set the position of the chart in the chart sheet below the first chart +$chart->setTopLeftPosition('A13'); +$chart->setBottomRightPosition('P25'); +$chart->setRoundedCorners('true'); // Rounded corners in Chart Outline + +// Add the chart to the worksheet $chartSheet +$chartSheet->addChart($chart); +$spreadsheet->setActiveSheetIndex(1); + +// 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); +$spreadsheet->disconnectWorksheets(); + +function dateRange(int $nrows, Spreadsheet $wrkbk): array +{ + $dataSheet = $wrkbk->getSheetByName('Data'); + + // start the xaxis at the beginning of the quarter of the first date + $startDateStr = $dataSheet->getCell('B2')->getValue(); // yyyy-mm-dd date string + $startDate = DateTime::createFromFormat('Y-m-d', $startDateStr); // php date obj + + // get date of first day of the quarter of the start date + $startMonth = $startDate->format('n'); // suppress leading zero + $startYr = $startDate->format('Y'); + $qtr = intdiv($startMonth, 3) + (($startMonth % 3 > 0) ? 1 : 0); + $qtrStartMonth = sprintf('%02d', 1 + (($qtr - 1) * 3)); + $qtrStartStr = "$startYr-$qtrStartMonth-01"; + $ExcelQtrStartDateVal = SharedDate::convertIsoDate($qtrStartStr); + + // end the xaxis at the end of the quarter of the last date + $lastDateStr = $dataSheet->getCellByColumnAndRow(2, $nrows + 1)->getValue(); + $lastDate = DateTime::createFromFormat('Y-m-d', $lastDateStr); + $lastMonth = $lastDate->format('n'); + $lastYr = $lastDate->format('Y'); + $qtr = intdiv($lastMonth, 3) + (($lastMonth % 3 > 0) ? 1 : 0); + $qtrEndMonth = 3 + (($qtr - 1) * 3); + $lastDOM = cal_days_in_month(CAL_GREGORIAN, $qtrEndMonth, $lastYr); + $qtrEndMonth = sprintf('%02d', $qtrEndMonth); + $qtrEndStr = "$lastYr-$qtrEndMonth-$lastDOM"; + $ExcelQtrEndDateVal = SharedDate::convertIsoDate($qtrEndStr); + + $minMaxDates = ['min' => $ExcelQtrStartDateVal, 'max' => $ExcelQtrEndDateVal]; + + return $minMaxDates; +} diff --git a/samples/Chart/33_Chart_create_scatter5_trendlines.php b/samples/Chart/33_Chart_create_scatter5_trendlines.php index da831fa9..a640f735 100644 --- a/samples/Chart/33_Chart_create_scatter5_trendlines.php +++ b/samples/Chart/33_Chart_create_scatter5_trendlines.php @@ -263,6 +263,7 @@ $chart->setBottomRightPosition('P25'); // Add the chart to the worksheet $chartSheet $chartSheet->addChart($chart); +$spreadsheet->setActiveSheetIndex(1); // Save Excel 2007 file $filename = $helper->getFilename(__FILE__); diff --git a/samples/templates/32readwriteLineDateAxisChart1.xlsx b/samples/templates/32readwriteLineDateAxisChart1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..42470303aa766a74253901e16ead0683db938d13 GIT binary patch literal 12114 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 null, 'textRotation' => null, 'hidden' => null, + 'majorTimeUnit' => self::TIME_UNIT_YEARS, + 'minorTimeUnit' => self::TIME_UNIT_MONTHS, + 'baseTimeUnit' => self::TIME_UNIT_DAYS, ]; /** @@ -74,6 +85,7 @@ class Axis extends Properties private const NUMERIC_FORMAT = [ Properties::FORMAT_CODE_NUMBER, Properties::FORMAT_CODE_DATE, + Properties::FORMAT_CODE_DATE_ISO8601, ]; /** @@ -115,12 +127,12 @@ class Axis extends Properties public function getAxisIsNumericFormat(): bool { - return (bool) $this->axisNumber['numeric']; + return $this->axisType === self::AXIS_TYPE_DATE || (bool) $this->axisNumber['numeric']; } public function setAxisOption(string $key, ?string $value): void { - if (!empty($value)) { + if ($value !== null && $value !== '') { $this->axisOptions[$key] = $value; } } @@ -140,7 +152,10 @@ class Axis extends Properties ?string $majorUnit = null, ?string $minorUnit = null, ?string $textRotation = null, - ?string $hidden = null + ?string $hidden = null, + ?string $baseTimeUnit = null, + ?string $majorTimeUnit = null, + ?string $minorTimeUnit = null ): void { $this->axisOptions['axis_labels'] = $axisLabels; $this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue); @@ -154,6 +169,9 @@ class Axis extends Properties $this->setAxisOption('minor_unit', $minorUnit); $this->setAxisOption('textRotation', $textRotation); $this->setAxisOption('hidden', $hidden); + $this->setAxisOption('baseTimeUnit', $baseTimeUnit); + $this->setAxisOption('majorTimeUnit', $majorTimeUnit); + $this->setAxisOption('minorTimeUnit', $minorTimeUnit); } /** @@ -185,7 +203,7 @@ class Axis extends Properties public function setAxisType(string $type): self { - if ($type === 'catAx' || $type === 'valAx') { + if ($type === self::AXIS_TYPE_CATEGORY || $type === self::AXIS_TYPE_VALUE || $type === self::AXIS_TYPE_DATE) { $this->axisType = $type; } else { $this->axisType = ''; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index dab2b410..76316db9 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -139,12 +139,13 @@ class Chart $plotAreaLayout = $this->chartLayoutDetails($chartDetail); break; - case 'catAx': + case Axis::AXIS_TYPE_CATEGORY: + case Axis::AXIS_TYPE_DATE: $catAxRead = true; if (isset($chartDetail->title)) { $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); } - $xAxis->setAxisType('catAx'); + $xAxis->setAxisType($chartDetailKey); $this->readEffects($chartDetail, $xAxis); if (isset($chartDetail->spPr)) { $sppr = $chartDetail->spPr->children($this->aNamespace); @@ -173,13 +174,7 @@ class Chart $this->setAxisProperties($chartDetail, $xAxis); break; - case 'dateAx': - if (isset($chartDetail->title)) { - $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); - } - - break; - case 'valAx': + case Axis::AXIS_TYPE_VALUE: $whichAxis = null; $axPos = null; if (isset($chartDetail->axPos)) { @@ -1375,6 +1370,15 @@ class Chart if (isset($chartDetail->minorUnit)) { $whichAxis->setAxisOption('minor_unit', (string) self::getAttribute($chartDetail->minorUnit, 'val', 'string')); } + if (isset($chartDetail->baseTimeUnit)) { + $whichAxis->setAxisOption('baseTimeUnit', (string) self::getAttribute($chartDetail->baseTimeUnit, 'val', 'string')); + } + if (isset($chartDetail->majorTimeUnit)) { + $whichAxis->setAxisOption('majorTimeUnit', (string) self::getAttribute($chartDetail->majorTimeUnit, 'val', 'string')); + } + if (isset($chartDetail->minorTimeUnit)) { + $whichAxis->setAxisOption('minorTimeUnit', (string) self::getAttribute($chartDetail->minorTimeUnit, 'val', 'string')); + } if (isset($chartDetail->txPr)) { $children = $chartDetail->txPr->children($this->aNamespace); if (isset($children->bodyPr)) { diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index ad746a0a..48f7d255 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -490,13 +490,14 @@ class Chart extends WriterPart private function writeCategoryAxis(XMLWriter $objWriter, ?Title $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void { // 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->getAxisType() !== '') { - $objWriter->startElement('c:' . $yAxis->getAxisType()); + // In that case, xAxis may contain values like the yAxis, or it may be a date axis (LINECHART). + $axisType = $yAxis->getAxisType(); + if ($axisType !== '') { + $objWriter->startElement("c:$axisType"); } elseif ($yAxis->getAxisIsNumericFormat()) { - $objWriter->startElement('c:valAx'); + $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE); } else { - $objWriter->startElement('c:catAx'); + $objWriter->startElement('c:' . Axis::AXIS_TYPE_CATEGORY); } $majorGridlines = $yAxis->getMajorGridlines(); $minorGridlines = $yAxis->getMinorGridlines(); @@ -654,7 +655,8 @@ class Chart extends WriterPart } $objWriter->startElement('c:auto'); - $objWriter->writeAttribute('val', '1'); + // LineChart with dateAx wants '0' + $objWriter->writeAttribute('val', ($axisType === Axis::AXIS_TYPE_DATE) ? '0' : '1'); $objWriter->endElement(); $objWriter->startElement('c:lblAlgn'); @@ -665,6 +667,30 @@ class Chart extends WriterPart $objWriter->writeAttribute('val', '100'); $objWriter->endElement(); + if ($axisType === Axis::AXIS_TYPE_DATE) { + $property = 'baseTimeUnit'; + $propertyVal = $yAxis->getAxisOptionsProperty($property); + if (!empty($propertyVal)) { + $objWriter->startElement("c:$property"); + $objWriter->writeAttribute('val', $propertyVal); + $objWriter->endElement(); + } + $property = 'majorTimeUnit'; + $propertyVal = $yAxis->getAxisOptionsProperty($property); + if (!empty($propertyVal)) { + $objWriter->startElement("c:$property"); + $objWriter->writeAttribute('val', $propertyVal); + $objWriter->endElement(); + } + $property = 'minorTimeUnit'; + $propertyVal = $yAxis->getAxisOptionsProperty($property); + if (!empty($propertyVal)) { + $objWriter->startElement("c:$property"); + $objWriter->writeAttribute('val', $propertyVal); + $objWriter->endElement(); + } + } + if ($isMultiLevelSeries) { $objWriter->startElement('c:noMultiLvlLbl'); $objWriter->writeAttribute('val', '0'); @@ -683,7 +709,7 @@ class Chart extends WriterPart */ private function writeValueAxis(XMLWriter $objWriter, ?Title $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis): void { - $objWriter->startElement('c:valAx'); + $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE); $majorGridlines = $xAxis->getMajorGridlines(); $minorGridlines = $xAxis->getMinorGridlines(); @@ -1079,7 +1105,7 @@ class Chart extends WriterPart $objWriter->endElement(); // a:ln } } - $nofill = $groupType == DataSeries::TYPE_STOCKCHART || ($groupType === DataSeries::TYPE_SCATTERCHART && !$plotSeriesValues->getScatterLines()); + $nofill = $groupType === DataSeries::TYPE_STOCKCHART || (($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) && !$plotSeriesValues->getScatterLines()); if ($callLineStyles) { $this->writeLineStyles($objWriter, $plotSeriesValues, $nofill); $this->writeEffects($objWriter, $plotSeriesValues); diff --git a/tests/PhpSpreadsheetTests/Chart/AxisPropertiesTest.php b/tests/PhpSpreadsheetTests/Chart/AxisPropertiesTest.php index 91df25cb..0bfc2966 100644 --- a/tests/PhpSpreadsheetTests/Chart/AxisPropertiesTest.php +++ b/tests/PhpSpreadsheetTests/Chart/AxisPropertiesTest.php @@ -109,7 +109,9 @@ class AxisPropertiesTest extends AbstractFunctional '8', //minimum '68', //maximum '20', //majorUnit - '5' //minorUnit + '5', //minorUnit + '6', //textRotation + '0', //hidden ); self::assertSame(Properties::AXIS_LABELS_HIGH, $xAxis->getAxisOptionsProperty('axis_labels')); self::assertNull($xAxis->getAxisOptionsProperty('horizontal_crosses_value')); @@ -121,6 +123,8 @@ class AxisPropertiesTest extends AbstractFunctional self::assertSame('68', $xAxis->getAxisOptionsProperty('maximum')); self::assertSame('20', $xAxis->getAxisOptionsProperty('major_unit')); self::assertSame('5', $xAxis->getAxisOptionsProperty('minor_unit')); + self::assertSame('6', $xAxis->getAxisOptionsProperty('textRotation')); + self::assertSame('0', $xAxis->getAxisOptionsProperty('hidden')); $yAxis = new Axis(); $yAxis->setFillParameters('accent1', 30, 'schemeClr'); @@ -158,6 +162,8 @@ class AxisPropertiesTest extends AbstractFunctional self::assertSame('68', $xAxis2->getAxisOptionsProperty('maximum')); self::assertSame('20', $xAxis2->getAxisOptionsProperty('major_unit')); self::assertSame('5', $xAxis2->getAxisOptionsProperty('minor_unit')); + self::assertSame('6', $xAxis2->getAxisOptionsProperty('textRotation')); + self::assertSame('0', $xAxis2->getAxisOptionsProperty('hidden')); $yAxis2 = $chart->getChartAxisY(); self::assertSame('accent1', $yAxis2->getFillProperty('value')); @@ -198,6 +204,8 @@ class AxisPropertiesTest extends AbstractFunctional self::assertSame('68', $xAxis3->getAxisOptionsProperty('maximum')); self::assertSame('20', $xAxis3->getAxisOptionsProperty('major_unit')); self::assertSame('5', $xAxis3->getAxisOptionsProperty('minor_unit')); + self::assertSame('6', $xAxis3->getAxisOptionsProperty('textRotation')); + self::assertSame('0', $xAxis3->getAxisOptionsProperty('hidden')); $yAxis3 = $chart2->getChartAxisY(); self::assertSame('accent1', $yAxis3->getFillProperty('value')); diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php index 268ee094..1f046af9 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php @@ -23,6 +23,8 @@ class Charts32CatAxValAxTest extends TestCase /** @var string */ private $outputFileName = ''; + private const FORMAT_CODE_DATE_ISO8601_SLASH = 'yyyy/mm/dd'; // not automatically treated as numeric + protected function tearDown(): void { if ($this->outputFileName !== '') { @@ -48,7 +50,7 @@ class Charts32CatAxValAxTest extends TestCase ['=DATEVALUE("2021-01-10")', 30.2, 32.2, 0.2], ] ); - $worksheet->getStyle('A2:A5')->getNumberFormat()->setFormatCode(Properties::FORMAT_CODE_DATE_ISO8601); + $worksheet->getStyle('A2:A5')->getNumberFormat()->setFormatCode(self::FORMAT_CODE_DATE_ISO8601_SLASH); $worksheet->getColumnDimension('A')->setAutoSize(true); $worksheet->setSelectedCells('A1'); @@ -91,9 +93,9 @@ class Charts32CatAxValAxTest extends TestCase $xAxis = new Axis(); //$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE ); if (is_bool($numeric)) { - $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, $numeric); + $xAxis->setAxisNumberProperties(self::FORMAT_CODE_DATE_ISO8601_SLASH, $numeric); } else { - $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601); + $xAxis->setAxisNumberProperties(self::FORMAT_CODE_DATE_ISO8601_SLASH); } // Build the dataseries diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php index 6a4673fd..4cc62360 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php @@ -177,4 +177,45 @@ class Charts32XmlTest extends TestCase ) ); } + + public function testDateAx(): void + { + $file = self::DIRECTORY . '32readwriteLineDateAxisChart1.xlsx'; + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(2, $charts); + $chart = $charts[1]; + self::assertNotNull($chart); + + $writer = new XlsxWriter($spreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); + $spreadsheet->disconnectWorksheets(); + + self::assertSame( + 1, + substr_count( + $data, + '' + ) + ); + self::assertSame( + 1, + substr_count( + $data, + '' + ) + ); + self::assertSame( + 1, + substr_count( + $data, + '' + ) + ); + } }