PluginProbe
E2Pdf – Export Pdf Tool for WordPress / 1.32.23
E2Pdf – Export Pdf Tool for WordPress v1.32.23
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 / BoxAndWhiskerGraph.php

BoxAndWhiskerGraph.php in E2Pdf – Export Pdf Tool for WordPress 1.32.23, at vendors/svggraph/BoxAndWhiskerGraph.php

284 lines 8.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) 2013-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 class BoxAndWhiskerGraph extends PointGraph {
25
26 private $min_value = null;
27 private $max_value = null;
28
29 public function __construct($w, $h, array $settings, array $fixed_settings = [])
30 {
31 $fs = [
32 'label_centre' => !isset($settings['datetime_keys']),
33 'require_structured' => ['top', 'bottom', 'wtop', 'wbottom'],
34 ];
35 $fs = array_merge($fs, $fixed_settings);
36 parent::__construct($w, $h, $settings, $fs);
37 }
38
39 protected function draw()
40 {
41 $body = $this->grid() . $this->underShapes();
42
43 $bar_width = $this->barWidth();
44 $dataset = $this->getOption(['dataset', 0], 0);
45 $x_axis = $this->x_axes[$this->main_x_axis];
46
47 $bspace = max(0, ($this->x_axes[$this->main_x_axis]->unit() - $bar_width) / 2);
48 $whisker_width = $this->getOption('whisker_width');
49 $whisker_dash = $this->getOption('whisker_dash');
50 $median_width = $this->getOption('median_stroke_width');
51 $median_dash = $this->getOption('median_dash');
52 $median_colour = $this->getOption('median_colour');
53 $bnum = 0;
54 $series = '';
55 foreach($this->values[$dataset] as $item) {
56 $bar_pos = $this->gridPosition($item, $bnum);
57
58 if($item->value !== null && $bar_pos !== null) {
59
60 $box_style = ['fill' => $this->getColour($item, $bnum, $dataset)];
61 $this->setStroke($box_style, $item, $bnum, $dataset);
62 $this->setLegendEntry($dataset, $bnum, $item, $box_style);
63
64 $top = $item->top;
65 $bottom = $item->bottom;
66 $round = max($this->getItemOption('bar_round', $dataset, $item), 0);
67 $m_colour = null;
68 if(!empty($median_colour)) {
69 $cg = new ColourGroup($this, $item, $bnum, $dataset, 'median_colour');
70 $m_colour = $cg->stroke();
71 }
72 $shape = $this->whiskerBox($bspace + $bar_pos, $bar_width, $item->value,
73 $top, $bottom, $item->wtop, $item->wbottom, $round, $whisker_width,
74 $whisker_dash, $median_width, $median_dash, $m_colour);
75
76 // wrap the whisker box in a group
77 $g = [];
78 $show_label = $this->addDataLabel($dataset, $bnum, $g, $item,
79 $bspace + $bar_pos, $this->gridY($top), $bar_width,
80 $this->gridY($bottom) - $this->gridY($top));
81 if($this->getOption('show_tooltips'))
82 $this->setTooltip($g, $item, $dataset, $item->key, $item->value, $show_label);
83 if($this->getOption('show_context_menu'))
84 $this->setContextMenu($g, $dataset, $item, $show_label);
85
86 if($this->getOption('semantic_classes'))
87 $g['class'] = 'series0';
88 $group = $this->element('g', array_merge($g, $box_style), null, $shape);
89 $series .= $this->getLink($item, $item->key, $group);
90
91 // add outliers as markers
92 $x = $bar_pos + $x_axis->unit() / 2;
93 foreach($this->getOutliers($item) as $ovalue) {
94 $y = $this->gridY($ovalue);
95 $this->addMarker($x, $y, $item);
96 }
97 }
98 ++$bnum;
99 }
100
101 $group = [];
102 if($this->getOption('semantic_classes'))
103 $group['class'] = 'series';
104 $shadow_id = $this->defs->getShadow();
105 if($shadow_id !== null)
106 $group['filter'] = 'url(#' . $shadow_id . ')';
107 if(!empty($group))
108 $series = $this->element('g', $group, null, $series);
109
110 $body .= $series;
111 $body .= $this->overShapes();
112 $body .= $this->axes();
113 $body .= $this->drawMarkers();
114 return $body;
115 }
116
117 /**
118 * Returns the width of a bar
119 */
120 protected function barWidth()
121 {
122 $bw = $this->getOption('bar_width');
123 if(is_numeric($bw) && $bw >= 1)
124 return $bw;
125 $unit_w = $this->x_axes[$this->main_x_axis]->unit();
126 $bw = $unit_w - $this->getOption('bar_space');
127 return max(1, $bw, $this->getOption('bar_width_min'));
128 }
129
130 /**
131 * Returns the code for a box with whiskers
132 */
133 protected function whiskerBox($x, $w, $median, $top, $bottom,
134 $wtop, $wbottom, $round, $whisker_width, $whisker_dash,
135 $median_width, $median_dash, Colour $median_colour = null)
136 {
137 $t = $this->gridY($top);
138 $b = $this->gridY($bottom);
139 $wt = $this->gridY($wtop);
140 $wb = $this->gridY($wbottom);
141
142 $box = ['x' => $x, 'y' => $t, 'width' => $w, 'height' => $b - $t];
143 if($round > 0) {
144 $box['rx'] = $box['ry'] = min($round, $box['width'] / 2,
145 $box['height'] / 2);
146 }
147 $rect = $this->element('rect', $box);
148
149 // whisker lines
150 $lg = $w * (1 - $whisker_width) * 0.5;
151 $ll = $x + $lg;
152 $lr = $x + $w - $lg;
153 $l = ['x1' => $ll, 'x2' => $lr];
154 $l['y1'] = $l['y2'] = $wt;
155 $l1 = $this->element('line', $l);
156 $l['y1'] = $l['y2'] = $wb;
157 $l2 = $this->element('line', $l);
158
159 // median line
160 $l['x1'] = $x;
161 $l['x2'] = $x + $w;
162 $l['y1'] = $l['y2'] = $this->gridY($median);
163 $style = [ 'stroke-width' => $median_width ];
164 if($median_colour !== null)
165 $style['stroke'] = $median_colour;
166 if(!empty($median_dash))
167 $style['stroke-dasharray'] = $median_dash;
168 $l3 = $this->element('line', array_merge($l, $style));
169
170 // whisker dashed lines
171 $style = [ 'stroke-dasharray' => $whisker_dash ];
172 $l['x1'] = $l['x2'] = $x + $w / 2;
173 $l['y1'] = $wt;
174 $l['y2'] = $t;
175 $w1 = $this->element('line', array_merge($l, $style));
176 $l['y1'] = $wb;
177 $l['y2'] = $b;
178 $w2 = $this->element('line', array_merge($l, $style));
179
180 return $rect . $w1 . $w2 . $l1 . $l2 . $l3;
181 }
182
183 /**
184 * Checks that the data contains sensible values
185 */
186 protected function checkValues()
187 {
188 parent::checkValues();
189
190 foreach($this->values[0] as $item) {
191 $val = $item->value;
192 if($val === null)
193 continue;
194 $wb = $item->wbottom;
195 $wt = $item->wtop;
196 $b = $item->bottom;
197 $t = $item->top;
198 if($wb > $b || $wt < $t || $val < $b || $val > $t) {
199
200 $wb = new Number($wb);
201 $b = new Number($b);
202 $wt = new Number($wt);
203 $t = new Number($t);
204 $val = new Number($val);
205 throw new \Exception('Data problem: ' . $wb . '--[' . $b . ' ' . $val .
206 ' ' . $t . ']--' . $wt);
207 }
208 }
209 }
210
211 /**
212 * Return box for legend
213 */
214 public function drawLegendEntry($x, $y, $w, $h, $entry)
215 {
216 $box = ['x' => $x, 'y' => $y, 'width' => $w, 'height' => $h];
217 return $this->element('rect', $box, $entry->style);
218 }
219
220 /**
221 * Returns the maximum bar end
222 */
223 public function getMaxValue()
224 {
225 if($this->max_value !== null)
226 return $this->max_value;
227 $max = null;
228 $dataset = $this->getOption(['dataset', 0], 0);
229 foreach($this->values[$dataset] as $item) {
230 if($item->value === null)
231 continue;
232 $points = [$item->wtop];
233 $points = array_merge($points, $this->getOutliers($item));
234 $m = max($points);
235 if($max === null || $m > $max)
236 $max = $m;
237 }
238 return ($this->max_value = $max);
239 }
240
241 /**
242 * Returns the minimum bar end
243 */
244 public function getMinValue()
245 {
246 if($this->min_value !== null)
247 return $this->min_value;
248 $min = null;
249 $dataset = $this->getOption(['dataset', 0], 0);
250 foreach($this->values[$dataset] as $item) {
251 if($item->value === null)
252 continue;
253 $points = [$item->wbottom];
254 $points = array_merge($points, $this->getOutliers($item));
255 $m = min($points);
256 if($min === null || $m < $min)
257 $min = $m;
258 }
259 return ($this->min_value = $min);
260 }
261
262 /**
263 * Returns the list of outliers for an item
264 */
265 protected function getOutliers(&$item)
266 {
267 $outliers = [];
268 $structure = $this->getOption('structure');
269 if(!isset($structure['outliers']) ||
270 !is_array($structure['outliers']))
271 return $outliers;
272
273 $min = $item->wbottom;
274 $max = $item->wtop;
275 foreach($structure['outliers'] as $o) {
276 $v = $item->rawData($o);
277 if($v !== null && ($v > $max || $v < $min))
278 $outliers[] = $v;
279 }
280 return $outliers;
281 }
282 }
283
284