diff --git a/samples/Chart/32_Chart_read_write.php b/samples/Chart/32_Chart_read_write.php index a1f2f54681..42944c0ccb 100644 --- a/samples/Chart/32_Chart_read_write.php +++ b/samples/Chart/32_Chart_read_write.php @@ -42,7 +42,7 @@ foreach ($chartNames as $i => $chartName) { $chart = $worksheet->getChartByName($chartName); if ($chart->getTitle() !== null) { - $caption = '"' . implode(' ', $chart->getTitle()->getCaption()) . '"'; + $caption = '"' . $chart->getTitle()->getCaptionText($spreadsheet) . '"'; } else { $caption = 'Untitled'; } diff --git a/samples/Chart/37_Chart_dynamic_title.php b/samples/Chart/37_Chart_dynamic_title.php new file mode 100644 index 0000000000..b8b801faae --- /dev/null +++ b/samples/Chart/37_Chart_dynamic_title.php @@ -0,0 +1,83 @@ +log('File ' . $inputFileNameShort . ' does not exist'); + + continue; + } + $reader = IOFactory::createReader($inputFileType); + $reader->setIncludeCharts(true); + $callStartTime = microtime(true); + $spreadsheet = $reader->load($inputFileName); + $helper->logRead($inputFileType, $inputFileName, $callStartTime); + + $helper->log('Iterate worksheets looking at the charts'); + foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { + $sheetName = $worksheet->getTitle(); + $worksheet->getCell('A1')->setValue('Changed Title'); + $helper->log('Worksheet: ' . $sheetName); + + $chartNames = $worksheet->getChartNames(); + if (empty($chartNames)) { + $helper->log(' There are no charts in this worksheet'); + } else { + natsort($chartNames); + foreach ($chartNames as $i => $chartName) { + $chart = $worksheet->getChartByName($chartName); + if ($chart->getTitle() !== null) { + $caption = '"' . $chart->getTitle()->getCaptionText($spreadsheet) . '"'; + } else { + $caption = 'Untitled'; + } + $helper->log(' ' . $chartName . ' - ' . $caption); + $indentation = str_repeat(' ', strlen($chartName) + 3); + $groupCount = $chart->getPlotArea()->getPlotGroupCount(); + if ($groupCount == 1) { + $chartType = $chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); + $helper->log($indentation . ' ' . $chartType); + $helper->renderChart($chart, __FILE__, $spreadsheet); + } else { + $chartTypes = []; + for ($i = 0; $i < $groupCount; ++$i) { + $chartTypes[] = $chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + } + $chartTypes = array_unique($chartTypes); + if (count($chartTypes) == 1) { + $chartType = 'Multiple Plot ' . array_pop($chartTypes); + $helper->log($indentation . ' ' . $chartType); + $helper->renderChart($chart, __FILE__); + } elseif (count($chartTypes) == 0) { + $helper->log($indentation . ' *** Type not yet implemented'); + } else { + $helper->log($indentation . ' Combination Chart'); + $helper->renderChart($chart, __FILE__); + } + } + } + } + } + + $callStartTime = microtime(true); + $helper->write($spreadsheet, $inputFileName, ['Xlsx'], true); + + Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class); + $callStartTime = microtime(true); + $helper->write($spreadsheet, $inputFileName, ['Html'], true); + + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); +} diff --git a/samples/templates/37dynamictitle.xlsx b/samples/templates/37dynamictitle.xlsx new file mode 100644 index 0000000000..6a5215e861 Binary files /dev/null and b/samples/templates/37dynamictitle.xlsx differ diff --git a/src/PhpSpreadsheet/Chart/Title.php b/src/PhpSpreadsheet/Chart/Title.php index d8e6e7497f..378987446e 100644 --- a/src/PhpSpreadsheet/Chart/Title.php +++ b/src/PhpSpreadsheet/Chart/Title.php @@ -3,9 +3,16 @@ namespace PhpOffice\PhpSpreadsheet\Chart; use PhpOffice\PhpSpreadsheet\RichText\RichText; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Font; class Title { + public const TITLE_CELL_REFERENCE + = '/^(.*)!' // beginning of string, everything up to ! is match[1] + . '[$]([A-Z]{1,3})' // absolute column string match[2] + . '[$](\d{1,7})$/i'; // absolute row string match[3] + /** * Title Caption. * @@ -25,6 +32,10 @@ class Title */ private ?Layout $layout; + private string $cellReference = ''; + + private ?Font $font = null; + /** * Create a new Title. * @@ -48,8 +59,14 @@ public function getCaption() return $this->caption; } - public function getCaptionText(): string + public function getCaptionText(?Spreadsheet $spreadsheet = null): string { + if ($spreadsheet !== null) { + $caption = $this->getCalculatedTitle($spreadsheet); + if ($caption !== null) { + return $caption; + } + } $caption = $this->caption; if (is_string($caption)) { return $caption; @@ -100,13 +117,50 @@ public function getOverlay() * * @param bool $overlay */ - public function setOverlay($overlay): void + public function setOverlay($overlay): static { $this->overlay = $overlay; + + return $this; } public function getLayout(): ?Layout { return $this->layout; } + + public function setCellReference(string $cellReference): self + { + $this->cellReference = $cellReference; + + return $this; + } + + public function getCellReference(): string + { + return $this->cellReference; + } + + public function getCalculatedTitle(?Spreadsheet $spreadsheet): ?string + { + preg_match(self::TITLE_CELL_REFERENCE, $this->cellReference, $matches); + if (count($matches) === 0 || $spreadsheet === null) { + return null; + } + $sheetName = preg_replace("/^'(.*)'$/", '$1', $matches[1]) ?? ''; + + return $spreadsheet->getSheetByName($sheetName)?->getCell($matches[2] . $matches[3])?->getFormattedValue(); + } + + public function getFont(): ?Font + { + return $this->font; + } + + public function setFont(?Font $font): self + { + $this->font = $font; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Helper/Sample.php b/src/PhpSpreadsheet/Helper/Sample.php index 3d53f1e053..cb23cf597c 100644 --- a/src/PhpSpreadsheet/Helper/Sample.php +++ b/src/PhpSpreadsheet/Helper/Sample.php @@ -198,7 +198,7 @@ public function log(string $message): void * * @codeCoverageIgnore */ - public function renderChart(Chart $chart, string $fileName): void + public function renderChart(Chart $chart, string $fileName, ?Spreadsheet $spreadsheet = null): void { if ($this->isCli() === true) { return; @@ -206,17 +206,32 @@ public function renderChart(Chart $chart, string $fileName): void Settings::setChartRenderer(MtJpGraphRenderer::class); $fileName = $this->getFilename($fileName, 'png'); + $title = $chart->getTitle(); + $caption = null; + if ($title !== null) { + $calculatedTitle = $title->getCalculatedTitle($spreadsheet); + if ($calculatedTitle !== null) { + $caption = $title->getCaption(); + $title->setCaption($calculatedTitle); + } + } try { $chart->render($fileName); $this->log('Rendered image: ' . $fileName); - $imageData = file_get_contents($fileName); + $imageData = @file_get_contents($fileName); if ($imageData !== false) { echo '
'; + } else { + $this->log('Unable to open chart' . PHP_EOL); } } catch (Throwable $e) { $this->log('Error rendering chart: ' . $e->getMessage() . PHP_EOL); } + if (isset($title, $caption)) { + $title->setCaption($caption); + } + Settings::unsetChartRenderer(); } public function titles(string $category, string $functionName, ?string $description = null): void diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index 789d46ee78..32737f01a6 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -497,6 +497,8 @@ private function chartTitle(SimpleXMLElement $titleDetails): Title $caption = []; $titleLayout = null; $titleOverlay = false; + $titleFormula = null; + $titleFont = null; foreach ($titleDetails as $titleDetailKey => $chartDetail) { $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($titleDetailKey) { @@ -517,6 +519,9 @@ private function chartTitle(SimpleXMLElement $titleDetails): Title $caption[] = (string) $pt->v; } } + if (isset($chartDetail->strRef->f)) { + $titleFormula = (string) $chartDetail->strRef->f; + } } break; @@ -527,11 +532,24 @@ private function chartTitle(SimpleXMLElement $titleDetails): Title case 'layout': $titleLayout = $this->chartLayoutDetails($chartDetail); + break; + case 'txPr': + if (isset($chartDetail->children($this->aNamespace)->p)) { + $titleFont = $this->parseFont($chartDetail->children($this->aNamespace)->p); + } + break; } } + $title = new Title($caption, $titleLayout, (bool) $titleOverlay); + if (!empty($titleFormula)) { + $title->setCellReference($titleFormula); + } + if ($titleFont !== null) { + $title->setFont($titleFont); + } - return new Title($caption, $titleLayout, (bool) $titleOverlay); + return $title; } private function chartLayoutDetails(SimpleXMLElement $chartDetail): ?Layout @@ -1185,6 +1203,7 @@ private function parseFont(SimpleXMLElement $titleDetailPart): ?Font $fontArray['italic'] = self::getAttributeBoolean($titleDetailPart->pPr->defRPr, 'i'); $fontArray['underscore'] = self::getAttributeString($titleDetailPart->pPr->defRPr, 'u'); $fontArray['strikethrough'] = self::getAttributeString($titleDetailPart->pPr->defRPr, 'strike'); + $fontArray['cap'] = self::getAttributeString($titleDetailPart->pPr->defRPr, 'cap'); if (isset($titleDetailPart->pPr->defRPr->latin)) { $fontArray['latin'] = self::getAttributeString($titleDetailPart->pPr->defRPr->latin, 'typeface'); diff --git a/src/PhpSpreadsheet/Settings.php b/src/PhpSpreadsheet/Settings.php index 9f2ef0cd89..f7daa45750 100644 --- a/src/PhpSpreadsheet/Settings.php +++ b/src/PhpSpreadsheet/Settings.php @@ -78,6 +78,11 @@ public static function setChartRenderer(string $rendererClassName): void self::$chartRenderer = $rendererClassName; } + public static function unsetChartRenderer(): void + { + self::$chartRenderer = null; + } + /** * Return the Chart Rendering Library that PhpSpreadsheet is currently configured to use. * diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php index 09a24d10ab..ea26b8ce64 100644 --- a/src/PhpSpreadsheet/Style/Font.php +++ b/src/PhpSpreadsheet/Style/Font.php @@ -13,6 +13,13 @@ class Font extends Supervisor const UNDERLINE_SINGLE = 'single'; const UNDERLINE_SINGLEACCOUNTING = 'singleAccounting'; + const CAP_ALL = 'all'; + const CAP_SMALL = 'small'; + const CAP_NONE = 'none'; + private const VALID_CAPS = [self::CAP_ALL, self::CAP_SMALL, self::CAP_NONE]; + + protected ?string $cap = null; + /** * Font Name. * @@ -236,6 +243,9 @@ public function applyFromArray(array $styleArray): static if (isset($styleArray['scheme'])) { $this->setScheme($styleArray['scheme']); } + if (isset($styleArray['cap'])) { + $this->setCap($styleArray['cap']); + } } return $this; @@ -795,6 +805,7 @@ public function getHashCode() $this->hashChartColor($this->chartColor), $this->hashChartColor($this->underlineColor), (string) $this->baseLine, + (string) $this->cap, ] ) . __CLASS__ @@ -806,6 +817,7 @@ protected function exportArray1(): array $exportedArray = []; $this->exportArray2($exportedArray, 'baseLine', $this->getBaseLine()); $this->exportArray2($exportedArray, 'bold', $this->getBold()); + $this->exportArray2($exportedArray, 'cap', $this->getCap()); $this->exportArray2($exportedArray, 'chartColor', $this->getChartColor()); $this->exportArray2($exportedArray, 'color', $this->getColor()); $this->exportArray2($exportedArray, 'complexScript', $this->getComplexScript()); @@ -847,4 +859,23 @@ public function setScheme(string $scheme): self return $this; } + + /** + * Set capitalization attribute. If not one of the permitted + * values (all, small, or none), set it to null. + * This will be honored only for the font for chart titles. + * None is distinguished from null because null will inherit + * the current value, whereas 'none' will override it. + */ + public function setCap(string $cap): self + { + $this->cap = in_array($cap, self::VALID_CAPS, true) ? $cap : null; + + return $this; + } + + public function getCap(): ?string + { + return $this->cap; + } } diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index b4547637cf..5df0525f11 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -23,6 +23,7 @@ use PhpOffice\PhpSpreadsheet\Style\Font; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Style; +use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; @@ -150,6 +151,12 @@ class Html extends BaseWriter */ private $editHtmlCallback; + /** @var BaseDrawing[] */ + private $sheetDrawings; + + /** @var Chart[] */ + private $sheetCharts; + /** * Create a new HTML. */ @@ -479,11 +486,14 @@ public function generateSheetData(): string foreach ($sheets as $sheet) { // Write table header $html .= $this->generateTableHeader($sheet); + $this->sheetCharts = []; + $this->sheetDrawings = []; // Get worksheet dimension [$min, $max] = explode(':', $sheet->calculateWorksheetDataDimension()); [$minCol, $minRow] = Coordinate::indexesFromString($min); [$maxCol, $maxRow] = Coordinate::indexesFromString($max); + $this->extendRowsAndColumns($sheet, $maxCol, $maxRow); [$theadStart, $theadEnd, $tbodyStart] = $this->generateSheetStarts($sheet, $minRow); @@ -510,8 +520,6 @@ public function generateSheetData(): string $html .= $endTag; } - --$row; - $html .= $this->extendRowsForChartsAndImages($sheet, $row); // Write table footer $html .= $this->generateTableFooter(); @@ -563,78 +571,33 @@ public function generateNavigation(): string return $html; } - /** - * Extend Row if chart is placed after nominal end of row. - * This code should be exercised by sample: - * Chart/32_Chart_read_write_PDF.php. - * - * @param int $row Row to check for charts - */ - private function extendRowsForCharts(Worksheet $worksheet, int $row): array + private function extendRowsAndColumns(Worksheet $worksheet, int &$colMax, int &$rowMax): void { - $rowMax = $row; - $colMax = 'A'; - $anyfound = false; if ($this->includeCharts) { foreach ($worksheet->getChartCollection() as $chart) { if ($chart instanceof Chart) { - $anyfound = true; $chartCoordinates = $chart->getTopLeftPosition(); - $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']); - $chartCol = Coordinate::columnIndexFromString($chartTL[0]); + $this->sheetCharts[$chartCoordinates['cell']] = $chart; + $chartTL = Coordinate::indexesFromString($chartCoordinates['cell']); if ($chartTL[1] > $rowMax) { $rowMax = $chartTL[1]; } - if ($chartCol > Coordinate::columnIndexFromString($colMax)) { + if ($chartTL[0] > $colMax) { $colMax = $chartTL[0]; } } } } - - return [$rowMax, $colMax, $anyfound]; - } - - private function extendRowsForChartsAndImages(Worksheet $worksheet, int $row): string - { - [$rowMax, $colMax, $anyfound] = $this->extendRowsForCharts($worksheet, $row); - foreach ($worksheet->getDrawingCollection() as $drawing) { - $anyfound = true; - $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates()); - $imageCol = Coordinate::columnIndexFromString($imageTL[0]); + $imageTL = Coordinate::indexesFromString($drawing->getCoordinates()); + $this->sheetDrawings[$drawing->getCoordinates()] = $drawing; if ($imageTL[1] > $rowMax) { $rowMax = $imageTL[1]; } - if ($imageCol > Coordinate::columnIndexFromString($colMax)) { + if ($imageTL[0] > $colMax) { $colMax = $imageTL[0]; } } - - // Don't extend rows if not needed - if ($row === $rowMax || !$anyfound) { - return ''; - } - - $html = ''; - ++$colMax; - ++$row; - while ($row <= $rowMax) { - $html .= ''; - for ($col = 'A'; $col != $colMax; ++$col) { - $htmlx = $this->writeImageInCell($worksheet, $col . $row); - $htmlx .= $this->includeCharts ? $this->writeChartInCell($worksheet, $col . $row) : ''; - if ($htmlx) { - $html .= "$htmlx"; - } else { - $html .= ""; - } - } - ++$row; - $html .= '' . PHP_EOL; - } - - return $html; } /** @@ -658,19 +621,16 @@ public static function winFileToUrl($filename, bool $mpdf = false) /** * Generate image tag in cell. * - * @param Worksheet $worksheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet * @param string $coordinates Cell coordinates */ - private function writeImageInCell(Worksheet $worksheet, string $coordinates): string + private function writeImageInCell(string $coordinates): string { // Construct HTML $html = ''; // Write images - foreach ($worksheet->getDrawingCollection() as $drawing) { - if ($drawing->getCoordinates() != $coordinates) { - continue; - } + $drawing = $this->sheetDrawings[$coordinates] ?? null; + if ($drawing !== null) { $filedesc = $drawing->getDescription(); $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded image'; if ($drawing instanceof Drawing) { @@ -744,37 +704,47 @@ private function writeChartInCell(Worksheet $worksheet, string $coordinates): st $html = ''; // Write charts - foreach ($worksheet->getChartCollection() as $chart) { - if ($chart instanceof Chart) { - $chartCoordinates = $chart->getTopLeftPosition(); - if ($chartCoordinates['cell'] == $coordinates) { - $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png'; - $renderedWidth = $chart->getRenderedWidth(); - $renderedHeight = $chart->getRenderedHeight(); - if ($renderedWidth === null || $renderedHeight === null) { - $this->adjustRendererPositions($chart, $worksheet); - } - $renderSuccessful = $chart->render($chartFileName); - $chart->setRenderedWidth($renderedWidth); - $chart->setRenderedHeight($renderedHeight); - if (!$renderSuccessful) { - return ''; - } + $chart = $this->sheetCharts[$coordinates] ?? null; + if ($chart !== null) { + $chartCoordinates = $chart->getTopLeftPosition(); + $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png'; + $renderedWidth = $chart->getRenderedWidth(); + $renderedHeight = $chart->getRenderedHeight(); + if ($renderedWidth === null || $renderedHeight === null) { + $this->adjustRendererPositions($chart, $worksheet); + } + $title = $chart->getTitle(); + $caption = null; + $filedesc = ''; + if ($title !== null) { + $calculatedTitle = $title->getCalculatedTitle($worksheet->getParent()); + if ($calculatedTitle !== null) { + $caption = $title->getCaption(); + $title->setCaption($calculatedTitle); + } + $filedesc = $title->getCaptionText($worksheet->getParent()); + } + $renderSuccessful = $chart->render($chartFileName); + $chart->setRenderedWidth($renderedWidth); + $chart->setRenderedHeight($renderedHeight); + if (isset($title, $caption)) { + $title->setCaption($caption); + } + if (!$renderSuccessful) { + return ''; + } - $html .= PHP_EOL; - $imageDetails = getimagesize($chartFileName) ?: ['', '', 'mime' => '']; - $filedesc = $chart->getTitle(); - $filedesc = $filedesc ? $filedesc->getCaptionText() : ''; - $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded chart'; - $picture = file_get_contents($chartFileName); - if ($picture !== false) { - $base64 = base64_encode($picture); - $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; + $html .= PHP_EOL; + $imageDetails = getimagesize($chartFileName) ?: ['', '', 'mime' => '']; - $html .= '' . $filedesc . '' . PHP_EOL; - } - unlink($chartFileName); - } + $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded chart'; + $picture = file_get_contents($chartFileName); + unlink($chartFileName); + if ($picture !== false) { + $base64 = base64_encode($picture); + $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; + + $html .= '' . $filedesc . '' . PHP_EOL; } } @@ -1444,7 +1414,7 @@ private function generateRowSpans(string $html, int $rowSpan, int $colSpan): str private function generateRowWriteCell(string &$html, Worksheet $worksheet, string $coordinate, string $cellType, string $cellData, int $colSpan, int $rowSpan, $cssClass, int $colNum, int $sheetIndex, int $row): void { // Image? - $htmlx = $this->writeImageInCell($worksheet, $coordinate); + $htmlx = $this->writeImageInCell($coordinate); // Chart? $htmlx .= $this->generateRowIncludeCharts($worksheet, $coordinate); // Column start diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index f27156507a..a3f27d1b93 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -99,9 +99,11 @@ public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $chart, mixed $ $objWriter->writeAttribute('val', (string) (int) $chart->getPlotVisibleOnly()); $objWriter->endElement(); - $objWriter->startElement('c:dispBlanksAs'); - $objWriter->writeAttribute('val', $chart->getDisplayBlanksAs()); - $objWriter->endElement(); + if ($chart->getDisplayBlanksAs() !== '') { + $objWriter->startElement('c:dispBlanksAs'); + $objWriter->writeAttribute('val', $chart->getDisplayBlanksAs()); + $objWriter->endElement(); + } $objWriter->startElement('c:showDLblsOverMax'); $objWriter->writeAttribute('val', '0'); @@ -151,6 +153,9 @@ private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void if ($title === null) { return; } + if ($this->writeCalculatedTitle($objWriter, $title)) { + return; + } $objWriter->startElement('c:title'); $objWriter->startElement('c:tx'); @@ -187,6 +192,60 @@ private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void $objWriter->endElement(); } + /** + * Write Calculated Chart Title. + */ + private function writeCalculatedTitle(XMLWriter $objWriter, Title $title): bool + { + $calc = $title->getCalculatedTitle($this->getParentWriter()->getSpreadsheet()); + if (empty($calc)) { + return false; + } + + $objWriter->startElement('c:title'); + $objWriter->startElement('c:tx'); + $objWriter->startElement('c:strRef'); + $objWriter->writeElement('c:f', $title->getCellReference()); + $objWriter->startElement('c:strCache'); + + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', '1'); + $objWriter->endElement(); // c:ptCount + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', '0'); + $objWriter->writeElement('c:v', $calc); + $objWriter->endElement(); // c:pt + + $objWriter->endElement(); // c:strCache + $objWriter->endElement(); // c:strRef + $objWriter->endElement(); // c:tx + + $this->writeLayout($objWriter, $title->getLayout()); + + $objWriter->startElement('c:overlay'); + $objWriter->writeAttribute('val', ($title->getOverlay()) ? '1' : '0'); + $objWriter->endElement(); // c:overlay + // c:spPr + + // c:txPr + $labelFont = $title->getFont(); + if ($labelFont !== null) { + $objWriter->startElement('c:txPr'); + + $objWriter->startElement('a:bodyPr'); + $objWriter->endElement(); // a:bodyPr + $objWriter->startElement('a:lstStyle'); + $objWriter->endElement(); // a:lstStyle + $this->writeLabelFont($objWriter, $labelFont, null); + + $objWriter->endElement(); // c:txPr + } + + $objWriter->endElement(); // c:title + + return true; + } + /** * Write Chart Legend. */ @@ -1804,6 +1863,10 @@ private function writeLabelFont(XMLWriter $objWriter, ?Font $labelFont, ?Propert if ($labelFont->getItalic() === true) { $objWriter->writeAttribute('i', '1'); } + $cap = $labelFont->getCap(); + if ($cap !== null) { + $objWriter->writeAttribute('cap', $cap); + } $fontColor = $labelFont->getChartColor(); if ($fontColor !== null) { $this->writeColor($objWriter, $fontColor); diff --git a/tests/PhpSpreadsheetTests/Chart/ChartsDynamicTitleTest.php b/tests/PhpSpreadsheetTests/Chart/ChartsDynamicTitleTest.php new file mode 100644 index 0000000000..2546d60de6 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/ChartsDynamicTitleTest.php @@ -0,0 +1,141 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testDynamicTitle(): void + { + // based on samples/templates/issue.3797.2007.xlsx + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Only Sheet'); + $sheet->fromArray( + [ + ['Some Title'], + [], + [null, null, 'Data'], + [null, 'L1', 1.3], + [null, 'L2', 1.3], + [null, 'L3', 2.3], + [null, 'L4', 1.6], + [null, 'L5', 1.5], + [null, 'L6', 1.4], + [null, 'L7', 2.2], + [null, 'L8', 1.8], + [null, 'L9', 1.1], + [null, 'L10', 1.8], + [null, 'L11', 1.6], + [null, 'L12', 2.7], + [null, 'L13', 2.2], + [null, 'L14', 1.3], + ] + ); + + $dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, '\'Only Sheet\'!$B$4', null, 1), // 2010 + ]; + // Set the X-Axis Labels + $xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, '\'Only Sheet\'!$B$4:$B$17'), + ]; + // Set the Data values for each data series we want to plot + $dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, '\'Only Sheet\'!$C$4:$C$17'), + ]; + + // Build the dataseries + $series = new DataSeries( + DataSeries::TYPE_BARCHART, // plotType + DataSeries::GROUPING_STANDARD, // 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]); + $title = new Title(); + $title->setCellReference('\'Only Sheet\'!$A$1'); + $font = new Font(); + $font->setCap(Font::CAP_ALL); + $title->setFont($font); + + // Create the chart + $chart = new Chart( + 'chart1', // name + $title, // title + null, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + null, // yAxisLabel + null, // xAxis + ); + + // Set the position where the chart should appear in the worksheet + $chart->setTopLeftPosition('G7'); + $chart->setBottomRightPosition('N21'); + // Add the chart to the worksheet + $sheet->addChart($chart); + $sheet->setSelectedCells('D1'); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $rsheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $rsheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $original = $chart2->getTitle()?->getCalculatedTitle($reloadedSpreadsheet); + self::assertSame('Some Title', $original); + $rsheet->getCell('A1')->setValue('Changed Title'); + self::assertNotNull($chart2->getTitle()); + self::assertSame('Changed Title', $chart2->getTitle()->getCalculatedTitle($reloadedSpreadsheet)); + self::assertSame(Font::CAP_ALL, $chart2->getTitle()->getFont()?->getCap()); + + $writer = new Html($reloadedSpreadsheet); + Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class); + $writer->setIncludeCharts(true); + $content = $writer->generateHtmlAll(); + self::assertStringContainsString('alt="Changed Title"', $content); + $reloadedSpreadsheet->disconnectWorksheets(); + } +}