PluginProbe
E2Pdf – Export Pdf Tool for WordPress / 1.32.49
E2Pdf – Export Pdf Tool for WordPress v1.32.49
1.32.49 1.32.48 1.32.43 1.32.40 1.32.34 1.32.32 1.32.31 1.32.26 1.32.22 1.32.23 1.32.18 1.32.17 1.32.15 trunk 1.00.00 1.00.13 1.01.01 1.02.02 1.03.07 1.04.07 1.05.03 1.06.02 1.07.11 1.08.00 1.08.06 All 72 releases
e2pdf / vendors / svggraph / LineGraph.php

LineGraph.php in E2Pdf – Export Pdf Tool for WordPress 1.32.49, at vendors/svggraph/LineGraph.php

336 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Copyright (C) 2009-2022 Graham Breach
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 /**
19 * For more information, please contact <graham@goat1000.com>
20 */
21
22 namespace Goat1000\SVGGraph;
23
24 /**
25 * LineGraph - joined line, with axes and grid
26 */
27 class LineGraph extends PointGraph {
28
29 protected $line_styles = [];
30 protected $fill_styles = [];
31
32 /**
33 * Override constructor to handle some odd settings
34 */
35 public function __construct($width, $height, array $settings, array $fixed_settings = [])
36 {
37 $fs = ['require_integer_keys' => false];
38 if(isset($settings['line_figure']) && $settings['line_figure']) {
39
40 // drawing figures means keeping data in order and accepting
41 // repeated keys
42 $fs['sort_keys'] = false;
43 $fs['repeated_keys'] = 'accept';
44 }
45 $fs = array_merge($fs, $fixed_settings);
46 parent::__construct($width, $height, $settings, $fs);
47 }
48
49 protected function draw()
50 {
51 $body = $this->grid() . $this->underShapes();
52 $dataset = $this->getOption(['dataset', 0], 0);
53
54 $bnum = 0;
55 $cmd = 'M';
56 $y_axis_pos = $this->height - $this->pad_bottom -
57 $this->y_axes[$this->main_y_axis]->zero();
58 $y_bottom = min($y_axis_pos, $this->height - $this->pad_bottom);
59
60 $graph_line = '';
61 $line_breaks = $this->getOption(['line_breaks', $dataset]);
62 $points = [];
63 foreach($this->values[$dataset] as $item) {
64 if($line_breaks && $item->value === null && count($points) > 0) {
65 $graph_line .= $this->drawLine($dataset, $points, $y_bottom);
66 $points = [];
67 } else {
68 $x = $this->gridPosition($item, $bnum);
69 if($item->value !== null && $x !== null) {
70 $y = $this->gridY($item->value);
71 $points[] = [$x, $y, $item, $dataset, $bnum];
72 }
73 }
74 ++$bnum;
75 }
76
77 $graph_line .= $this->drawLine($dataset, $points, $y_bottom);
78 $group = [];
79 $this->clipGrid($group);
80 if($this->getOption('semantic_classes'))
81 $group['class'] = 'series';
82 if(!empty($group))
83 $graph_line = $this->element('g', $group, null, $graph_line);
84
85 $group = [];
86 $shadow_id = $this->defs->getShadow();
87 if($shadow_id !== null)
88 $group['filter'] = 'url(#' . $shadow_id . ')';
89 if(!empty($group))
90 $graph_line = $this->element('g', $group, null, $graph_line);
91
92 list($best_fit_above, $best_fit_below) = $this->bestFitLines();
93 $body .= $best_fit_below;
94 $body .= $graph_line;
95 $body .= $this->overShapes();
96 $body .= $this->axes();
97 $body .= $this->drawMarkers();
98 $body .= $best_fit_above;
99 return $body;
100 }
101
102 /**
103 * Line graphs and lines in general require at least two points
104 */
105 protected function checkValues()
106 {
107 parent::checkValues();
108
109 if($this->values->itemsCount() <= 1)
110 throw new \Exception('Not enough values for ' . get_class($this));
111 }
112
113 /**
114 * Returns the SVG fragemnt for a line
115 * $points = array of array($x, $y, $item, $dataset, $index)
116 * use NULL $item for non-data points
117 */
118 public function drawLine($dataset, $points, $y_bottom)
119 {
120 $graph_line = '';
121
122 // can't draw a line between fewer than 2 points
123 if(count($points) > 1) {
124 $figure = $this->getOption('line_figure');
125 $close = $figure && $this->getOption(['line_figure_closed', $dataset]);
126 $fill = $this->getOption(['fill_under', $dataset]);
127 $dash = $this->getOption(['line_dash', $dataset]);
128 $stroke_width = $this->getOption(['line_stroke_width', $dataset]);
129 $attr = ['fill' => 'none'];
130
131 $cg = new ColourGroup($this, null, 0, $dataset);
132 $attr['stroke'] = $cg->stroke();
133
134 if(!empty($dash))
135 $attr['stroke-dasharray'] = $dash;
136 $attr['stroke-width'] = $stroke_width <= 0 ? 1 : $stroke_width;
137 $y_bottom = new Number($fill === 'full' ? $this->height - $this->pad_bottom : $y_bottom);
138
139 $line_points = $this->getLinePoints($points);
140 $curve = min(5, max(0, $this->getOption(['line_curve', $dataset])));
141 if($curve)
142 list($path, $fillpath) = $this->getCurvedLinePath($line_points, $y_bottom, $last_x, $curve);
143 else
144 list($path, $fillpath) = $this->getLinePath($line_points, $y_bottom, $last_x);
145
146 // close the path?
147 if($close)
148 $path->add('z');
149 $this->line_styles[$dataset] = $attr;
150 $attr['d'] = $path;
151 if($this->getOption('semantic_classes'))
152 $attr['class'] = 'series' . $dataset;
153 $graph_line = $this->element('path', $attr);
154
155 if($fill) {
156 $opacity = $this->getOption(['fill_opacity', $dataset]);
157 if(!$figure)
158 $fillpath->add($this->fillTo($last_x, $y_bottom));
159 $fill_style = [
160 'fill' => $this->getColour(null, 0, $dataset),
161 'd' => $fillpath,
162 'stroke' => 'none',
163 ];
164 if($opacity < 1)
165 $fill_style['opacity'] = $opacity;
166 if($this->getOption('semantic_classes'))
167 $fill_style['class'] = 'series' . $dataset;
168 $graph_line = $this->element('path', $fill_style) . $graph_line;
169
170 unset($fill_style['d'], $fill_style['class']);
171 $this->fill_styles[$dataset] = $fill_style;
172 } else {
173 $this->fill_styles[$dataset] = null;
174 }
175 }
176
177 // add markers (and therefore legend entries too)
178 foreach($points as $point) {
179 list($x, $y, $item, $dataset, $index) = $point;
180
181 if($item !== null) {
182 $marker_id = $this->markerLabel($dataset, $index, $item, $x, $y);
183 $extra = empty($marker_id) ? null : ['id' => $marker_id];
184 $this->addMarker($x, $y, $item, $extra, $dataset);
185 }
186 }
187 return $graph_line;
188 }
189
190 /**
191 * Preprocess the points of a joined line
192 */
193 protected function getLinePoints($points)
194 {
195 return $points;
196 }
197
198 /**
199 * Returns the path for a line
200 */
201 protected function getLinePath($points, $y_bottom, &$last_x)
202 {
203 $path = new PathData;
204 $fillpath = new PathData;
205 $cmd = 'M';
206
207 foreach($points as $point) {
208 list($x, $y, $item, $dataset, $index) = $point;
209 $x = new Number($x);
210 $y = new Number($y);
211
212 if($fillpath->isEmpty()) {
213 if($this->getOption('line_figure'))
214 $fillpath->add('M', $x, $y);
215 else
216 $fillpath->add($this->fillFrom($x, $y_bottom));
217 $fillpath->add('L');
218 }
219
220 $path->add($cmd, $x, $y);
221 $fillpath->add($x, $y);
222
223 // no need to repeat same L command
224 $cmd = $cmd == 'M' ? 'L' : '';
225 $last_x = $x;
226 }
227 return [$path, $fillpath];
228 }
229
230 /**
231 * Returns the path for a curved line
232 */
233 protected function getCurvedLinePath($points, $y_bottom, &$last_x, $curve)
234 {
235 $path = new PathData;
236 $fillpath = new PathData;
237
238 $t = $curve / 2.0;
239 $ctrl = [];
240 $last_point = count($points) - 1;
241 foreach($points as $i => $point) {
242 list($x, $y, $item, $dataset, $index) = $point;
243 $nx = new Number($x);
244 $ny = new Number($y);
245
246 $p_prev = $points[$i == 0 ? $i + 1 : $i - 1];
247 $p_next = $points[$i == $last_point ? $i - 1 : $i + 1];
248 $ctrl_prev = $ctrl;
249 $ctrl = $this->getControlPoints($p_prev[0], $p_prev[1], $x, $y, $p_next[0], $p_next[1], $t);
250
251 if($i == 0) {
252 $path->add('M', $nx, $ny);
253 if($this->getOption('line_figure')) {
254 $fillpath->add('M', $nx, $ny);
255 } else {
256 $fillpath->add($this->fillFrom($nx, $y_bottom));
257 $fillpath->add('L', $nx, $ny);
258 }
259 } else {
260 $path->add('C', $ctrl_prev[2], $ctrl_prev[3], $ctrl[0], $ctrl[1], $nx, $ny);
261 $fillpath->add('C', $ctrl_prev[2], $ctrl_prev[3], $ctrl[0], $ctrl[1], $nx, $ny);
262 }
263
264 $last_x = $nx;
265 }
266 return [$path, $fillpath];
267 }
268
269 /**
270 * Returns the control points either side of a point
271 */
272 public static function getControlPoints($x0, $y0, $x1, $y1, $x2, $y2, $t)
273 {
274 $d1 = sqrt(pow($x1 - $x0, 2) + pow($y1 - $y0, 2));
275 $d2 = sqrt(pow($x2 - $x1, 2) + pow($y2 - $y1, 2));
276 $fa = $t * $d1 / ($d1 + $d2);
277 $fb = $t - $fa;
278 $w = $x2 - $x0;
279 $h = $y2 - $y0;
280 return [$x1 - $fa * $w, $y1 - $fa * $h, $x1 + $fb * $w, $y1 + $fb * $h];
281 }
282
283 /**
284 * Returns the path segment to start filling under line
285 */
286 protected function fillFrom(Number $x, Number $y_axis)
287 {
288 return new PathData('M', $x, $y_axis);
289 }
290
291 /**
292 * Returns the path segment to end filling under line
293 */
294 protected function fillTo(Number $x, Number $y_axis)
295 {
296 return new PathData('L', $x, $y_axis, 'z');
297 }
298
299 /**
300 * Override to add the line info and marker at the same time
301 */
302 protected function setLegendEntry($dataset, $index, $item, $style_info)
303 {
304 $style_info['line_style'] = isset($this->line_styles[$dataset]) ?
305 $this->line_styles[$dataset] : null;
306 $style_info['fill_style'] = isset($this->fill_styles[$dataset]) ?
307 $this->fill_styles[$dataset] : null;
308 parent::setLegendEntry($dataset, $index, $item, $style_info);
309 }
310
311 /**
312 * Return line and marker for legend
313 */
314 public function drawLegendEntry($x, $y, $w, $h, $entry)
315 {
316 $marker = parent::drawLegendEntry($x, $y, $w, $h, $entry);
317 $graph_line = '';
318
319 $h1 = $h/2;
320 $y += $h1;
321
322 if(isset($entry->style['line_style'])) {
323 $line = $entry->style['line_style'];
324 $line['d'] = new PathData('M', $x, $y, 'l', $w, 0);
325 $graph_line = $this->element('path', $line);
326 }
327 if(isset($entry->style['fill_style'])) {
328 $fill = $entry->style['fill_style'];
329 $fill['d'] = new PathData('M', $x, $y, 'l', $w, 0, 0, $h1, -$w, 0, 'z');
330 $graph_line = $this->element('path', $fill) . $graph_line;
331 }
332 return $graph_line . $marker;
333 }
334 }
335
336