PluginProbe
E2Pdf – Export Pdf Tool for WordPress / 1.32.32
E2Pdf – Export Pdf Tool for WordPress v1.32.32
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 / PopulationPyramid.php

PopulationPyramid.php in E2Pdf – Export Pdf Tool for WordPress 1.32.32, at vendors/svggraph/PopulationPyramid.php

341 lines 11.2 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 PopulationPyramid extends HorizontalStackedBarGraph {
25
26 protected $neg_datasets = [];
27
28 protected function draw()
29 {
30 if($this->getOption('log_axis_y'))
31 throw new \Exception('log_axis_y not supported by PopulationPyramid');
32
33 $body = $this->grid() . $this->underShapes();
34
35 $bar_height = $this->barWidth();
36 $bar = ['height' => $bar_height];
37
38 $bnum = 0;
39 $bspace = max(0, ($this->y_axes[$this->main_y_axis]->unit() - $bar_height) / 2);
40 $b_start = $this->height - $this->pad_bottom - ($this->getOption('bar_space') / 2);
41 $chunk_count = count($this->multi_graph);
42 $bars_shown = array_fill(0, $chunk_count, 0);
43 $bars = '';
44 $datasets = $this->multi_graph->getEnabledDatasets();
45
46 foreach($this->multi_graph as $itemlist) {
47 $item = $itemlist[0];
48 $k = $item->key;
49 $bar_pos = $this->gridPosition($item, $bnum);
50 if($bar_pos !== null) {
51 $bar['y'] = $bar_pos - $bspace - $bar_height;
52 $xpos = $xneg = 0;
53
54 // find greatest -/+ bar
55 $max_neg_bar = $max_pos_bar = -1;
56 for($j = 0, $enabled = 0; $j < $chunk_count; ++$j) {
57 if(!in_array($j, $datasets))
58 continue;
59
60 $item = $itemlist[$j];
61 $value = $enabled % 2 ? $item->value : -$item->value;
62 if($value > 0)
63 $max_pos_bar = $j;
64 else
65 $max_neg_bar = $j;
66 ++$enabled;
67 }
68 for($j = 0, $enabled = 0; $j < $chunk_count; ++$j) {
69 if(!in_array($j, $datasets))
70 continue;
71
72 $item = $itemlist[$j];
73 if($enabled % 2) {
74 $value = $item->value;
75 } else {
76 $value = -$item->value;
77 $this->neg_datasets[] = $j;
78 }
79 ++$enabled;
80 $bar_style = ['fill' => $this->getColour($item, $bnum, $j)];
81 $this->setStroke($bar_style, $item, $bnum, $j);
82
83 // store whether the bar can be seen or not
84 $this->setBarVisibility($j, $item, false);
85 $this->setBarLegendEntry($j, $bnum, $item);
86
87 $this->barY($value, $bar, $value >= 0 ? $xpos : $xneg);
88 if($value < 0)
89 $xneg += $value;
90 else
91 $xpos += $value;
92
93 if($bar['width'] > 0) {
94 ++$bars_shown[$j];
95
96 $round = max($this->getItemOption('bar_round', $j, $item), 0);
97 if($round > 0) {
98 $bar['rx'] = $bar['ry'] = min($round, $bar['width'] / 2,
99 $bar['height'] / 2);
100 }
101
102 $show_label = $this->addDataLabel($j, $bnum, $bar, $item,
103 $bar['x'], $bar['y'], $bar['width'], $bar['height']);
104 if($this->getOption('show_tooltips'))
105 $this->setTooltip($bar, $item, $j, $item->key, $item->value, $show_label);
106 if($this->getOption('show_context_menu'))
107 $this->setContextMenu($bar, $j, $item, $show_label);
108 if($this->getOption('semantic_classes'))
109 $bar['class'] = 'series' . $j;
110 $rect = $this->element('rect', $bar, $bar_style);
111 $bars .= $this->getLink($item, $k, $rect);
112 unset($bar['id']);
113 }
114 }
115 if($this->getOption('show_bar_totals')) {
116 if($xpos) {
117 $this->barY($xpos, $bar);
118 if(is_callable($this->getOption('bar_total_callback'))) {
119 $bar_total = call_user_func($this->getOption('bar_total_callback'), $item->key,
120 $xpos);
121 } else {
122 $bar_total = new Number($xpos);
123 $bar_total = $bar_total->format();
124 }
125 $this->addContentLabel('totalpos', $bnum, $bar['x'], $bar['y'],
126 $bar['width'], $bar['height'], $bar_total);
127 }
128 if($xneg) {
129 $this->barY($xneg, $bar);
130 if(is_callable($this->getOption('bar_total_callback'))) {
131 $bar_total = call_user_func($this->getOption('bar_total_callback'), $item->key,
132 -$xneg);
133 } else {
134 $bar_total = new Number(-$xneg);
135 $bar_total = $bar_total->format();
136 }
137 $this->addContentLabel('totalneg', $bnum, $bar['x'], $bar['y'],
138 $bar['width'], $bar['height'], $bar_total);
139 }
140 }
141 }
142 ++$bnum;
143 }
144
145 $group = [];
146 if($this->getOption('semantic_classes'))
147 $group['class'] = 'series';
148 $shadow_id = $this->defs->getShadow();
149 if($shadow_id !== null)
150 $group['filter'] = 'url(#' . $shadow_id . ')';
151 if(!empty($group))
152 $bars = $this->element('g', $group, null, $bars);
153 $body .= $bars;
154 $body .= $this->overShapes();
155 $body .= $this->axes();
156 return $body;
157 }
158
159 /**
160 * Overridden to prevent drawing on other bars
161 */
162 public function dataLabelPosition($dataset, $index, &$item, $x, $y, $w, $h,
163 $label_w, $label_h)
164 {
165 if(in_array($dataset, $this->neg_datasets, true)) {
166 // pass in an item with negative value for positions on left
167 $ineg = $item;
168 $ineg->value = -$item->value;
169 return parent::dataLabelPosition($dataset, $index, $ineg, $x, $y, $w, $h,
170 $label_w, $label_h);
171 } else {
172 return parent::dataLabelPosition($dataset, $index, $item, $x, $y, $w, $h,
173 $label_w, $label_h);
174 }
175 }
176
177 /**
178 * Returns the maximum (stacked) value
179 */
180 public function getMaxValue()
181 {
182 $sums = [ [], [] ];
183 $datasets = $this->multi_graph->getEnabledDatasets();
184 if(count($datasets) < 2)
185 return $this->multi_graph->getMaxValue();
186
187 $i = 0;
188 foreach($datasets as $d) {
189 $dir = $i % 2;
190 foreach($this->values[$d] as $item) {
191 if($item->value === null)
192 continue;
193 if(isset($sums[$dir][$item->key]))
194 $sums[$dir][$item->key] += $item->value;
195 else
196 $sums[$dir][$item->key] = $item->value;
197 }
198 ++$i;
199 }
200 if(!count($sums[0]))
201 return null;
202 return max(max($sums[0]), max($sums[1]));
203 }
204
205 /**
206 * Returns the minimum (stacked) value
207 */
208 public function getMinValue()
209 {
210 $sums = [ [], [] ];
211 $datasets = $this->multi_graph->getEnabledDatasets();
212 if(count($datasets) < 2)
213 return $this->multi_graph->getMinValue();
214
215 $i = 0;
216 foreach($datasets as $d) {
217 $dir = $i % 2;
218 foreach($this->values[$d] as $item) {
219 if($item->value === null)
220 continue;
221 if(!is_numeric($item->value))
222 throw new \Exception('Non-numeric value');
223 if(isset($sums[$dir][$item->key]))
224 $sums[$dir][$item->key] += $item->value;
225 else
226 $sums[$dir][$item->key] = $item->value;
227 }
228 ++$i;
229 }
230 if(!count($sums[0]))
231 return null;
232 return min(min($sums[0]), min($sums[1]));
233 }
234
235 /**
236 * Returns the X and Y axis class instances as a list
237 */
238 protected function getAxes($ends, &$x_len, &$y_len)
239 {
240 // always assoc, no units
241 $this->setOption('units_x', null);
242 $this->setOption('units_before_x', null);
243
244 // if fixed grid spacing is specified, make the min spacing 1 pixel
245 if(is_numeric($this->getOption('grid_division_v')))
246 $this->setOption('minimum_grid_spacing_v', 1);
247 if(is_numeric($this->getOption('grid_division_h')))
248 $this->setOption('minimum_grid_spacing_h', 1);
249
250 $max_h = $ends['v_max'][0];
251 $min_h = $ends['v_min'][0];
252 $max_v = $ends['k_max'][0];
253 $min_v = $ends['k_min'][0];
254 $x_min_unit = $this->getOption(['minimum_units_y', 0]);
255 $x_fit = false;
256 $y_min_unit = 1;
257 $y_fit = true;
258 $x_units_after = (string)$this->getOption(['units_y', 0]);
259 $y_units_after = '';
260 $x_units_before = (string)$this->getOption(['units_before_y', 0]);
261 $y_units_before = '';
262 $x_decimal_digits = $this->getOption(['decimal_digits_y', 0],
263 'decimal_digits');
264 $y_decimal_digits = $this->getOption(['decimal_digits_x', 0],
265 'decimal_digits');
266 $x_text_callback = $this->getOption(['axis_text_callback_x', 0],
267 'axis_text_callback');
268 $y_text_callback = $this->getOption(['axis_text_callback_y', 0],
269 'axis_text_callback');
270
271 $grid_division_h = $this->getOption(['grid_division_h', 0]);
272 $grid_division_v = $this->getOption(['grid_division_v', 0]);
273
274 // sanitise grid divisions
275 if(is_numeric($grid_division_v) && $grid_division_v <= 0)
276 $grid_division_v = null;
277 if(is_numeric($grid_division_h) && $grid_division_h <= 0)
278 $grid_division_h = null;
279 $this->setOption('grid_division_v', $grid_division_v);
280 $this->setOption('grid_division_h', $grid_division_h);
281
282 if(!is_numeric($max_h) || !is_numeric($min_h) ||
283 !is_numeric($max_v) || !is_numeric($min_v))
284 throw new \Exception('Non-numeric min/max');
285
286 if($min_h == $max_h) {
287 if($x_min_unit > 0) {
288 $inc = $x_min_unit;
289 } else {
290 $fallback = $this->getOption('axis_fallback_max');
291 $inc = $fallback > 0 ? $fallback : 1;
292 }
293 $max_h += $inc;
294 }
295
296 if(!is_numeric($grid_division_h)) {
297 $x_min_space = $this->getOption(['minimum_grid_spacing_h', 0],
298 'minimum_grid_spacing');
299 $x_axis = new AxisDoubleEnded($x_len, $max_h, $min_h, $x_min_unit,
300 $x_min_space, $x_fit, $x_units_before, $x_units_after,
301 $x_decimal_digits, $x_text_callback);
302 } else {
303 $x_axis = new AxisFixedDoubleEnded($x_len, $max_h, $min_h,
304 $grid_division_h, $x_units_before, $x_units_after,
305 $x_decimal_digits, $x_text_callback);
306 }
307
308 $min_space = $this->getOption(['minimum_grid_spacing_v', 0],
309 'minimum_grid_spacing');
310 $grid_division = $this->getOption(['grid_division_v', 0]);
311 if(is_numeric($grid_division)) {
312 if($grid_division <= 0)
313 throw new \Exception('Invalid grid division');
314 $this->setOption('minimum_grid_spacing_v', 1);
315 $min_space = 1;
316 }
317
318 $levels = $this->getOption(['axis_levels_h', 0]);
319 $ticks = $this->getOption('axis_ticks_x');
320
321 $y_axis_factory = new AxisFactory($this->getOption('datetime_keys'), $this->settings,
322 true, true, true);
323 $y_axis = $y_axis_factory->get($y_len, $min_v, $max_v, $y_min_unit,
324 $min_space, $grid_division, $y_units_before, $y_units_after,
325 $y_decimal_digits, $y_text_callback, $this->values, false, 0, $levels,
326 $ticks);
327
328 return [ [$x_axis], [$y_axis] ];
329 }
330
331 /**
332 * Override the function to pass in the class to use
333 */
334 protected function calcAverages($cls = 'Goat1000\SVGGraph\PopulationPyramidAverage')
335 {
336 return parent::calcAverages('Goat1000\SVGGraph\PopulationPyramidAverage');
337 }
338
339 }
340
341