summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Chart/Primitive/Path.php
blob: b9d5f7b46aa36d8558568c6fda6cbf95d313d769 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
<?php
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */

namespace Icinga\Chart\Primitive;

use DOMDocument;
use DOMElement;
use Icinga\Chart\Render\RenderContext;
use Icinga\Chart\Format;

/**
 * Drawable for creating a svg path element
 */
class Path extends Styleable implements Drawable
{
    /**
     * Syntax template for moving
     *
     * @see http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
     */
    const TPL_MOVE = 'M %s %s ';

    /**
     * Syntax template for bezier curve
     *
     * @see http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
     */
    const TPL_BEZIER = 'S %s %s ';

    /**
     * Syntax template for straight lines
     *
     * @see http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
     */
    const TPL_STRAIGHT = 'L %s %s ';

    /**
     * The default stroke width
     *
     * @var int
     */
    public $strokeWidth = 1;

    /**
     * True to treat coordinates as absolute values
     *
     * @var bool
     */
    protected $isAbsolute = false;

    /**
     * The points to draw, in the order they are drawn
     *
     * @var array
     */
    protected $points = array();

    /**
     * True to draw the path discrete, i.e. make hard steps between points
     *
     * @var bool
     */
    protected $discrete = false;

    /**
     * Create the path using the given points
     *
     * @param array $points Either a single [x, y] point or an array of x, y points
     */
    public function __construct(array $points)
    {
        $this->append($points);
    }

    /**
     * Append a single point or an array of points to this path
     *
     * @param   array $points Either a single [x, y] point or an array of x, y points
     *
     * @return  $this          Fluid interface
     */
    public function append(array $points)
    {
        if (count($points) === 0) {
            return $this;
        }
        if (!is_array($points[0])) {
            $points = array($points);
        }
        $this->points = array_merge($this->points, $points);
        return $this;
    }

    /**
     * Prepend a single point or an array of points to this path
     *
     * @param   array $points Either a single [x, y] point or an array of x, y points
     *
     * @return  $this          Fluid interface
     */
    public function prepend(array $points)
    {
        if (count($points) === 0) {
            return $this;
        }
        if (!is_array($points[0])) {
            $points = array($points);
        }
        $this->points = array_merge($points, $this->points);
        return $this;
    }

    /**
     * Set this path to be discrete
     *
     * @param   boolean $bool True to draw discrete or false to draw straight lines between points
     *
     * @return  $this          Fluid interface
     */
    public function setDiscrete($bool)
    {
        $this->discrete = $bool;
        return $this;
    }

    /**
     * Mark this path as containing absolute coordinates
     *
     * @return $this Fluid interface
     */
    public function toAbsolute()
    {
        $this->isAbsolute = true;
        return $this;
    }

    /**
     * Create the SVG representation from this Drawable
     *
     * @param   RenderContext $ctx The context to use for rendering
     * @return  DOMElement         The SVG Element
     */
    public function toSvg(RenderContext $ctx)
    {
        $doc = $ctx->getDocument();
        $group = $doc->createElement('g');

        $pathDescription = '';
        $tpl = self::TPL_MOVE;
        $lastPoint = null;
        foreach ($this->points as $point) {
            if (!$this->isAbsolute) {
                $point = $ctx->toAbsolute($point[0], $point[1]);
            }
            $point[0] = Format::formatSVGNumber($point[0]);
            $point[1] = Format::formatSVGNumber($point[1]);
            if ($lastPoint && $this->discrete) {
                $pathDescription .= sprintf($tpl, $point[0], $lastPoint[1]);
            }
            $pathDescription .= vsprintf($tpl, $point);
            $lastPoint = $point;
            $tpl = self::TPL_STRAIGHT;
        }

        $path = $doc->createElement('path');

        $id = $this->id ?? uniqid('path-');
        $path->setAttribute('id', $id);
        $this->setId($id);

        $path->setAttribute('d', $pathDescription);

        $this->applyAttributes($path);
        $style = new DOMDocument();
        $style->loadHTML($this->getStyle());

        $path->appendChild(
            $path->ownerDocument->importNode(
                $style->getElementsByTagName('style')->item(0),
                true
            )
        );

        $group->appendChild($path);
        return $group;
    }
}