PluginProbe
E2Pdf – Export Pdf Tool for WordPress / 1.32.17
E2Pdf – Export Pdf Tool for WordPress v1.32.17
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 / Legend.php

Legend.php in E2Pdf – Export Pdf Tool for WordPress 1.32.17, at vendors/svggraph/Legend.php

444 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Copyright (C) 2016-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 /**
25 * Draws legend
26 */
27 class Legend {
28
29 protected $graph;
30 protected $entry_details = [];
31 protected $autohide;
32 protected $autohide_opacity;
33 protected $back_colour;
34 protected $colour;
35 protected $columns;
36 protected $draggable;
37 protected $entries;
38 protected $entry_height;
39 protected $entry_width;
40 protected $font;
41 protected $font_adjust;
42 protected $font_size;
43 protected $font_weight;
44 protected $position;
45 protected $round;
46 protected $shadow_opacity;
47 protected $show_empty;
48 protected $stroke_colour;
49 protected $stroke_width;
50 protected $text_side;
51 protected $line_spacing;
52 protected $title;
53 protected $title_colour;
54 protected $title_font;
55 protected $title_font_adjust;
56 protected $title_font_size;
57 protected $title_font_weight;
58 protected $title_line_spacing;
59 protected $title_link;
60 protected $type;
61 protected $unique_fields;
62
63 public function __construct(&$graph)
64 {
65 $this->graph =& $graph;
66
67 // copy options to class
68 $opts = ['autohide', 'autohide_opacity', 'back_colour', 'colour', 'columns',
69 'draggable', 'entries', 'entry_height', 'entry_width', 'font',
70 'font_adjust', 'font_weight', 'position', 'round',
71 'shadow_opacity', 'show_empty', 'stroke_colour', 'stroke_width',
72 'text_side', 'title', 'title_link', 'title_font_weight', 'type',
73 'unique_fields'];
74 foreach($opts as $opt) {
75 $this->{$opt} = $graph->getOption('legend_' . $opt);
76 }
77
78 // slightly more complicated options
79 $this->font_size = Number::units($graph->getOption('legend_font_size'));
80 $this->title_colour = $graph->getOption('legend_title_colour',
81 'legend_colour');
82 $this->title_font = $graph->getOption('legend_title_font', 'legend_font');
83 $this->title_font_adjust = $graph->getOption('legend_title_font_adjust',
84 'legend_font_adjust');
85 $this->title_font_size = Number::units($graph->getOption('legend_title_font_size',
86 'legend_font_size'));
87 $this->line_spacing = Number::units($graph->getOption('legend_line_spacing'));
88 if($this->line_spacing === null || $this->line_spacing < 1)
89 $this->line_spacing = $this->font_size;
90 $this->title_line_spacing = Number::units($graph->getOption('legend_title_line_spacing'));
91 if($this->title_line_spacing === null || $this->title_line_spacing < 1)
92 $this->title_line_spacing = $this->title_font_size;
93 }
94
95 /**
96 * Sets the style information for an entry
97 */
98 public function setEntry($dataset, $index, $item, $style_info)
99 {
100 // ignore entry if empty and legend_show_empty not enabled
101 if(!$this->show_empty && !$this->graph->isVisible($item, $dataset))
102 return;
103
104 // find the text first
105 $text = '';
106 $link = null;
107 $entry = count($this->entry_details);
108 $itext = $item->legend_text;
109 if($itext !== null)
110 $text = $itext;
111
112 if($text == '') {
113 // no text from structured data
114 if($this->type == 'none')
115 return;
116
117 if($this->type == 'dataset') {
118 // one entry per dataset
119 $entry = $dataset;
120 if(!isset($this->entry_details[$entry]) &&
121 isset($this->entries[$dataset]))
122 $text = $this->entries[$dataset];
123 } else { // $this->type == 'all'
124 // one entry per data item
125 if(isset($this->entries[$index]))
126 $text = $this->entries[$index];
127 }
128 }
129
130 // split out link
131 if(is_array($text))
132 list($text, $link) = $text;
133
134 if($this->unique_fields) {
135 // prevent adding multiple entries with the same text
136 foreach($this->entry_details as $e) {
137 if($e->text == $text)
138 return;
139 }
140 }
141
142 // if there is no text, don't add the entry
143 if($text != '')
144 $this->entry_details[$entry] = new LegendEntry($item, $text, $link,
145 $style_info);
146 }
147
148 /**
149 * Draws the legend
150 */
151 public function draw()
152 {
153 $entries = $this->getEntries();
154 $entry_count = count($entries);
155 if($entry_count < 1)
156 return '';
157
158 $encoding = $this->graph->encoding;
159
160 // find the largest width / height
161 $font_size = $this->font_size;
162 $max_width = $max_height = 0;
163 $svg_text = new Text($this->graph, $this->font, $this->font_adjust);
164 $baseline = $svg_text->baseline($font_size);
165 foreach($this->entry_details as $entry) {
166 list($w, $h) = $svg_text->measure($entry->text, $font_size, 0, $this->line_spacing);
167 if($w > $max_width)
168 $max_width = $w;
169 if($h > $max_height)
170 $max_height = $h;
171 $entry->width = $w;
172 $entry->height = $h;
173 }
174
175 $title = '';
176 $title_width = $entries_x = 0;
177 $padding_y = $this->graph->getOption('legend_padding_y', 'legend_padding', 1);
178 $padding_x = $this->graph->getOption('legend_padding_x', 'legend_padding', 1);
179 $start_y = $padding_y;
180
181 $w = $this->entry_width;
182 $x = 0;
183 $entry_height = max($max_height, $this->entry_height);
184
185 // make room for title
186 if($this->title != '') {
187 $title_font_size = $this->title_font_size;
188 $svg_text_title = new Text($this->graph, $this->title_font,
189 $this->title_font_adjust);
190 list($tw, $th) = $svg_text_title->measure($this->title, $title_font_size,
191 0, $this->title_line_spacing);
192 $title_width = $tw + $padding_x * 2;
193 $start_y += $th + $this->graph->getOption('legend_title_spacing',
194 'legend_padding', 1);
195 }
196
197 $columns = max(1, min(ceil($this->columns), $entry_count));
198 $per_column = ceil($entry_count / $columns);
199 $columns = ceil($entry_count / $per_column);
200 $column = 0;
201
202 $text = ['x' => 0];
203
204 $column_entry = 0;
205 $y = $start_y;
206 $text_columns = array_fill(0, $columns, '');
207 $entry_columns = array_fill(0, $columns, '');
208 $valid_entries = 0;
209 $spacing = $this->graph->getOption('legend_spacing', 'legend_padding', 1);
210 $entry_space = $this->graph->getOption('legend_entry_spacing',
211 'legend_padding', 1);
212 $col_space = $this->graph->getOption('legend_column_spacing',
213 'legend_padding', 1);
214
215 foreach($entries as $entry) {
216 $y = $start_y + $column_entry * ($entry_height + $spacing);
217
218 // position the graph element
219 $e_y = $y + ($entry_height - $this->entry_height) / 2;
220 $element = $this->graph->drawLegendEntry($x, $e_y, $w,
221 $this->entry_height, $entry);
222 if(!empty($element)) {
223 // position the text element
224 $text['y'] = $y + $baseline + ($entry_height - $entry->height) / 2;
225 $text_element = $svg_text->text($entry->text, $this->line_spacing, $text);
226 if($entry->link !== null)
227 $text_element = $this->graph->getLink($entry, 0, $text_element);
228 $text_columns[$column] .= $text_element;
229 $entry_columns[$column] .= $element;
230
231 ++$valid_entries;
232 if(++$column_entry == $per_column) {
233 $column_entry = 0;
234 ++$column;
235 }
236 }
237 }
238 // if there's nothing to go in the legend, stop now
239 if(!$valid_entries)
240 return '';
241
242 if($this->text_side == 'left') {
243 $text_x_offset = $max_width + $padding_x;
244 $entries_x_offset = $max_width + $padding_x + $entry_space;
245 } else {
246 $text_x_offset = $w + $padding_x + $entry_space;
247 $entries_x_offset = $padding_x;
248 }
249 $longest_width = $padding_x * 2 + ($entry_space * $columns) +
250 $col_space * ($columns - 1) +
251 ($this->entry_width + $max_width) * $columns;
252 $column_width = $col_space + $this->entry_width + $entry_space + $max_width;
253 $width = max($title_width, $longest_width);
254 $height = $start_y + $per_column * ($entry_height + $spacing) - $spacing
255 + $padding_y;
256
257 // centre the entries if the title makes the box bigger
258 if($width > $longest_width) {
259 $offset = ($width - $longest_width) / 2;
260 $entries_x_offset += $offset;
261 $text_x_offset += $offset;
262 }
263
264 $xform = new Transform;
265 $xform->translate($text_x_offset, 0);
266 $text_group = ['transform' => $xform];
267 if($this->text_side == 'left')
268 $text_group['text-anchor'] = 'end';
269 $xform = new Transform;
270 $xform->translate($entries_x_offset, 0);
271 $entries_group = ['transform' => $xform];
272
273 $parts = '';
274 foreach($entry_columns as $col) {
275 $parts .= $this->graph->element('g', $entries_group, null, $col);
276 $entries_x_offset += $column_width;
277 $xform = new Transform;
278 $xform->translate($entries_x_offset, 0);
279 $entries_group['transform'] = $xform;
280 }
281 foreach($text_columns as $col) {
282 $parts .= $this->graph->element('g', $text_group, null, $col);
283 $text_x_offset += $column_width;
284 $xform = new Transform;
285 $xform->translate($text_x_offset, 0);
286 $text_group['transform'] = $xform;
287 }
288
289 // create box and title
290 $box = [
291 'fill' => new Colour($this->graph, $this->back_colour),
292 'width' => $width,
293 'height' => $height,
294 ];
295 if($this->round > 0)
296 $box['rx'] = $box['ry'] = $this->round;
297 if($this->stroke_width) {
298 $box['stroke-width'] = $this->stroke_width;
299 $box['stroke'] = new Colour($this->graph, $this->stroke_colour);
300 }
301 $rect = $this->graph->element('rect', $box);
302 if($this->title != '') {
303 $text['x'] = $width / 2;
304 $text['y'] = $padding_y + $svg_text_title->baseline($title_font_size);
305 $text['text-anchor'] = 'middle';
306 if($this->title_font != $this->font)
307 $text['font-family'] = $this->title_font;
308 if($title_font_size != $font_size)
309 $text['font-size'] = $title_font_size;
310 if($this->title_font_weight != $this->font_weight)
311 $text['font-weight'] = $this->title_font_weight;
312 if($this->title_colour != $this->colour)
313 $text['fill'] = new Colour($this->graph, $this->title_colour);
314 $title = $svg_text_title->text($this->title, $this->title_line_spacing, $text);
315 if(!empty($this->title_link)) {
316 $item = new \stdClass;
317 $item->link = $this->title_link;
318 $title = $this->graph->getLink($item, 0, $title);
319 }
320 }
321
322 // find position for legend
323 if(is_array($this->position)) {
324 list($px, $py) = $this->position;
325 $vx = Coords::parseValue($px);
326 $vy = Coords::parseValue($py);
327 $c = new Coords($this->graph);
328 list($tx, $ty) = $c->transformCoords($px, $py);
329
330 // handle relative positions
331 switch($vx['value']) {
332 case 'c' : $left = $tx - ($width / 2); break;
333 case 'r' : $left = $tx - $width; break;
334 default : $left = $tx;
335 }
336 switch($vy['value']) {
337 case 'c' : $top = $ty - ($height / 2); break;
338 case 'b' : $top = $ty - $height; break;
339 default: $top = $ty;
340 }
341 } else {
342 list($left, $top) = $this->graph->parsePosition($this->position,
343 $width, $height);
344 }
345
346 // create group to contain whole legend
347 $xform = new Transform;
348 $xform->translate($left, $top);
349 $group = [
350 'font-family' => $this->font,
351 'font-size' => $font_size,
352 'fill' => new Colour($this->graph, $this->colour),
353 'transform' => $xform,
354 ];
355 if($this->font_weight != 'normal')
356 $group['font-weight'] = $this->font_weight;
357
358 // add shadow if not completely transparent
359 if($this->shadow_opacity > 0) {
360 $box['x'] = $box['y'] = 2 + ($this->stroke_width / 2);
361 $box['fill'] = '#000';
362 $box['opacity'] = $this->shadow_opacity;
363 unset($box['stroke'], $box['stroke-width']);
364 $rect = $this->graph->element('rect', $box) . $rect;
365 }
366
367 if($this->autohide) {
368 $o0 = $this->autohide_opacity;
369 $o1 = 1;
370 if($o0 < 0)
371 {
372 $o1 = new Number(-$o0);
373 $o0 = 1;
374 $group['opacity'] = $o1;
375 }
376 $this->graph->getJavascript()->autoHide($group, $o0, $o1);
377 }
378 if($this->draggable)
379 $this->graph->getJavascript()->setDraggable($group);
380 return $this->graph->element('g', $group, null, $rect . $title . $parts);
381 }
382
383 /**
384 * Filters the entry list
385 */
386 protected function filterEntries($entries)
387 {
388 $callback = $this->graph->getOption('legend_entries_callback');
389 if(!is_callable($callback))
390 return $entries;
391
392 $filtered = call_user_func($callback, $entries);
393 $valid = false;
394 if(is_array($filtered)) {
395 $valid = true;
396 foreach($filtered as $e) {
397 if(!is_object($e) || get_class($e) !== 'Goat1000\\SVGGraph\\LegendEntry')
398 $valid = false;
399 }
400 }
401
402 if(!$valid)
403 throw new \Exception('Callback did not return array of LegendEntry values.');
404
405 return $filtered;
406 }
407
408 /**
409 * Returns the list of entries in the correct order
410 */
411 protected function getEntries()
412 {
413 $entry_order = $this->graph->getOption('legend_order');
414 if($entry_order === null || $entry_order == 'auto')
415 $entry_order = $this->graph->getLegendOrder();
416
417 if(is_array($entry_order)) {
418 $entries = [];
419 foreach($entry_order as $e) {
420 if(isset($this->entry_details[$e]))
421 $entries[] = $this->entry_details[$e];
422 }
423 return $this->filterEntries($entries);
424 }
425
426 $entries = $this->entry_details;
427 if(!empty($entry_order)) {
428 if(strpos($entry_order, 'sort') !== false) {
429 usort($entries, function($a, $b) {
430 if($a->text == $b->text)
431 return 0;
432 return $a->text > $b->text ? 1 : -1;
433 });
434 }
435
436 if(strpos($entry_order, 'reverse') !== false)
437 $entries = array_reverse($entries, true);
438 }
439
440 return $this->filterEntries($entries);
441 }
442 }
443
444