// SPDX-License-Identifier: GPL-3.0-or-later namespace Icinga\Chart\Graph; use DOMElement; use Icinga\Chart\Primitive\Drawable; use Icinga\Chart\Primitive\Path; use Icinga\Chart\Primitive\Circle; use Icinga\Chart\Primitive\Styleable; use Icinga\Chart\Render\RenderContext; /** * LineGraph implementation for drawing a set of datapoints as * a connected path */ class LineGraph extends Styleable implements Drawable { /** * The dataset to use * * @var array */ private $dataset; /** * True to show dots for each datapoint * * @var bool */ private $showDataPoints = false; /** * When true, the path will be discrete, i.e. showing hard steps instead of a direct line * * @var bool */ private $isDiscrete = false; /** * The tooltips * * @var */ private $tooltips; /** @var array */ private $graphs; /** @var int */ private $order; /** * The default stroke width * @var int */ public $strokeWidth = 5; /** * The size of the displayed dots * * @var int */ public $dotWith = 0; /** * Create a new LineGraph displaying the given dataset * * @param array $dataset An array of [x, y] arrays to display */ public function __construct( array $dataset, array &$graphs, $order, ?array $tooltips = null ) { usort($dataset, array($this, 'sortByX')); $this->dataset = $dataset; $this->graphs = $graphs; $this->tooltips = $tooltips; $ts = []; foreach ($this->tooltips as $value) { $ts[] = $value; } $this->tooltips = $ts; $this->order = $order; } /** * Set datapoints to be emphased via dots * * @param bool $bool True to enable datapoints, otherwise false */ public function setShowDataPoints($bool) { $this->showDataPoints = $bool; } /** * Sort the daset by the xaxis * * @param array $v1 * @param array $v2 * @return int */ private function sortByX(array $v1, array $v2) { if ($v1[0] === $v2[0]) { return 0; } return ($v1[0] < $v2[0]) ? -1 : 1; } /** * Configure this style * * @param array $cfg The configuration as given in the drawLine call */ public function setStyleFromConfig(array $cfg) { $fill = false; foreach ($cfg as $elem => $value) { if ($elem === 'color') { $this->setStrokeColor($value); } elseif ($elem === 'width') { $this->setStrokeWidth($value); } elseif ($elem === 'showPoints') { $this->setShowDataPoints($value); } elseif ($elem === 'fill') { $fill = $value; } elseif ($elem === 'discrete') { $this->isDiscrete = true; } } if ($fill) { $this->setFill($this->strokeColor); $this->setStrokeColor('black'); } } /** * Render this BarChart * * @param RenderContext $ctx The rendering context to use for drawing * * @return DOMElement $dom Element */ public function toSvg(RenderContext $ctx) { $path = new Path($this->dataset); if ($this->isDiscrete) { $path->setDiscrete(true); } $path->setStrokeColor($this->strokeColor); $path->setStrokeWidth($this->strokeWidth); $path->setAttribute('data-icinga-graph-type', 'line'); if ($this->fill !== 'none') { $firstX = $this->dataset[0][0]; $lastX = $this->dataset[count($this->dataset)-1][0]; $path->prepend(array($firstX, 100)) ->append(array($lastX, 100)); $path->setFill($this->fill); } $path->setAdditionalStyle(['clip-path' => 'url(#clip)']); $path->setId($this->id ?? uniqid('line-graph-')); $group = $path->toSvg($ctx); foreach ($this->dataset as $x => $point) { if ($this->showDataPoints === true) { $dot = new Circle($point[0], $point[1], $this->dotWith); $dot->setFill($this->strokeColor); $group->appendChild($dot->toSvg($ctx)); } // Draw invisible circle for tooltip hovering if (isset($this->tooltips[$x])) { $invisible = new Circle($point[0], $point[1], 20); $invisible->setFill($this->strokeColor); $invisible->setAdditionalStyle(['opacity' => '0.0']); $data = array( 'label' => isset($this->graphs[$this->order]['label']) ? strtolower($this->graphs[$this->order]['label']) : '', 'color' => isset($this->graphs[$this->order]['color']) ? strtolower($this->graphs[$this->order]['color']) : '#fff' ); $format = isset($this->graphs[$this->order]['tooltip']) ? $this->graphs[$this->order]['tooltip'] : null; $title = $ctx->getDocument()->createElement('title'); $title->textContent = $this->tooltips[$x]->renderNoHtml($this->order, $data, $format); $invisibleRendered = $invisible->toSvg($ctx); $invisibleRendered->appendChild($title); $group->appendChild($invisibleRendered); } } return $group; } }