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 / Guidelines.php

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

455 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Copyright (C) 2017-2023 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 class Guidelines {
25
26 const ABOVE = 1;
27 const BELOW = 0;
28
29 protected $graph;
30 protected $flip_axes;
31 protected $assoc_keys;
32 protected $datetime_keys;
33 protected $guidelines;
34 protected $coords;
35 protected $min_guide = ['x' => null, 'y' => null];
36 protected $max_guide = ['x' => null, 'y' => null];
37
38 private $above;
39 private $colour;
40 private $dash;
41 private $font;
42 private $font_adjust;
43 private $font_size;
44 private $font_weight;
45 private $length;
46 private $length_units;
47 private $opacity;
48 private $stroke_width;
49 private $text_align;
50 private $text_angle;
51 private $text_colour;
52 private $text_opacity;
53 private $text_padding;
54 private $text_position;
55 private $line_spacing;
56
57 public function __construct(&$graph, $flip_axes, $assoc, $datetime)
58 {
59 // see if there is anything to do
60 $lines = $graph->getOption('guideline');
61 if(empty($lines) && $lines !== 0)
62 return;
63
64 $this->graph =& $graph;
65 $this->flip_axes = $flip_axes;
66 $this->assoc_keys = $assoc;
67 $this->datetime_keys = $datetime;
68 $this->guidelines = [];
69
70 // set up options
71 $opts = ['above', 'dash', 'font', 'font_adjust', 'font_weight',
72 'length', 'length_units', 'opacity', 'stroke_width',
73 'text_align', 'text_angle', 'text_padding', 'text_position' ];
74 foreach($opts as $opt)
75 $this->{$opt} = $graph->getOption('guideline_' . $opt);
76
77 // more complicated options
78 $this->colour = new Colour($graph, $graph->getOption('guideline_colour'));
79 $this->text_colour = new Colour($graph,
80 $graph->getOption('guideline_text_colour', 'guideline_colour'));
81 $this->text_opacity = $graph->getOption('guideline_text_opacity',
82 'guideline_opacity');
83 $this->font_size = Number::units($graph->getOption('guideline_font_size'));
84 $this->line_spacing = Number::units($graph->getOption('guideline_line_spacing'));
85
86 $lines = $this->normalize($lines);
87 foreach($lines as $line)
88 $this->calculate($line);
89
90 if(!empty($this->guidelines))
91 $this->coords = new Coords($graph);
92 }
93
94 /**
95 * Simplifies the supported option formats
96 */
97 public static function normalize($lines)
98 {
99 // no lines at all
100 if(empty($lines) && $lines !== 0)
101 return [];
102
103 if(is_array($lines) &&
104 (is_array($lines[0]) || (count($lines) > 1 && !is_string($lines[1])))) {
105
106 // array of guidelines, corrent format
107 return $lines;
108 }
109
110 // single guideline
111 return [$lines];
112 }
113
114 /**
115 * Converts guideline options to more useful member variables
116 */
117 protected function calculate($g)
118 {
119 if(!is_array($g))
120 $g = [$g];
121
122 // $mmvalue is for min/max
123 $value = $mmvalue = $g[0];
124 $axis = (isset($g[2]) && ($g[2] == 'x' || $g[2] == 'y')) ? $g[2] : 'y';
125 if($axis == 'x') {
126 if($this->datetime_keys) {
127 // $value is a datetime string, try to convert it
128 $mmvalue = Graph::dateConvert($value);
129
130 // if the value could not be converted it can't be drawn either
131 if($mmvalue === null)
132 return;
133 } else if($this->assoc_keys) {
134 // $value is a key - must be converted later when the axis
135 // has been created
136 }
137 }
138 $above = isset($g['above']) ? $g['above'] : $this->above;
139 $position = $above ? Guidelines::ABOVE : Guidelines::BELOW;
140 $guideline = [
141 'value' => $value,
142 'depth' => $position,
143 'title' => isset($g[1]) ? $g[1] : '',
144 'axis' => $axis
145 ];
146 $lopts = $topts = [];
147 $line_opts = [
148 'dash' => 'stroke-dasharray',
149 'stroke_width' => 'stroke-width',
150 'opacity' => 'opacity',
151
152 // not SVG attributes
153 'length' => 'length',
154 'length_units' => 'length_units',
155 ];
156 $text_opts = [
157 'opacity' => 'opacity',
158 'font' => 'font-family',
159 'font_weight' => 'font-weight',
160 'text_opacity' => 'opacity', // overrides line opacity
161
162 // these options do not map to SVG attributes
163 'font_adjust' => 'font_adjust',
164 'text_position' => 'text_position',
165 'text_padding' => 'text_padding',
166 'text_angle' => 'text_angle',
167 'text_align' => 'text_align',
168 ];
169
170 // handle colours first
171 if(isset($g['colour'])) {
172 $lopts['stroke'] = new Colour($this->graph, $g['colour']);
173 $topts['fill'] = new Colour($this->graph, $g['colour']);
174 }
175 if(isset($g['text_colour'])) {
176 // text colour overrides line colour
177 $topts['fill'] = new Colour($this->graph, $g['text_colour']);
178 }
179
180 // font size and line spacing
181 if(isset($g['font_size']))
182 $topts['font-size'] = Number::units($g['font_size']);
183 if(isset($g['line_spacing']))
184 $topts['line_spacing'] = Number::units($g['line_spacing']);
185
186 // copy other options to line or text array
187 foreach($line_opts as $okey => $opt)
188 if(isset($g[$okey]))
189 $lopts[$opt] = $g[$okey];
190 foreach($text_opts as $okey => $opt)
191 if(isset($g[$okey]))
192 $topts[$opt] = $g[$okey];
193
194 if(count($lopts))
195 $guideline['line'] = $lopts;
196 if(count($topts))
197 $guideline['text'] = $topts;
198
199 // update maxima and minima
200 if(!isset($g['no_min_max']) || $g['no_min_max'] === false) {
201 if($this->max_guide[$axis] === null || $mmvalue > $this->max_guide[$axis])
202 $this->max_guide[$axis] = $mmvalue;
203 if($this->min_guide[$axis] === null || $mmvalue < $this->min_guide[$axis])
204 $this->min_guide[$axis] = $mmvalue;
205 }
206
207 // can flip the axes now the min/max are stored
208 if($this->flip_axes)
209 $guideline['axis'] = ($guideline['axis'] == 'x' ? 'y' : 'x');
210
211 $this->guidelines[] = $guideline;
212 }
213
214 /**
215 * Returns the minimum and maximum axis guidelines
216 * array($min_x, $min_y, $max_x, $max_y)
217 */
218 public function getMinMax()
219 {
220 $min_max = [
221 $this->min_guide['x'], $this->min_guide['y'],
222 $this->max_guide['x'], $this->max_guide['y']
223 ];
224 return $min_max;
225 }
226
227 /**
228 * Returns the guidelines above content
229 */
230 public function getAbove()
231 {
232 return $this->get(Guidelines::ABOVE);
233 }
234
235 /**
236 * Returns the guidelines below content
237 */
238 public function getBelow()
239 {
240 return $this->get(Guidelines::BELOW);
241 }
242
243 /**
244 * Returns the elements to draw the guidelines
245 */
246 protected function get($depth)
247 {
248 if(empty($this->guidelines))
249 return '';
250
251 // build all the lines at this depth (above/below) that use
252 // global options as one path
253 $d = $lines = $text = '';
254 $path = [
255 'stroke' => $this->colour,
256 'stroke-width' => $this->stroke_width,
257 'stroke-dasharray' => $this->dash,
258 'fill' => 'none'
259 ];
260 if($this->opacity != 1)
261 $path['opacity'] = $this->opacity;
262 $textopts = [
263 'font-family' => $this->font,
264 'font-size' => $this->font_size,
265 'font-weight' => $this->font_weight,
266 'fill' => $this->text_colour,
267 ];
268
269 foreach($this->guidelines as $line) {
270 if($line['depth'] == $depth) {
271 // opacity cannot go in the group because child opacity is multiplied
272 // by group opacity
273 if($this->text_opacity != 1 && !isset($line['text']['opacity']))
274 $line['text']['opacity'] = $this->text_opacity;
275 $this->buildGuideline($line, $lines, $text, $path, $d);
276 }
277 }
278 if(!empty($d)) {
279 $path['d'] = $d;
280 $lines .= $this->graph->element('path', $path);
281 }
282
283 if(!empty($text))
284 $text = $this->graph->element('g', $textopts, null, $text);
285 return $lines . $text;
286 }
287
288 /**
289 * Adds a single guideline and its title to content
290 */
291 protected function buildGuideline(&$line, &$lines, &$text, &$path, &$d)
292 {
293 $length = $this->length;
294 $length_units = $this->length_units;
295 if(isset($line['line'])) {
296 $this->updateAndUnset($length, $line['line'], 'length');
297 $this->updateAndUnset($length_units, $line['line'], 'length_units');
298 }
299
300 $reverse_length = false;
301 $w = $h = 0;
302 if($length != 0) {
303 if($length < 0) {
304 $reverse_length = true;
305 $length = -$length;
306 }
307
308 if($line['axis'] == 'x')
309 $h = $length;
310 else
311 $w = $length;
312
313 } elseif($length_units != 0) {
314 if($length_units < 0) {
315 $reverse_length = true;
316 $length_units = -$length_units;
317 }
318
319 $lnum = new Number($length_units);
320 if($line['axis'] == 'x')
321 $h = 'u' . $lnum;
322 else
323 $w = 'u' . $lnum;
324 }
325
326 // if the graph class has a custom path method, use it
327 // - its signature is the same as GuidelinePath but without $depth
328 $custom_method = ($line['depth'] == Guidelines::ABOVE ?
329 'guidelinePathAbove' : 'guidelinePathBelow');
330
331 if(method_exists($this->graph, $custom_method)) {
332 $path_data = $this->graph->{$custom_method}($line['axis'], $line['value'],
333 $x, $y, $w, $h, $reverse_length);
334 } else {
335 $path_data = $this->guidelinePath($line['axis'], $line['value'],
336 $line['depth'], $x, $y, $w, $h, $reverse_length);
337 }
338 if($path_data == '')
339 return;
340
341 if(!isset($line['line'])) {
342 // no special options, add to main path
343 $d .= $path_data;
344 } else {
345 $line_path = array_merge($path, $line['line'], ['d' => $path_data]);
346 $lines .= $this->graph->element('path', $line_path);
347 }
348 if(!empty($line['title'])) {
349 $text_pos = $this->text_position;
350 $text_pad = $this->text_padding;
351 $text_angle = $this->text_angle;
352 $text_align = $this->text_align;
353 $font = $this->font;
354 $font_size = $this->font_size;
355 $font_adjust = $this->font_adjust;
356 $line_spacing = $this->line_spacing;
357 if(isset($line['text'])) {
358 $this->updateAndUnset($text_pos, $line['text'], 'text_position');
359 $this->updateAndUnset($text_pad, $line['text'], 'text_padding');
360 $this->updateAndUnset($text_angle, $line['text'], 'text_angle');
361 $this->updateAndUnset($text_align, $line['text'], 'text_align');
362 $this->updateAndUnset($font_adjust, $line['text'], 'font_adjust');
363 $this->updateAndUnset($line_spacing, $line['text'], 'line_spacing');
364 if(isset($line['text']['font-family']))
365 $font = $line['text']['font-family'];
366 if(isset($line['text']['font-size']))
367 $font_size = $line['text']['font-size'];
368 }
369 if($line_spacing === null || $line_spacing < 1)
370 $line_spacing = $font_size;
371
372 $svg_text = new Text($this->graph, $font, $font_adjust);
373 list($text_w, $text_h) = $svg_text->measure($line['title'], $font_size,
374 $text_angle, $line_spacing);
375
376 list($x, $y, $text_pos_align) = Graph::relativePosition(
377 $text_pos, $y, $x, $y + $h, $x + $w,
378 $text_w, $text_h, $text_pad, true);
379
380 $t = ['x' => $x, 'y' => $y + $svg_text->baseline($font_size)];
381 if(empty($text_align) && $text_pos_align != 'start') {
382 $t['text-anchor'] = $text_pos_align;
383 } else {
384 $align_map = ['right' => 'end', 'centre' => 'middle'];
385 if(isset($align_map[$text_align]))
386 $t['text-anchor'] = $align_map[$text_align];
387 }
388
389 if($text_angle != 0) {
390 $rx = $x + $text_h/2;
391 $ry = $y + $text_h/2;
392 $xform = new Transform;
393 $xform->rotate($text_angle, $rx, $ry);
394 $t['transform'] = $xform;
395 }
396
397 if(isset($line['text']))
398 $t = array_merge($t, $line['text']);
399 $text .= $svg_text->text($line['title'], $line_spacing, $t);
400 }
401 }
402
403 /**
404 * Creates the path data for a guideline and sets the dimensions
405 */
406 protected function guidelinePath($axis, $value, $depth, &$x, &$y, &$w, &$h,
407 $reverse_length)
408 {
409 // use the Coords class to find measurements
410 $strvalue = (string)(is_numeric($value) ? new Number($value) : $value);
411 if($axis == 'x') {
412 $y = $this->coords->transform('gt', 'y');
413 $x = $this->coords->transform('g' . $strvalue, 'x', null);
414 if($x === null)
415 return new PathData;
416
417 if(is_string($h)) {
418 $h = $this->coords->transform($h, 'y');
419 } elseif($h <= 0) {
420 $h = $this->coords->transform('gh', 'y');
421 }
422 if(!$reverse_length)
423 $y = $this->coords->transform('gb', 'y') - $h;
424 return new PathData('M', $x, $y, 'v', $h);
425 } else {
426 $x = $this->coords->transform('gl', 'x');
427 $y = $this->coords->transform('g' . $strvalue, 'y', null);
428 if($y === null)
429 return new PathData;
430
431 if(is_string($w)) {
432 $w = $this->coords->transform($w, 'x');
433 } elseif($w <= 0) {
434 $w = $this->coords->transform('gw', 'x');
435 }
436 if($reverse_length)
437 $x = $this->coords->transform('gr', 'x') - $w;
438 $h = 0;
439 return new PathData('M', $x, $y, 'h', $w);
440 }
441 }
442
443 /**
444 * Updates $var with $array[$key] and removes it from array
445 */
446 protected function updateAndUnset(&$var, &$array, $key)
447 {
448 if(isset($array[$key])) {
449 $var = $array[$key];
450 unset($array[$key]);
451 }
452 }
453 }
454
455