See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; 4 5 use PhpOffice\PhpSpreadsheet\Chart\Axis; 6 use PhpOffice\PhpSpreadsheet\Chart\ChartColor; 7 use PhpOffice\PhpSpreadsheet\Chart\DataSeries; 8 use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; 9 use PhpOffice\PhpSpreadsheet\Chart\Layout; 10 use PhpOffice\PhpSpreadsheet\Chart\Legend; 11 use PhpOffice\PhpSpreadsheet\Chart\PlotArea; 12 use PhpOffice\PhpSpreadsheet\Chart\Properties; 13 use PhpOffice\PhpSpreadsheet\Chart\Title; 14 use PhpOffice\PhpSpreadsheet\Chart\TrendLine; 15 use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; 16 use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; 17 18 class Chart extends WriterPart 19 { 20 /** 21 * @var int 22 */ 23 private $seriesIndex; 24 25 /** 26 * Write charts to XML format. 27 * 28 * @param mixed $calculateCellValues 29 * 30 * @return string XML Output 31 */ 32 public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $chart, $calculateCellValues = true) 33 { 34 // Create XML writer 35 $objWriter = null; 36 if ($this->getParentWriter()->getUseDiskCaching()) { 37 $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); 38 } else { 39 $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); 40 } 41 // Ensure that data series values are up-to-date before we save 42 if ($calculateCellValues) { 43 $chart->refresh(); 44 } 45 46 // XML header 47 $objWriter->startDocument('1.0', 'UTF-8', 'yes'); 48 49 // c:chartSpace 50 $objWriter->startElement('c:chartSpace'); 51 $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); 52 $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); 53 $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); 54 55 $objWriter->startElement('c:date1904'); 56 $objWriter->writeAttribute('val', '0'); 57 $objWriter->endElement(); 58 $objWriter->startElement('c:lang'); 59 $objWriter->writeAttribute('val', 'en-GB'); 60 $objWriter->endElement(); 61 $objWriter->startElement('c:roundedCorners'); 62 $objWriter->writeAttribute('val', $chart->getRoundedCorners() ? '1' : '0'); 63 $objWriter->endElement(); 64 65 $this->writeAlternateContent($objWriter); 66 67 $objWriter->startElement('c:chart'); 68 69 $this->writeTitle($objWriter, $chart->getTitle()); 70 71 $objWriter->startElement('c:autoTitleDeleted'); 72 $objWriter->writeAttribute('val', (string) (int) $chart->getAutoTitleDeleted()); 73 $objWriter->endElement(); 74 75 $objWriter->startElement('c:view3D'); 76 $surface2D = false; 77 $plotArea = $chart->getPlotArea(); 78 if ($plotArea !== null) { 79 $seriesArray = $plotArea->getPlotGroup(); 80 foreach ($seriesArray as $series) { 81 if ($series->getPlotType() === DataSeries::TYPE_SURFACECHART) { 82 $surface2D = true; 83 84 break; 85 } 86 } 87 } 88 $this->writeView3D($objWriter, $chart->getRotX(), 'c:rotX', $surface2D, 90); 89 $this->writeView3D($objWriter, $chart->getRotY(), 'c:rotY', $surface2D); 90 $this->writeView3D($objWriter, $chart->getRAngAx(), 'c:rAngAx', $surface2D); 91 $this->writeView3D($objWriter, $chart->getPerspective(), 'c:perspective', $surface2D); 92 $objWriter->endElement(); // view3D 93 94 $this->writePlotArea($objWriter, $chart->getPlotArea(), $chart->getXAxisLabel(), $chart->getYAxisLabel(), $chart->getChartAxisX(), $chart->getChartAxisY()); 95 96 $this->writeLegend($objWriter, $chart->getLegend()); 97 98 $objWriter->startElement('c:plotVisOnly'); 99 $objWriter->writeAttribute('val', (string) (int) $chart->getPlotVisibleOnly()); 100 $objWriter->endElement(); 101 102 $objWriter->startElement('c:dispBlanksAs'); 103 $objWriter->writeAttribute('val', $chart->getDisplayBlanksAs()); 104 $objWriter->endElement(); 105 106 $objWriter->startElement('c:showDLblsOverMax'); 107 $objWriter->writeAttribute('val', '0'); 108 $objWriter->endElement(); 109 110 $objWriter->endElement(); // c:chart 111 if ($chart->getNoFill()) { 112 $objWriter->startElement('c:spPr'); 113 $objWriter->startElement('a:noFill'); 114 $objWriter->endElement(); // a:noFill 115 $objWriter->endElement(); // c:spPr 116 } 117 118 $this->writePrintSettings($objWriter); 119 120 $objWriter->endElement(); // c:chartSpace 121 122 // Return 123 return $objWriter->getData(); 124 } 125 126 private function writeView3D(XMLWriter $objWriter, ?int $value, string $tag, bool $surface2D, int $default = 0): void 127 { 128 if ($value === null && $surface2D) { 129 $value = $default; 130 } 131 if ($value !== null) { 132 $objWriter->startElement($tag); 133 $objWriter->writeAttribute('val', "$value"); 134 $objWriter->endElement(); 135 } 136 } 137 138 /** 139 * Write Chart Title. 140 */ 141 private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void 142 { 143 if ($title === null) { 144 return; 145 } 146 147 $objWriter->startElement('c:title'); 148 $objWriter->startElement('c:tx'); 149 $objWriter->startElement('c:rich'); 150 151 $objWriter->startElement('a:bodyPr'); 152 $objWriter->endElement(); 153 154 $objWriter->startElement('a:lstStyle'); 155 $objWriter->endElement(); 156 157 $objWriter->startElement('a:p'); 158 $objWriter->startElement('a:pPr'); 159 $objWriter->startElement('a:defRPr'); 160 $objWriter->endElement(); 161 $objWriter->endElement(); 162 163 $caption = $title->getCaption(); 164 if ((is_array($caption)) && (count($caption) > 0)) { 165 $caption = $caption[0]; 166 } 167 $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a'); 168 169 $objWriter->endElement(); 170 $objWriter->endElement(); 171 $objWriter->endElement(); 172 173 $this->writeLayout($objWriter, $title->getLayout()); 174 175 $objWriter->startElement('c:overlay'); 176 $objWriter->writeAttribute('val', '0'); 177 $objWriter->endElement(); 178 179 $objWriter->endElement(); 180 } 181 182 /** 183 * Write Chart Legend. 184 */ 185 private function writeLegend(XMLWriter $objWriter, ?Legend $legend = null): void 186 { 187 if ($legend === null) { 188 return; 189 } 190 191 $objWriter->startElement('c:legend'); 192 193 $objWriter->startElement('c:legendPos'); 194 $objWriter->writeAttribute('val', $legend->getPosition()); 195 $objWriter->endElement(); 196 197 $this->writeLayout($objWriter, $legend->getLayout()); 198 199 $objWriter->startElement('c:overlay'); 200 $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0'); 201 $objWriter->endElement(); 202 203 $objWriter->startElement('c:txPr'); 204 $objWriter->startElement('a:bodyPr'); 205 $objWriter->endElement(); 206 207 $objWriter->startElement('a:lstStyle'); 208 $objWriter->endElement(); 209 210 $objWriter->startElement('a:p'); 211 $objWriter->startElement('a:pPr'); 212 $objWriter->writeAttribute('rtl', '0'); 213 214 $objWriter->startElement('a:defRPr'); 215 $objWriter->endElement(); 216 $objWriter->endElement(); 217 218 $objWriter->startElement('a:endParaRPr'); 219 $objWriter->writeAttribute('lang', 'en-US'); 220 $objWriter->endElement(); 221 222 $objWriter->endElement(); 223 $objWriter->endElement(); 224 225 $objWriter->endElement(); 226 } 227 228 /** 229 * Write Chart Plot Area. 230 */ 231 private function writePlotArea(XMLWriter $objWriter, ?PlotArea $plotArea, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null): void 232 { 233 if ($plotArea === null) { 234 return; 235 } 236 237 $id1 = $id2 = $id3 = '0'; 238 $this->seriesIndex = 0; 239 $objWriter->startElement('c:plotArea'); 240 241 $layout = $plotArea->getLayout(); 242 243 $this->writeLayout($objWriter, $layout); 244 245 $chartTypes = self::getChartType($plotArea); 246 $catIsMultiLevelSeries = $valIsMultiLevelSeries = false; 247 $plotGroupingType = ''; 248 $chartType = null; 249 foreach ($chartTypes as $chartType) { 250 $objWriter->startElement('c:' . $chartType); 251 252 $groupCount = $plotArea->getPlotGroupCount(); 253 $plotGroup = null; 254 for ($i = 0; $i < $groupCount; ++$i) { 255 $plotGroup = $plotArea->getPlotGroupByIndex($i); 256 $groupType = $plotGroup->getPlotType(); 257 if ($groupType == $chartType) { 258 $plotStyle = $plotGroup->getPlotStyle(); 259 if (!empty($plotStyle) && $groupType === DataSeries::TYPE_RADARCHART) { 260 $objWriter->startElement('c:radarStyle'); 261 $objWriter->writeAttribute('val', $plotStyle); 262 $objWriter->endElement(); 263 } elseif (!empty($plotStyle) && $groupType === DataSeries::TYPE_SCATTERCHART) { 264 $objWriter->startElement('c:scatterStyle'); 265 $objWriter->writeAttribute('val', $plotStyle); 266 $objWriter->endElement(); 267 } elseif ($groupType === DataSeries::TYPE_SURFACECHART_3D || $groupType === DataSeries::TYPE_SURFACECHART) { 268 $objWriter->startElement('c:wireframe'); 269 $objWriter->writeAttribute('val', $plotStyle ? '1' : '0'); 270 $objWriter->endElement(); 271 } 272 273 $this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType); 274 } 275 } 276 277 $this->writeDataLabels($objWriter, $layout); 278 279 if ($chartType === DataSeries::TYPE_LINECHART && $plotGroup) { 280 // Line only, Line3D can't be smoothed 281 $objWriter->startElement('c:smooth'); 282 $objWriter->writeAttribute('val', (string) (int) $plotGroup->getSmoothLine()); 283 $objWriter->endElement(); 284 } elseif (($chartType === DataSeries::TYPE_BARCHART) || ($chartType === DataSeries::TYPE_BARCHART_3D)) { 285 $objWriter->startElement('c:gapWidth'); 286 $objWriter->writeAttribute('val', '150'); 287 $objWriter->endElement(); 288 289 if ($plotGroupingType == 'percentStacked' || $plotGroupingType == 'stacked') { 290 $objWriter->startElement('c:overlap'); 291 $objWriter->writeAttribute('val', '100'); 292 $objWriter->endElement(); 293 } 294 } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) { 295 $scale = ($plotGroup === null) ? '' : (string) $plotGroup->getPlotStyle(); 296 if ($scale !== '') { 297 $objWriter->startElement('c:bubbleScale'); 298 $objWriter->writeAttribute('val', $scale); 299 $objWriter->endElement(); 300 } 301 302 $objWriter->startElement('c:showNegBubbles'); 303 $objWriter->writeAttribute('val', '0'); 304 $objWriter->endElement(); 305 } elseif ($chartType === DataSeries::TYPE_STOCKCHART) { 306 $objWriter->startElement('c:hiLowLines'); 307 $objWriter->endElement(); 308 309 $objWriter->startElement('c:upDownBars'); 310 311 $objWriter->startElement('c:gapWidth'); 312 $objWriter->writeAttribute('val', '300'); 313 $objWriter->endElement(); 314 315 $objWriter->startElement('c:upBars'); 316 $objWriter->endElement(); 317 318 $objWriter->startElement('c:downBars'); 319 $objWriter->endElement(); 320 321 $objWriter->endElement(); 322 } 323 324 // Generate 3 unique numbers to use for axId values 325 $id1 = '110438656'; 326 $id2 = '110444544'; 327 $id3 = '110365312'; // used in Surface Chart 328 329 if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) { 330 $objWriter->startElement('c:axId'); 331 $objWriter->writeAttribute('val', $id1); 332 $objWriter->endElement(); 333 $objWriter->startElement('c:axId'); 334 $objWriter->writeAttribute('val', $id2); 335 $objWriter->endElement(); 336 if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) { 337 $objWriter->startElement('c:axId'); 338 $objWriter->writeAttribute('val', $id3); 339 $objWriter->endElement(); 340 } 341 } else { 342 $objWriter->startElement('c:firstSliceAng'); 343 $objWriter->writeAttribute('val', '0'); 344 $objWriter->endElement(); 345 346 if ($chartType === DataSeries::TYPE_DONUTCHART) { 347 $objWriter->startElement('c:holeSize'); 348 $objWriter->writeAttribute('val', '50'); 349 $objWriter->endElement(); 350 } 351 } 352 353 $objWriter->endElement(); 354 } 355 356 if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) { 357 if ($chartType === DataSeries::TYPE_BUBBLECHART) { 358 $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id2, $id1, $catIsMultiLevelSeries, $xAxis ?? new Axis()); 359 } else { 360 $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $xAxis ?? new Axis()); 361 } 362 363 $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $yAxis ?? new Axis()); 364 if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) { 365 $this->writeSerAxis($objWriter, $id2, $id3); 366 } 367 } 368 $stops = $plotArea->getGradientFillStops(); 369 if ($plotArea->getNoFill() || !empty($stops)) { 370 $objWriter->startElement('c:spPr'); 371 if ($plotArea->getNoFill()) { 372 $objWriter->startElement('a:noFill'); 373 $objWriter->endElement(); // a:noFill 374 } 375 if (!empty($stops)) { 376 $objWriter->startElement('a:gradFill'); 377 $objWriter->startElement('a:gsLst'); 378 foreach ($stops as $stop) { 379 $objWriter->startElement('a:gs'); 380 $objWriter->writeAttribute('pos', (string) (Properties::PERCENTAGE_MULTIPLIER * (float) $stop[0])); 381 $this->writeColor($objWriter, $stop[1], false); 382 $objWriter->endElement(); // a:gs 383 } 384 $objWriter->endElement(); // a:gsLst 385 $angle = $plotArea->getGradientFillAngle(); 386 if ($angle !== null) { 387 $objWriter->startElement('a:lin'); 388 $objWriter->writeAttribute('ang', Properties::angleToXml($angle)); 389 $objWriter->endElement(); // a:lin 390 } 391 $objWriter->endElement(); // a:gradFill 392 } 393 $objWriter->endElement(); // c:spPr 394 } 395 396 $objWriter->endElement(); // c:plotArea 397 } 398 399 private function writeDataLabelsBool(XMLWriter $objWriter, string $name, ?bool $value): void 400 { 401 if ($value !== null) { 402 $objWriter->startElement("c:$name"); 403 $objWriter->writeAttribute('val', $value ? '1' : '0'); 404 $objWriter->endElement(); 405 } 406 } 407 408 /** 409 * Write Data Labels. 410 */ 411 private function writeDataLabels(XMLWriter $objWriter, ?Layout $chartLayout = null): void 412 { 413 if (!isset($chartLayout)) { 414 return; 415 } 416 $objWriter->startElement('c:dLbls'); 417 418 $fillColor = $chartLayout->getLabelFillColor(); 419 $borderColor = $chartLayout->getLabelBorderColor(); 420 if ($fillColor && $fillColor->isUsable()) { 421 $objWriter->startElement('c:spPr'); 422 $this->writeColor($objWriter, $fillColor); 423 if ($borderColor && $borderColor->isUsable()) { 424 $objWriter->startElement('a:ln'); 425 $this->writeColor($objWriter, $borderColor); 426 $objWriter->endElement(); // a:ln 427 } 428 $objWriter->endElement(); // c:spPr 429 } 430 $fontColor = $chartLayout->getLabelFontColor(); 431 if ($fontColor && $fontColor->isUsable()) { 432 $objWriter->startElement('c:txPr'); 433 434 $objWriter->startElement('a:bodyPr'); 435 $objWriter->writeAttribute('wrap', 'square'); 436 $objWriter->writeAttribute('lIns', '38100'); 437 $objWriter->writeAttribute('tIns', '19050'); 438 $objWriter->writeAttribute('rIns', '38100'); 439 $objWriter->writeAttribute('bIns', '19050'); 440 $objWriter->writeAttribute('anchor', 'ctr'); 441 $objWriter->startElement('a:spAutoFit'); 442 $objWriter->endElement(); // a:spAutoFit 443 $objWriter->endElement(); // a:bodyPr 444 445 $objWriter->startElement('a:lstStyle'); 446 $objWriter->endElement(); // a:lstStyle 447 448 $objWriter->startElement('a:p'); 449 $objWriter->startElement('a:pPr'); 450 $objWriter->startElement('a:defRPr'); 451 $this->writeColor($objWriter, $fontColor); 452 $objWriter->endElement(); // a:defRPr 453 $objWriter->endElement(); // a:pPr 454 $objWriter->endElement(); // a:p 455 456 $objWriter->endElement(); // c:txPr 457 } 458 459 if ($chartLayout->getNumFmtCode() !== '') { 460 $objWriter->startElement('c:numFmt'); 461 $objWriter->writeAttribute('formatCode', $chartLayout->getnumFmtCode()); 462 $objWriter->writeAttribute('sourceLinked', (string) (int) $chartLayout->getnumFmtLinked()); 463 $objWriter->endElement(); // c:numFmt 464 } 465 if ($chartLayout->getDLblPos() !== '') { 466 $objWriter->startElement('c:dLblPos'); 467 $objWriter->writeAttribute('val', $chartLayout->getDLblPos()); 468 $objWriter->endElement(); // c:dLblPos 469 } 470 $this->writeDataLabelsBool($objWriter, 'showLegendKey', $chartLayout->getShowLegendKey()); 471 $this->writeDataLabelsBool($objWriter, 'showVal', $chartLayout->getShowVal()); 472 $this->writeDataLabelsBool($objWriter, 'showCatName', $chartLayout->getShowCatName()); 473 $this->writeDataLabelsBool($objWriter, 'showSerName', $chartLayout->getShowSerName()); 474 $this->writeDataLabelsBool($objWriter, 'showPercent', $chartLayout->getShowPercent()); 475 $this->writeDataLabelsBool($objWriter, 'showBubbleSize', $chartLayout->getShowBubbleSize()); 476 $this->writeDataLabelsBool($objWriter, 'showLeaderLines', $chartLayout->getShowLeaderLines()); 477 478 $objWriter->endElement(); // c:dLbls 479 } 480 481 /** 482 * Write Category Axis. 483 * 484 * @param string $id1 485 * @param string $id2 486 * @param bool $isMultiLevelSeries 487 */ 488 private function writeCategoryAxis(XMLWriter $objWriter, ?Title $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void 489 { 490 // N.B. writeCategoryAxis may be invoked with the last parameter($yAxis) using $xAxis for ScatterChart, etc 491 // In that case, xAxis may contain values like the yAxis, or it may be a date axis (LINECHART). 492 $axisType = $yAxis->getAxisType(); 493 if ($axisType !== '') { 494 $objWriter->startElement("c:$axisType"); 495 } elseif ($yAxis->getAxisIsNumericFormat()) { 496 $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE); 497 } else { 498 $objWriter->startElement('c:' . Axis::AXIS_TYPE_CATEGORY); 499 } 500 $majorGridlines = $yAxis->getMajorGridlines(); 501 $minorGridlines = $yAxis->getMinorGridlines(); 502 503 if ($id1 !== '0') { 504 $objWriter->startElement('c:axId'); 505 $objWriter->writeAttribute('val', $id1); 506 $objWriter->endElement(); 507 } 508 509 $objWriter->startElement('c:scaling'); 510 if ($yAxis->getAxisOptionsProperty('maximum') !== null) { 511 $objWriter->startElement('c:max'); 512 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('maximum')); 513 $objWriter->endElement(); 514 } 515 if ($yAxis->getAxisOptionsProperty('minimum') !== null) { 516 $objWriter->startElement('c:min'); 517 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minimum')); 518 $objWriter->endElement(); 519 } 520 if (!empty($yAxis->getAxisOptionsProperty('orientation'))) { 521 $objWriter->startElement('c:orientation'); 522 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation')); 523 $objWriter->endElement(); 524 } 525 $objWriter->endElement(); // c:scaling 526 527 $objWriter->startElement('c:delete'); 528 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('hidden') ?? '0'); 529 $objWriter->endElement(); 530 531 $objWriter->startElement('c:axPos'); 532 $objWriter->writeAttribute('val', 'b'); 533 $objWriter->endElement(); 534 535 if ($majorGridlines !== null) { 536 $objWriter->startElement('c:majorGridlines'); 537 $objWriter->startElement('c:spPr'); 538 $this->writeLineStyles($objWriter, $majorGridlines); 539 $this->writeEffects($objWriter, $majorGridlines); 540 $objWriter->endElement(); //end spPr 541 $objWriter->endElement(); //end majorGridLines 542 } 543 544 if ($minorGridlines !== null && $minorGridlines->getObjectState()) { 545 $objWriter->startElement('c:minorGridlines'); 546 $objWriter->startElement('c:spPr'); 547 $this->writeLineStyles($objWriter, $minorGridlines); 548 $this->writeEffects($objWriter, $minorGridlines); 549 $objWriter->endElement(); //end spPr 550 $objWriter->endElement(); //end minorGridLines 551 } 552 553 if ($xAxisLabel !== null) { 554 $objWriter->startElement('c:title'); 555 $objWriter->startElement('c:tx'); 556 $objWriter->startElement('c:rich'); 557 558 $objWriter->startElement('a:bodyPr'); 559 $objWriter->endElement(); 560 561 $objWriter->startElement('a:lstStyle'); 562 $objWriter->endElement(); 563 564 $objWriter->startElement('a:p'); 565 566 $caption = $xAxisLabel->getCaption(); 567 if (is_array($caption)) { 568 $caption = $caption[0]; 569 } 570 $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a'); 571 572 $objWriter->endElement(); 573 $objWriter->endElement(); 574 $objWriter->endElement(); 575 576 $layout = $xAxisLabel->getLayout(); 577 $this->writeLayout($objWriter, $layout); 578 579 $objWriter->startElement('c:overlay'); 580 $objWriter->writeAttribute('val', '0'); 581 $objWriter->endElement(); 582 583 $objWriter->endElement(); 584 } 585 586 $objWriter->startElement('c:numFmt'); 587 $objWriter->writeAttribute('formatCode', $yAxis->getAxisNumberFormat()); 588 $objWriter->writeAttribute('sourceLinked', $yAxis->getAxisNumberSourceLinked()); 589 $objWriter->endElement(); 590 591 if (!empty($yAxis->getAxisOptionsProperty('major_tick_mark'))) { 592 $objWriter->startElement('c:majorTickMark'); 593 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark')); 594 $objWriter->endElement(); 595 } 596 597 if (!empty($yAxis->getAxisOptionsProperty('minor_tick_mark'))) { 598 $objWriter->startElement('c:minorTickMark'); 599 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark')); 600 $objWriter->endElement(); 601 } 602 603 if (!empty($yAxis->getAxisOptionsProperty('axis_labels'))) { 604 $objWriter->startElement('c:tickLblPos'); 605 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels')); 606 $objWriter->endElement(); 607 } 608 609 $textRotation = $yAxis->getAxisOptionsProperty('textRotation'); 610 if (is_numeric($textRotation)) { 611 $objWriter->startElement('c:txPr'); 612 $objWriter->startElement('a:bodyPr'); 613 $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation)); 614 $objWriter->endElement(); // a:bodyPr 615 $objWriter->startElement('a:lstStyle'); 616 $objWriter->endElement(); // a:lstStyle 617 $objWriter->startElement('a:p'); 618 $objWriter->startElement('a:pPr'); 619 $objWriter->startElement('a:defRPr'); 620 $objWriter->endElement(); // a:defRPr 621 $objWriter->endElement(); // a:pPr 622 $objWriter->endElement(); // a:p 623 $objWriter->endElement(); // c:txPr 624 } 625 626 $objWriter->startElement('c:spPr'); 627 $this->writeColor($objWriter, $yAxis->getFillColorObject()); 628 $this->writeEffects($objWriter, $yAxis); 629 $objWriter->endElement(); // spPr 630 631 if ($yAxis->getAxisOptionsProperty('major_unit') !== null) { 632 $objWriter->startElement('c:majorUnit'); 633 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_unit')); 634 $objWriter->endElement(); 635 } 636 637 if ($yAxis->getAxisOptionsProperty('minor_unit') !== null) { 638 $objWriter->startElement('c:minorUnit'); 639 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_unit')); 640 $objWriter->endElement(); 641 } 642 643 if ($id2 !== '0') { 644 $objWriter->startElement('c:crossAx'); 645 $objWriter->writeAttribute('val', $id2); 646 $objWriter->endElement(); 647 648 if (!empty($yAxis->getAxisOptionsProperty('horizontal_crosses'))) { 649 $objWriter->startElement('c:crosses'); 650 $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses')); 651 $objWriter->endElement(); 652 } 653 } 654 655 $objWriter->startElement('c:auto'); 656 // LineChart with dateAx wants '0' 657 $objWriter->writeAttribute('val', ($axisType === Axis::AXIS_TYPE_DATE) ? '0' : '1'); 658 $objWriter->endElement(); 659 660 $objWriter->startElement('c:lblAlgn'); 661 $objWriter->writeAttribute('val', 'ctr'); 662 $objWriter->endElement(); 663 664 $objWriter->startElement('c:lblOffset'); 665 $objWriter->writeAttribute('val', '100'); 666 $objWriter->endElement(); 667 668 if ($axisType === Axis::AXIS_TYPE_DATE) { 669 $property = 'baseTimeUnit'; 670 $propertyVal = $yAxis->getAxisOptionsProperty($property); 671 if (!empty($propertyVal)) { 672 $objWriter->startElement("c:$property"); 673 $objWriter->writeAttribute('val', $propertyVal); 674 $objWriter->endElement(); 675 } 676 $property = 'majorTimeUnit'; 677 $propertyVal = $yAxis->getAxisOptionsProperty($property); 678 if (!empty($propertyVal)) { 679 $objWriter->startElement("c:$property"); 680 $objWriter->writeAttribute('val', $propertyVal); 681 $objWriter->endElement(); 682 } 683 $property = 'minorTimeUnit'; 684 $propertyVal = $yAxis->getAxisOptionsProperty($property); 685 if (!empty($propertyVal)) { 686 $objWriter->startElement("c:$property"); 687 $objWriter->writeAttribute('val', $propertyVal); 688 $objWriter->endElement(); 689 } 690 } 691 692 if ($isMultiLevelSeries) { 693 $objWriter->startElement('c:noMultiLvlLbl'); 694 $objWriter->writeAttribute('val', '0'); 695 $objWriter->endElement(); 696 } 697 $objWriter->endElement(); 698 } 699 700 /** 701 * Write Value Axis. 702 * 703 * @param null|string $groupType Chart type 704 * @param string $id1 705 * @param string $id2 706 * @param bool $isMultiLevelSeries 707 */ 708 private function writeValueAxis(XMLWriter $objWriter, ?Title $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis): void 709 { 710 $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE); 711 $majorGridlines = $xAxis->getMajorGridlines(); 712 $minorGridlines = $xAxis->getMinorGridlines(); 713 714 if ($id2 !== '0') { 715 $objWriter->startElement('c:axId'); 716 $objWriter->writeAttribute('val', $id2); 717 $objWriter->endElement(); 718 } 719 720 $objWriter->startElement('c:scaling'); 721 722 if ($xAxis->getAxisOptionsProperty('maximum') !== null) { 723 $objWriter->startElement('c:max'); 724 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('maximum')); 725 $objWriter->endElement(); 726 } 727 728 if ($xAxis->getAxisOptionsProperty('minimum') !== null) { 729 $objWriter->startElement('c:min'); 730 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minimum')); 731 $objWriter->endElement(); 732 } 733 734 if (!empty($xAxis->getAxisOptionsProperty('orientation'))) { 735 $objWriter->startElement('c:orientation'); 736 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation')); 737 $objWriter->endElement(); 738 } 739 740 $objWriter->endElement(); // c:scaling 741 742 $objWriter->startElement('c:delete'); 743 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('hidden') ?? '0'); 744 $objWriter->endElement(); 745 746 $objWriter->startElement('c:axPos'); 747 $objWriter->writeAttribute('val', 'l'); 748 $objWriter->endElement(); 749 750 if ($majorGridlines !== null) { 751 $objWriter->startElement('c:majorGridlines'); 752 $objWriter->startElement('c:spPr'); 753 $this->writeLineStyles($objWriter, $majorGridlines); 754 $this->writeEffects($objWriter, $majorGridlines); 755 $objWriter->endElement(); //end spPr 756 $objWriter->endElement(); //end majorGridLines 757 } 758 759 if ($minorGridlines !== null && $minorGridlines->getObjectState()) { 760 $objWriter->startElement('c:minorGridlines'); 761 $objWriter->startElement('c:spPr'); 762 $this->writeLineStyles($objWriter, $minorGridlines); 763 $this->writeEffects($objWriter, $minorGridlines); 764 $objWriter->endElement(); //end spPr 765 $objWriter->endElement(); //end minorGridLines 766 } 767 768 if ($yAxisLabel !== null) { 769 $objWriter->startElement('c:title'); 770 $objWriter->startElement('c:tx'); 771 $objWriter->startElement('c:rich'); 772 773 $objWriter->startElement('a:bodyPr'); 774 $objWriter->endElement(); 775 776 $objWriter->startElement('a:lstStyle'); 777 $objWriter->endElement(); 778 779 $objWriter->startElement('a:p'); 780 781 $caption = $yAxisLabel->getCaption(); 782 if (is_array($caption)) { 783 $caption = $caption[0]; 784 } 785 $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a'); 786 787 $objWriter->endElement(); 788 $objWriter->endElement(); 789 $objWriter->endElement(); 790 791 if ($groupType !== DataSeries::TYPE_BUBBLECHART) { 792 $layout = $yAxisLabel->getLayout(); 793 $this->writeLayout($objWriter, $layout); 794 } 795 796 $objWriter->startElement('c:overlay'); 797 $objWriter->writeAttribute('val', '0'); 798 $objWriter->endElement(); 799 800 $objWriter->endElement(); 801 } 802 803 $objWriter->startElement('c:numFmt'); 804 $objWriter->writeAttribute('formatCode', $xAxis->getAxisNumberFormat()); 805 $objWriter->writeAttribute('sourceLinked', $xAxis->getAxisNumberSourceLinked()); 806 $objWriter->endElement(); 807 808 if (!empty($xAxis->getAxisOptionsProperty('major_tick_mark'))) { 809 $objWriter->startElement('c:majorTickMark'); 810 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark')); 811 $objWriter->endElement(); 812 } 813 814 if (!empty($xAxis->getAxisOptionsProperty('minor_tick_mark'))) { 815 $objWriter->startElement('c:minorTickMark'); 816 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark')); 817 $objWriter->endElement(); 818 } 819 820 if (!empty($xAxis->getAxisOptionsProperty('axis_labels'))) { 821 $objWriter->startElement('c:tickLblPos'); 822 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels')); 823 $objWriter->endElement(); 824 } 825 826 $textRotation = $xAxis->getAxisOptionsProperty('textRotation'); 827 if (is_numeric($textRotation)) { 828 $objWriter->startElement('c:txPr'); 829 $objWriter->startElement('a:bodyPr'); 830 $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation)); 831 $objWriter->endElement(); // a:bodyPr 832 $objWriter->startElement('a:lstStyle'); 833 $objWriter->endElement(); // a:lstStyle 834 $objWriter->startElement('a:p'); 835 $objWriter->startElement('a:pPr'); 836 $objWriter->startElement('a:defRPr'); 837 $objWriter->endElement(); // a:defRPr 838 $objWriter->endElement(); // a:pPr 839 $objWriter->endElement(); // a:p 840 $objWriter->endElement(); // c:txPr 841 } 842 843 $objWriter->startElement('c:spPr'); 844 $this->writeColor($objWriter, $xAxis->getFillColorObject()); 845 $this->writeLineStyles($objWriter, $xAxis); 846 $this->writeEffects($objWriter, $xAxis); 847 $objWriter->endElement(); //end spPr 848 849 if ($id1 !== '0') { 850 $objWriter->startElement('c:crossAx'); 851 $objWriter->writeAttribute('val', $id1); 852 $objWriter->endElement(); 853 854 if ($xAxis->getAxisOptionsProperty('horizontal_crosses_value') !== null) { 855 $objWriter->startElement('c:crossesAt'); 856 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses_value')); 857 $objWriter->endElement(); 858 } else { 859 $crosses = $xAxis->getAxisOptionsProperty('horizontal_crosses'); 860 if ($crosses) { 861 $objWriter->startElement('c:crosses'); 862 $objWriter->writeAttribute('val', $crosses); 863 $objWriter->endElement(); 864 } 865 } 866 867 $crossBetween = $xAxis->getCrossBetween(); 868 if ($crossBetween !== '') { 869 $objWriter->startElement('c:crossBetween'); 870 $objWriter->writeAttribute('val', $crossBetween); 871 $objWriter->endElement(); 872 } 873 874 if ($xAxis->getAxisOptionsProperty('major_unit') !== null) { 875 $objWriter->startElement('c:majorUnit'); 876 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_unit')); 877 $objWriter->endElement(); 878 } 879 880 if ($xAxis->getAxisOptionsProperty('minor_unit') !== null) { 881 $objWriter->startElement('c:minorUnit'); 882 $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_unit')); 883 $objWriter->endElement(); 884 } 885 } 886 887 if ($isMultiLevelSeries) { 888 if ($groupType !== DataSeries::TYPE_BUBBLECHART) { 889 $objWriter->startElement('c:noMultiLvlLbl'); 890 $objWriter->writeAttribute('val', '0'); 891 $objWriter->endElement(); 892 } 893 } 894 895 $objWriter->endElement(); 896 } 897 898 /** 899 * Write Ser Axis, for Surface chart. 900 */ 901 private function writeSerAxis(XMLWriter $objWriter, string $id2, string $id3): void 902 { 903 $objWriter->startElement('c:serAx'); 904 905 $objWriter->startElement('c:axId'); 906 $objWriter->writeAttribute('val', $id3); 907 $objWriter->endElement(); // axId 908 909 $objWriter->startElement('c:scaling'); 910 $objWriter->startElement('c:orientation'); 911 $objWriter->writeAttribute('val', 'minMax'); 912 $objWriter->endElement(); // orientation 913 $objWriter->endElement(); // scaling 914 915 $objWriter->startElement('c:delete'); 916 $objWriter->writeAttribute('val', '0'); 917 $objWriter->endElement(); // delete 918 919 $objWriter->startElement('c:axPos'); 920 $objWriter->writeAttribute('val', 'b'); 921 $objWriter->endElement(); // axPos 922 923 $objWriter->startElement('c:majorTickMark'); 924 $objWriter->writeAttribute('val', 'out'); 925 $objWriter->endElement(); // majorTickMark 926 927 $objWriter->startElement('c:minorTickMark'); 928 $objWriter->writeAttribute('val', 'none'); 929 $objWriter->endElement(); // minorTickMark 930 931 $objWriter->startElement('c:tickLblPos'); 932 $objWriter->writeAttribute('val', 'nextTo'); 933 $objWriter->endElement(); // tickLblPos 934 935 $objWriter->startElement('c:crossAx'); 936 $objWriter->writeAttribute('val', $id2); 937 $objWriter->endElement(); // crossAx 938 939 $objWriter->startElement('c:crosses'); 940 $objWriter->writeAttribute('val', 'autoZero'); 941 $objWriter->endElement(); // crosses 942 943 $objWriter->endElement(); //serAx 944 } 945 946 /** 947 * Get the data series type(s) for a chart plot series. 948 * 949 * @return string[] 950 */ 951 private static function getChartType(PlotArea $plotArea): array 952 { 953 $groupCount = $plotArea->getPlotGroupCount(); 954 955 if ($groupCount == 1) { 956 $chartType = [$plotArea->getPlotGroupByIndex(0)->getPlotType()]; 957 } else { 958 $chartTypes = []; 959 for ($i = 0; $i < $groupCount; ++$i) { 960 $chartTypes[] = $plotArea->getPlotGroupByIndex($i)->getPlotType(); 961 } 962 $chartType = array_unique($chartTypes); 963 if (count($chartTypes) == 0) { 964 throw new WriterException('Chart is not yet implemented'); 965 } 966 } 967 968 return $chartType; 969 } 970 971 /** 972 * Method writing plot series values. 973 */ 974 private function writePlotSeriesValuesElement(XMLWriter $objWriter, int $val, ?ChartColor $fillColor): void 975 { 976 if ($fillColor === null || !$fillColor->isUsable()) { 977 return; 978 } 979 $objWriter->startElement('c:dPt'); 980 981 $objWriter->startElement('c:idx'); 982 $objWriter->writeAttribute('val', "$val"); 983 $objWriter->endElement(); // c:idx 984 985 $objWriter->startElement('c:spPr'); 986 $this->writeColor($objWriter, $fillColor); 987 $objWriter->endElement(); // c:spPr 988 989 $objWriter->endElement(); // c:dPt 990 } 991 992 /** 993 * Write Plot Group (series of related plots). 994 * 995 * @param string $groupType Type of plot for dataseries 996 * @param bool $catIsMultiLevelSeries Is category a multi-series category 997 * @param bool $valIsMultiLevelSeries Is value set a multi-series set 998 * @param string $plotGroupingType Type of grouping for multi-series values 999 */ 1000 private function writePlotGroup(?DataSeries $plotGroup, string $groupType, XMLWriter $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void 1001 { 1002 if ($plotGroup === null) { 1003 return; 1004 } 1005 1006 if (($groupType == DataSeries::TYPE_BARCHART) || ($groupType == DataSeries::TYPE_BARCHART_3D)) { 1007 $objWriter->startElement('c:barDir'); 1008 $objWriter->writeAttribute('val', $plotGroup->getPlotDirection()); 1009 $objWriter->endElement(); 1010 } 1011 1012 $plotGroupingType = $plotGroup->getPlotGrouping(); 1013 if ($plotGroupingType !== null && $groupType !== DataSeries::TYPE_SURFACECHART && $groupType !== DataSeries::TYPE_SURFACECHART_3D) { 1014 $objWriter->startElement('c:grouping'); 1015 $objWriter->writeAttribute('val', $plotGroupingType); 1016 $objWriter->endElement(); 1017 } 1018 1019 // Get these details before the loop, because we can use the count to check for varyColors 1020 $plotSeriesOrder = $plotGroup->getPlotOrder(); 1021 $plotSeriesCount = count($plotSeriesOrder); 1022 1023 if (($groupType !== DataSeries::TYPE_RADARCHART) && ($groupType !== DataSeries::TYPE_STOCKCHART)) { 1024 if ($groupType !== DataSeries::TYPE_LINECHART) { 1025 if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART) || ($plotSeriesCount > 1)) { 1026 $objWriter->startElement('c:varyColors'); 1027 $objWriter->writeAttribute('val', '1'); 1028 $objWriter->endElement(); 1029 } else { 1030 $objWriter->startElement('c:varyColors'); 1031 $objWriter->writeAttribute('val', '0'); 1032 $objWriter->endElement(); 1033 } 1034 } 1035 } 1036 1037 $plotSeriesIdx = 0; 1038 foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) { 1039 $objWriter->startElement('c:ser'); 1040 1041 $objWriter->startElement('c:idx'); 1042 $objWriter->writeAttribute('val', (string) ($this->seriesIndex + $plotSeriesIdx)); 1043 $objWriter->endElement(); 1044 1045 $objWriter->startElement('c:order'); 1046 $objWriter->writeAttribute('val', (string) ($this->seriesIndex + $plotSeriesRef)); 1047 $objWriter->endElement(); 1048 1049 $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx); 1050 $labelFill = null; 1051 if ($plotLabel && $groupType === DataSeries::TYPE_LINECHART) { 1052 $labelFill = $plotLabel->getFillColorObject(); 1053 $labelFill = ($labelFill instanceof ChartColor) ? $labelFill : null; 1054 } 1055 if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) { 1056 $fillColor = $plotLabel->getFillColorObject(); 1057 if ($fillColor !== null && !is_array($fillColor) && $fillColor->isUsable()) { 1058 $objWriter->startElement('c:spPr'); 1059 $this->writeColor($objWriter, $fillColor); 1060 $objWriter->endElement(); // c:spPr 1061 } 1062 } 1063 1064 // Values 1065 $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesIdx); 1066 1067 if ($plotSeriesValues !== false && in_array($groupType, self::CUSTOM_COLOR_TYPES, true)) { 1068 $fillColorValues = $plotSeriesValues->getFillColorObject(); 1069 if ($fillColorValues !== null && is_array($fillColorValues)) { 1070 foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) { 1071 $this->writePlotSeriesValuesElement($objWriter, $dataKey, $fillColorValues[$dataKey] ?? null); 1072 } 1073 } 1074 } 1075 if ($plotSeriesValues !== false && $plotSeriesValues->getLabelLayout()) { 1076 $this->writeDataLabels($objWriter, $plotSeriesValues->getLabelLayout()); 1077 } 1078 1079 // Labels 1080 $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx); 1081 if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) { 1082 $objWriter->startElement('c:tx'); 1083 $objWriter->startElement('c:strRef'); 1084 $this->writePlotSeriesLabel($plotSeriesLabel, $objWriter); 1085 $objWriter->endElement(); 1086 $objWriter->endElement(); 1087 } 1088 1089 // Formatting for the points 1090 if ( 1091 $plotSeriesValues !== false 1092 ) { 1093 $objWriter->startElement('c:spPr'); 1094 $fillObject = $labelFill ?? $plotSeriesValues->getFillColorObject(); 1095 $callLineStyles = true; 1096 if ($fillObject instanceof ChartColor && $fillObject->isUsable()) { 1097 if ($groupType === DataSeries::TYPE_LINECHART) { 1098 $objWriter->startElement('a:ln'); 1099 $callLineStyles = false; 1100 } 1101 $this->writeColor($objWriter, $fillObject); 1102 if (!$callLineStyles) { 1103 $objWriter->endElement(); // a:ln 1104 } 1105 } 1106 $nofill = $groupType === DataSeries::TYPE_STOCKCHART || (($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) && !$plotSeriesValues->getScatterLines()); 1107 if ($callLineStyles) { 1108 $this->writeLineStyles($objWriter, $plotSeriesValues, $nofill); 1109 $this->writeEffects($objWriter, $plotSeriesValues); 1110 } 1111 $objWriter->endElement(); // c:spPr 1112 } 1113 1114 if ($plotSeriesValues) { 1115 $plotSeriesMarker = $plotSeriesValues->getPointMarker(); 1116 $markerFillColor = $plotSeriesValues->getMarkerFillColor(); 1117 $fillUsed = $markerFillColor->IsUsable(); 1118 $markerBorderColor = $plotSeriesValues->getMarkerBorderColor(); 1119 $borderUsed = $markerBorderColor->isUsable(); 1120 if ($plotSeriesMarker || $fillUsed || $borderUsed) { 1121 $objWriter->startElement('c:marker'); 1122 $objWriter->startElement('c:symbol'); 1123 if ($plotSeriesMarker) { 1124 $objWriter->writeAttribute('val', $plotSeriesMarker); 1125 } 1126 $objWriter->endElement(); 1127 1128 if ($plotSeriesMarker !== 'none') { 1129 $objWriter->startElement('c:size'); 1130 $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointSize()); 1131 $objWriter->endElement(); // c:size 1132 $objWriter->startElement('c:spPr'); 1133 $this->writeColor($objWriter, $markerFillColor); 1134 if ($borderUsed) { 1135 $objWriter->startElement('a:ln'); 1136 $this->writeColor($objWriter, $markerBorderColor); 1137 $objWriter->endElement(); // a:ln 1138 } 1139 $objWriter->endElement(); // spPr 1140 } 1141 1142 $objWriter->endElement(); 1143 } 1144 } 1145 1146 if (($groupType === DataSeries::TYPE_BARCHART) || ($groupType === DataSeries::TYPE_BARCHART_3D) || ($groupType === DataSeries::TYPE_BUBBLECHART)) { 1147 $objWriter->startElement('c:invertIfNegative'); 1148 $objWriter->writeAttribute('val', '0'); 1149 $objWriter->endElement(); 1150 } 1151 // Trendlines 1152 if ($plotSeriesValues !== false) { 1153 foreach ($plotSeriesValues->getTrendLines() as $trendLine) { 1154 $trendLineType = $trendLine->getTrendLineType(); 1155 $order = $trendLine->getOrder(); 1156 $period = $trendLine->getPeriod(); 1157 $dispRSqr = $trendLine->getDispRSqr(); 1158 $dispEq = $trendLine->getDispEq(); 1159 $forward = $trendLine->getForward(); 1160 $backward = $trendLine->getBackward(); 1161 $intercept = $trendLine->getIntercept(); 1162 $name = $trendLine->getName(); 1163 $trendLineColor = $trendLine->getLineColor(); // ChartColor 1164 1165 $objWriter->startElement('c:trendline'); // N.B. lowercase 'ell' 1166 if ($name !== '') { 1167 $objWriter->startElement('c:name'); 1168 $objWriter->writeRawData($name); 1169 $objWriter->endElement(); // c:name 1170 } 1171 $objWriter->startElement('c:spPr'); 1172 1173 if (!$trendLineColor->isUsable()) { 1174 // use dataSeriesValues line color as a backup if $trendLineColor is null 1175 $dsvLineColor = $plotSeriesValues->getLineColor(); 1176 if ($dsvLineColor->isUsable()) { 1177 $trendLine 1178 ->getLineColor() 1179 ->setColorProperties($dsvLineColor->getValue(), $dsvLineColor->getAlpha(), $dsvLineColor->getType()); 1180 } 1181 } // otherwise, hope Excel does the right thing 1182 1183 $this->writeLineStyles($objWriter, $trendLine, false); // suppress noFill 1184 1185 $objWriter->endElement(); // spPr 1186 1187 $objWriter->startElement('c:trendlineType'); // N.B lowercase 'ell' 1188 $objWriter->writeAttribute('val', $trendLineType); 1189 $objWriter->endElement(); // trendlineType 1190 if ($backward !== 0.0) { 1191 $objWriter->startElement('c:backward'); 1192 $objWriter->writeAttribute('val', "$backward"); 1193 $objWriter->endElement(); // c:backward 1194 } 1195 if ($forward !== 0.0) { 1196 $objWriter->startElement('c:forward'); 1197 $objWriter->writeAttribute('val', "$forward"); 1198 $objWriter->endElement(); // c:forward 1199 } 1200 if ($intercept !== 0.0) { 1201 $objWriter->startElement('c:intercept'); 1202 $objWriter->writeAttribute('val', "$intercept"); 1203 $objWriter->endElement(); // c:intercept 1204 } 1205 if ($trendLineType == TrendLine::TRENDLINE_POLYNOMIAL) { 1206 $objWriter->startElement('c:order'); 1207 $objWriter->writeAttribute('val', $order); 1208 $objWriter->endElement(); // order 1209 } 1210 if ($trendLineType == TrendLine::TRENDLINE_MOVING_AVG) { 1211 $objWriter->startElement('c:period'); 1212 $objWriter->writeAttribute('val', $period); 1213 $objWriter->endElement(); // period 1214 } 1215 $objWriter->startElement('c:dispRSqr'); 1216 $objWriter->writeAttribute('val', $dispRSqr ? '1' : '0'); 1217 $objWriter->endElement(); 1218 $objWriter->startElement('c:dispEq'); 1219 $objWriter->writeAttribute('val', $dispEq ? '1' : '0'); 1220 $objWriter->endElement(); 1221 if ($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) { 1222 $objWriter->startElement('c:trendlineLbl'); 1223 $objWriter->startElement('c:numFmt'); 1224 $objWriter->writeAttribute('formatCode', 'General'); 1225 $objWriter->writeAttribute('sourceLinked', '0'); 1226 $objWriter->endElement(); // numFmt 1227 $objWriter->endElement(); // trendlineLbl 1228 } 1229 1230 $objWriter->endElement(); // trendline 1231 } 1232 } 1233 1234 // Category Labels 1235 $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesIdx); 1236 if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) { 1237 $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries(); 1238 1239 if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) { 1240 $plotStyle = $plotGroup->getPlotStyle(); 1241 if (is_numeric($plotStyle)) { 1242 $objWriter->startElement('c:explosion'); 1243 $objWriter->writeAttribute('val', $plotStyle); 1244 $objWriter->endElement(); 1245 } 1246 } 1247 1248 if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) { 1249 $objWriter->startElement('c:xVal'); 1250 } else { 1251 $objWriter->startElement('c:cat'); 1252 } 1253 1254 // xVals (Categories) are not always 'str' 1255 // Test X-axis Label's Datatype to decide 'str' vs 'num' 1256 $CategoryDatatype = $plotSeriesCategory->getDataType(); 1257 if ($CategoryDatatype == DataSeriesValues::DATASERIES_TYPE_NUMBER) { 1258 $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'num'); 1259 } else { 1260 $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str'); 1261 } 1262 $objWriter->endElement(); 1263 } 1264 1265 // Values 1266 if ($plotSeriesValues) { 1267 $valIsMultiLevelSeries = $valIsMultiLevelSeries || $plotSeriesValues->isMultiLevelSeries(); 1268 1269 if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) { 1270 $objWriter->startElement('c:yVal'); 1271 } else { 1272 $objWriter->startElement('c:val'); 1273 } 1274 1275 $this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num'); 1276 $objWriter->endElement(); 1277 if ($groupType === DataSeries::TYPE_SCATTERCHART && $plotGroup->getPlotStyle() === 'smoothMarker') { 1278 $objWriter->startElement('c:smooth'); 1279 $objWriter->writeAttribute('val', $plotSeriesValues->getSmoothLine() ? '1' : '0'); 1280 $objWriter->endElement(); 1281 } 1282 } 1283 1284 if ($groupType === DataSeries::TYPE_BUBBLECHART) { 1285 if (!empty($plotGroup->getPlotBubbleSizes()[$plotSeriesIdx])) { 1286 $objWriter->startElement('c:bubbleSize'); 1287 $this->writePlotSeriesValues( 1288 $plotGroup->getPlotBubbleSizes()[$plotSeriesIdx], 1289 $objWriter, 1290 $groupType, 1291 'num' 1292 ); 1293 $objWriter->endElement(); 1294 if ($plotSeriesValues !== false) { 1295 $objWriter->startElement('c:bubble3D'); 1296 $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0'); 1297 $objWriter->endElement(); 1298 } 1299 } elseif ($plotSeriesValues !== false) { 1300 $this->writeBubbles($plotSeriesValues, $objWriter); 1301 } 1302 } 1303 1304 $objWriter->endElement(); 1305 } 1306 1307 $this->seriesIndex += $plotSeriesIdx + 1; 1308 } 1309 1310 /** 1311 * Write Plot Series Label. 1312 */ 1313 private function writePlotSeriesLabel(?DataSeriesValues $plotSeriesLabel, XMLWriter $objWriter): void 1314 { 1315 if ($plotSeriesLabel === null) { 1316 return; 1317 } 1318 1319 $objWriter->startElement('c:f'); 1320 $objWriter->writeRawData($plotSeriesLabel->getDataSource()); 1321 $objWriter->endElement(); 1322 1323 $objWriter->startElement('c:strCache'); 1324 $objWriter->startElement('c:ptCount'); 1325 $objWriter->writeAttribute('val', (string) $plotSeriesLabel->getPointCount()); 1326 $objWriter->endElement(); 1327 1328 foreach ($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) { 1329 $objWriter->startElement('c:pt'); 1330 $objWriter->writeAttribute('idx', $plotLabelKey); 1331 1332 $objWriter->startElement('c:v'); 1333 $objWriter->writeRawData($plotLabelValue); 1334 $objWriter->endElement(); 1335 $objWriter->endElement(); 1336 } 1337 $objWriter->endElement(); 1338 } 1339 1340 /** 1341 * Write Plot Series Values. 1342 * 1343 * @param string $groupType Type of plot for dataseries 1344 * @param string $dataType Datatype of series values 1345 */ 1346 private function writePlotSeriesValues(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str'): void 1347 { 1348 if ($plotSeriesValues === null) { 1349 return; 1350 } 1351 1352 if ($plotSeriesValues->isMultiLevelSeries()) { 1353 $levelCount = $plotSeriesValues->multiLevelCount(); 1354 1355 $objWriter->startElement('c:multiLvlStrRef'); 1356 1357 $objWriter->startElement('c:f'); 1358 $objWriter->writeRawData($plotSeriesValues->getDataSource()); 1359 $objWriter->endElement(); 1360 1361 $objWriter->startElement('c:multiLvlStrCache'); 1362 1363 $objWriter->startElement('c:ptCount'); 1364 $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount()); 1365 $objWriter->endElement(); 1366 1367 for ($level = 0; $level < $levelCount; ++$level) { 1368 $objWriter->startElement('c:lvl'); 1369 1370 foreach ($plotSeriesValues->getDataValues() as $plotSeriesKey => $plotSeriesValue) { 1371 if (isset($plotSeriesValue[$level])) { 1372 $objWriter->startElement('c:pt'); 1373 $objWriter->writeAttribute('idx', $plotSeriesKey); 1374 1375 $objWriter->startElement('c:v'); 1376 $objWriter->writeRawData($plotSeriesValue[$level]); 1377 $objWriter->endElement(); 1378 $objWriter->endElement(); 1379 } 1380 } 1381 1382 $objWriter->endElement(); 1383 } 1384 1385 $objWriter->endElement(); 1386 1387 $objWriter->endElement(); 1388 } else { 1389 $objWriter->startElement('c:' . $dataType . 'Ref'); 1390 1391 $objWriter->startElement('c:f'); 1392 $objWriter->writeRawData($plotSeriesValues->getDataSource()); 1393 $objWriter->endElement(); 1394 1395 $count = $plotSeriesValues->getPointCount(); 1396 $source = $plotSeriesValues->getDataSource(); 1397 $values = $plotSeriesValues->getDataValues(); 1398 if ($count > 1 || ($count === 1 && "=$source" !== (string) $values[0])) { 1399 $objWriter->startElement('c:' . $dataType . 'Cache'); 1400 1401 if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) { 1402 if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) { 1403 $objWriter->startElement('c:formatCode'); 1404 $objWriter->writeRawData($plotSeriesValues->getFormatCode()); 1405 $objWriter->endElement(); 1406 } 1407 } 1408 1409 $objWriter->startElement('c:ptCount'); 1410 $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount()); 1411 $objWriter->endElement(); 1412 1413 $dataValues = $plotSeriesValues->getDataValues(); 1414 if (!empty($dataValues)) { 1415 if (is_array($dataValues)) { 1416 foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) { 1417 $objWriter->startElement('c:pt'); 1418 $objWriter->writeAttribute('idx', $plotSeriesKey); 1419 1420 $objWriter->startElement('c:v'); 1421 $objWriter->writeRawData($plotSeriesValue); 1422 $objWriter->endElement(); 1423 $objWriter->endElement(); 1424 } 1425 } 1426 } 1427 1428 $objWriter->endElement(); // *Cache 1429 } 1430 1431 $objWriter->endElement(); // *Ref 1432 } 1433 } 1434 1435 private const CUSTOM_COLOR_TYPES = [ 1436 DataSeries::TYPE_BARCHART, 1437 DataSeries::TYPE_BARCHART_3D, 1438 DataSeries::TYPE_PIECHART, 1439 DataSeries::TYPE_PIECHART_3D, 1440 DataSeries::TYPE_DONUTCHART, 1441 ]; 1442 1443 /** 1444 * Write Bubble Chart Details. 1445 */ 1446 private function writeBubbles(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter): void 1447 { 1448 if ($plotSeriesValues === null) { 1449 return; 1450 } 1451 1452 $objWriter->startElement('c:bubbleSize'); 1453 $objWriter->startElement('c:numLit'); 1454 1455 $objWriter->startElement('c:formatCode'); 1456 $objWriter->writeRawData('General'); 1457 $objWriter->endElement(); 1458 1459 $objWriter->startElement('c:ptCount'); 1460 $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount()); 1461 $objWriter->endElement(); 1462 1463 $dataValues = $plotSeriesValues->getDataValues(); 1464 if (!empty($dataValues)) { 1465 if (is_array($dataValues)) { 1466 foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) { 1467 $objWriter->startElement('c:pt'); 1468 $objWriter->writeAttribute('idx', $plotSeriesKey); 1469 $objWriter->startElement('c:v'); 1470 $objWriter->writeRawData('1'); 1471 $objWriter->endElement(); 1472 $objWriter->endElement(); 1473 } 1474 } 1475 } 1476 1477 $objWriter->endElement(); 1478 $objWriter->endElement(); 1479 1480 $objWriter->startElement('c:bubble3D'); 1481 $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0'); 1482 $objWriter->endElement(); 1483 } 1484 1485 /** 1486 * Write Layout. 1487 */ 1488 private function writeLayout(XMLWriter $objWriter, ?Layout $layout = null): void 1489 { 1490 $objWriter->startElement('c:layout'); 1491 1492 if ($layout !== null) { 1493 $objWriter->startElement('c:manualLayout'); 1494 1495 $layoutTarget = $layout->getLayoutTarget(); 1496 if ($layoutTarget !== null) { 1497 $objWriter->startElement('c:layoutTarget'); 1498 $objWriter->writeAttribute('val', $layoutTarget); 1499 $objWriter->endElement(); 1500 } 1501 1502 $xMode = $layout->getXMode(); 1503 if ($xMode !== null) { 1504 $objWriter->startElement('c:xMode'); 1505 $objWriter->writeAttribute('val', $xMode); 1506 $objWriter->endElement(); 1507 } 1508 1509 $yMode = $layout->getYMode(); 1510 if ($yMode !== null) { 1511 $objWriter->startElement('c:yMode'); 1512 $objWriter->writeAttribute('val', $yMode); 1513 $objWriter->endElement(); 1514 } 1515 1516 $x = $layout->getXPosition(); 1517 if ($x !== null) { 1518 $objWriter->startElement('c:x'); 1519 $objWriter->writeAttribute('val', "$x"); 1520 $objWriter->endElement(); 1521 } 1522 1523 $y = $layout->getYPosition(); 1524 if ($y !== null) { 1525 $objWriter->startElement('c:y'); 1526 $objWriter->writeAttribute('val', "$y"); 1527 $objWriter->endElement(); 1528 } 1529 1530 $w = $layout->getWidth(); 1531 if ($w !== null) { 1532 $objWriter->startElement('c:w'); 1533 $objWriter->writeAttribute('val', "$w"); 1534 $objWriter->endElement(); 1535 } 1536 1537 $h = $layout->getHeight(); 1538 if ($h !== null) { 1539 $objWriter->startElement('c:h'); 1540 $objWriter->writeAttribute('val', "$h"); 1541 $objWriter->endElement(); 1542 } 1543 1544 $objWriter->endElement(); 1545 } 1546 1547 $objWriter->endElement(); 1548 } 1549 1550 /** 1551 * Write Alternate Content block. 1552 */ 1553 private function writeAlternateContent(XMLWriter $objWriter): void 1554 { 1555 $objWriter->startElement('mc:AlternateContent'); 1556 $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); 1557 1558 $objWriter->startElement('mc:Choice'); 1559 $objWriter->writeAttribute('Requires', 'c14'); 1560 $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart'); 1561 1562 $objWriter->startElement('c14:style'); 1563 $objWriter->writeAttribute('val', '102'); 1564 $objWriter->endElement(); 1565 $objWriter->endElement(); 1566 1567 $objWriter->startElement('mc:Fallback'); 1568 $objWriter->startElement('c:style'); 1569 $objWriter->writeAttribute('val', '2'); 1570 $objWriter->endElement(); 1571 $objWriter->endElement(); 1572 1573 $objWriter->endElement(); 1574 } 1575 1576 /** 1577 * Write Printer Settings. 1578 */ 1579 private function writePrintSettings(XMLWriter $objWriter): void 1580 { 1581 $objWriter->startElement('c:printSettings'); 1582 1583 $objWriter->startElement('c:headerFooter'); 1584 $objWriter->endElement(); 1585 1586 $objWriter->startElement('c:pageMargins'); 1587 $objWriter->writeAttribute('footer', '0.3'); 1588 $objWriter->writeAttribute('header', '0.3'); 1589 $objWriter->writeAttribute('r', '0.7'); 1590 $objWriter->writeAttribute('l', '0.7'); 1591 $objWriter->writeAttribute('t', '0.75'); 1592 $objWriter->writeAttribute('b', '0.75'); 1593 $objWriter->endElement(); 1594 1595 $objWriter->startElement('c:pageSetup'); 1596 $objWriter->writeAttribute('orientation', 'portrait'); 1597 $objWriter->endElement(); 1598 1599 $objWriter->endElement(); 1600 } 1601 1602 private function writeEffects(XMLWriter $objWriter, Properties $yAxis): void 1603 { 1604 if ( 1605 !empty($yAxis->getSoftEdgesSize()) 1606 || !empty($yAxis->getShadowProperty('effect')) 1607 || !empty($yAxis->getGlowProperty('size')) 1608 ) { 1609 $objWriter->startElement('a:effectLst'); 1610 $this->writeGlow($objWriter, $yAxis); 1611 $this->writeShadow($objWriter, $yAxis); 1612 $this->writeSoftEdge($objWriter, $yAxis); 1613 $objWriter->endElement(); // effectLst 1614 } 1615 } 1616 1617 private function writeShadow(XMLWriter $objWriter, Properties $xAxis): void 1618 { 1619 if (empty($xAxis->getShadowProperty('effect'))) { 1620 return; 1621 } 1622 /** @var string */ 1623 $effect = $xAxis->getShadowProperty('effect'); 1624 $objWriter->startElement("a:$effect"); 1625 1626 if (is_numeric($xAxis->getShadowProperty('blur'))) { 1627 $objWriter->writeAttribute('blurRad', Properties::pointsToXml((float) $xAxis->getShadowProperty('blur'))); 1628 } 1629 if (is_numeric($xAxis->getShadowProperty('distance'))) { 1630 $objWriter->writeAttribute('dist', Properties::pointsToXml((float) $xAxis->getShadowProperty('distance'))); 1631 } 1632 if (is_numeric($xAxis->getShadowProperty('direction'))) { 1633 $objWriter->writeAttribute('dir', Properties::angleToXml((float) $xAxis->getShadowProperty('direction'))); 1634 } 1635 $algn = $xAxis->getShadowProperty('algn'); 1636 if (is_string($algn) && $algn !== '') { 1637 $objWriter->writeAttribute('algn', $algn); 1638 } 1639 foreach (['sx', 'sy'] as $sizeType) { 1640 $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]); 1641 if (is_numeric($sizeValue)) { 1642 $objWriter->writeAttribute($sizeType, Properties::tenthOfPercentToXml((float) $sizeValue)); 1643 } 1644 } 1645 foreach (['kx', 'ky'] as $sizeType) { 1646 $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]); 1647 if (is_numeric($sizeValue)) { 1648 $objWriter->writeAttribute($sizeType, Properties::angleToXml((float) $sizeValue)); 1649 } 1650 } 1651 $rotWithShape = $xAxis->getShadowProperty('rotWithShape'); 1652 if (is_numeric($rotWithShape)) { 1653 $objWriter->writeAttribute('rotWithShape', (string) (int) $rotWithShape); 1654 } 1655 1656 $this->writeColor($objWriter, $xAxis->getShadowColorObject(), false); 1657 1658 $objWriter->endElement(); 1659 } 1660 1661 private function writeGlow(XMLWriter $objWriter, Properties $yAxis): void 1662 { 1663 $size = $yAxis->getGlowProperty('size'); 1664 if (empty($size)) { 1665 return; 1666 } 1667 $objWriter->startElement('a:glow'); 1668 $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $size)); 1669 $this->writeColor($objWriter, $yAxis->getGlowColorObject(), false); 1670 $objWriter->endElement(); // glow 1671 } 1672 1673 private function writeSoftEdge(XMLWriter $objWriter, Properties $yAxis): void 1674 { 1675 $softEdgeSize = $yAxis->getSoftEdgesSize(); 1676 if (empty($softEdgeSize)) { 1677 return; 1678 } 1679 $objWriter->startElement('a:softEdge'); 1680 $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $softEdgeSize)); 1681 $objWriter->endElement(); //end softEdge 1682 } 1683 1684 private function writeLineStyles(XMLWriter $objWriter, Properties $gridlines, bool $noFill = false): void 1685 { 1686 $objWriter->startElement('a:ln'); 1687 $widthTemp = $gridlines->getLineStyleProperty('width'); 1688 if (is_numeric($widthTemp)) { 1689 $objWriter->writeAttribute('w', Properties::pointsToXml((float) $widthTemp)); 1690 } 1691 $this->writeNotEmpty($objWriter, 'cap', $gridlines->getLineStyleProperty('cap')); 1692 $this->writeNotEmpty($objWriter, 'cmpd', $gridlines->getLineStyleProperty('compound')); 1693 if ($noFill) { 1694 $objWriter->startElement('a:noFill'); 1695 $objWriter->endElement(); 1696 } else { 1697 $this->writeColor($objWriter, $gridlines->getLineColor()); 1698 } 1699 1700 $dash = $gridlines->getLineStyleProperty('dash'); 1701 if (!empty($dash)) { 1702 $objWriter->startElement('a:prstDash'); 1703 $this->writeNotEmpty($objWriter, 'val', $dash); 1704 $objWriter->endElement(); 1705 } 1706 1707 if ($gridlines->getLineStyleProperty('join') === 'miter') { 1708 $objWriter->startElement('a:miter'); 1709 $objWriter->writeAttribute('lim', '800000'); 1710 $objWriter->endElement(); 1711 } elseif ($gridlines->getLineStyleProperty('join') === 'bevel') { 1712 $objWriter->startElement('a:bevel'); 1713 $objWriter->endElement(); 1714 } 1715 1716 if ($gridlines->getLineStyleProperty(['arrow', 'head', 'type'])) { 1717 $objWriter->startElement('a:headEnd'); 1718 $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'head', 'type'])); 1719 $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('head')); 1720 $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('head')); 1721 $objWriter->endElement(); 1722 } 1723 1724 if ($gridlines->getLineStyleProperty(['arrow', 'end', 'type'])) { 1725 $objWriter->startElement('a:tailEnd'); 1726 $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'end', 'type'])); 1727 $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('end')); 1728 $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('end')); 1729 $objWriter->endElement(); 1730 } 1731 $objWriter->endElement(); //end ln 1732 } 1733 1734 private function writeNotEmpty(XMLWriter $objWriter, string $name, ?string $value): void 1735 { 1736 if ($value !== null && $value !== '') { 1737 $objWriter->writeAttribute($name, $value); 1738 } 1739 } 1740 1741 private function writeColor(XMLWriter $objWriter, ChartColor $chartColor, bool $solidFill = true): void 1742 { 1743 $type = $chartColor->getType(); 1744 $value = $chartColor->getValue(); 1745 if (!empty($type) && !empty($value)) { 1746 if ($solidFill) { 1747 $objWriter->startElement('a:solidFill'); 1748 } 1749 $objWriter->startElement("a:$type"); 1750 $objWriter->writeAttribute('val', $value); 1751 $alpha = $chartColor->getAlpha(); 1752 if (is_numeric($alpha)) { 1753 $objWriter->startElement('a:alpha'); 1754 $objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha)); 1755 $objWriter->endElement(); // a:alpha 1756 } 1757 $brightness = $chartColor->getBrightness(); 1758 if (is_numeric($brightness)) { 1759 $brightness = (int) $brightness; 1760 $lumOff = 100 - $brightness; 1761 $objWriter->startElement('a:lumMod'); 1762 $objWriter->writeAttribute('val', ChartColor::alphaToXml($brightness)); 1763 $objWriter->endElement(); // a:lumMod 1764 $objWriter->startElement('a:lumOff'); 1765 $objWriter->writeAttribute('val', ChartColor::alphaToXml($lumOff)); 1766 $objWriter->endElement(); // a:lumOff 1767 } 1768 $objWriter->endElement(); //a:srgbClr/schemeClr/prstClr 1769 if ($solidFill) { 1770 $objWriter->endElement(); //a:solidFill 1771 } 1772 } 1773 } 1774 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body