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

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

344 lines 10.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) 2017-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 StackedBarAndLineGraph extends StackedBarGraph {
25
26 protected $linegraph = null;
27 protected $dataset_types = [];
28 protected $min_values = null;
29 protected $max_values = null;
30
31 /**
32 * We need an instance of the LineGraph class
33 */
34 public function __construct($w, $h, array $settings, array $fixed_settings = [])
35 {
36 $fixed = ['single_axis' => false];
37 $fixed = array_merge($fixed, $fixed_settings);
38 parent::__construct($w, $h, $settings, $fixed);
39
40 // prevent repeated labels
41 unset($settings['label']);
42 $this->linegraph = new MultiLineGraph($w, $h, $settings);
43
44 // validate secondary axis datasets are only lines
45 if(isset($settings['dataset_axis'])) {
46 if(!is_array($settings['dataset_axis'])) {
47 $this->setOption('dataset_axis', null);
48 return;
49 }
50
51 $lines = is_array($settings['line_dataset']) ? $settings['line_dataset'] :
52 [$settings['line_dataset']];
53
54 $line_map = [];
55 foreach($lines as $line)
56 $line_map[$line] = 1;
57 foreach($settings['dataset_axis'] as $dataset => $axis) {
58 if($axis != 0 && !isset($line_map[$dataset])) {
59 throw new \Exception('Bar datasets must use axis 0');
60 }
61 }
62 }
63 }
64
65 /**
66 * Draws the bars and lines
67 */
68 protected function draw()
69 {
70 if($this->getOption('log_axis_y'))
71 throw new \Exception('log_axis_y not supported by StackedBarAndLineGraph');
72 $body = $this->grid() . $this->underShapes();
73
74 // LineGraph has not been initialised, need to copy in details
75 $copy = ['colours', 'links', 'x_axes', 'y_axes', 'main_x_axis',
76 'main_y_axis', 'legend',
77 // needed for best-fit line support
78 'g_width', 'g_height', 'pad_left', 'pad_top'];
79 foreach($copy as $member)
80 $this->linegraph->{$member} = $this->{$member};
81
82 // keep gradients and patterns synced
83 $this->linegraph->defs =& $this->defs;
84
85 // find the lines
86 $lines = $this->getOption('line_dataset');
87 $line_breaks = [];
88 $line_points = [];
89 $points = [];
90 if(!is_array($lines))
91 $lines = [$lines];
92 rsort($lines);
93 foreach($lines as $line) {
94 $line_breaks[$line] = $this->getOption(['line_breaks', $line]);
95 $line_points[$line] = [];
96 $points[$line] = [];
97 }
98 $lines = array_flip($lines);
99
100 $y_axis_pos = $this->height - $this->pad_bottom -
101 $this->y_axes[$this->main_y_axis]->zero();
102 $y_bottom = min($y_axis_pos, $this->height - $this->pad_bottom);
103
104 $this->barSetup();
105 $marker_offset = $this->x_axes[$this->main_x_axis]->unit() / 2;
106 $datasets = $this->multi_graph->getEnabledDatasets();
107
108 // draw bars, store line points
109 $line_dataset = 0;
110 $bars = '';
111 $legend_entries = [];
112 foreach($this->multi_graph as $bnum => $itemlist) {
113 $item = $itemlist[0];
114 $bar_pos = $this->gridPosition($item, $bnum);
115
116 if($bar_pos !== null) {
117
118 $yplus = $yminus = 0;
119 $chunk_values = [];
120 foreach($datasets as $j) {
121 $y_axis = $this->datasetYAxis($j);
122 $item = $itemlist[$j];
123
124 if(array_key_exists($j, $lines)) {
125 $line_dataset = $j;
126 $this->dataset_types[$j] = 'l';
127 if($line_breaks[$line_dataset] && $item->value === null &&
128 count($points[$line_dataset]) > 0) {
129 $line_points[$line_dataset][] = $points[$line_dataset];
130 $points[$line_dataset] = [];
131 } elseif($item->value !== null) {
132 $x = $bar_pos + $marker_offset;
133 $y = $this->gridY($item->value, $y_axis);
134 $points[$line_dataset][] = [$x, $y, $item, $line_dataset, $bnum];
135 $this->setBarVisibility($line_dataset, $item, false, true);
136 }
137 continue;
138 }
139
140 $this->dataset_types[$j] = 'b';
141 if($item->value !== null) {
142 // sort the values from bottom to top, assigning position
143 if($item->value < 0) {
144 array_unshift($chunk_values, [$j, $item, $yminus]);
145 $yminus += $item->value;
146 } else {
147 $chunk_values[] = [$j, $item, $yplus];
148 $yplus += $item->value;
149 }
150 }
151 }
152
153 $bar_count = count($chunk_values);
154 $b = 0;
155 foreach($chunk_values as $chunk) {
156 list($j, $item, $start) = $chunk;
157
158 // store whether the bar can be seen or not
159 $top = (++$b == $bar_count);
160 $this->setBarVisibility($j, $item, $top, $top || $item->value != 0);
161
162 $legend_entries[$j][$bnum] = $item;
163 $bars .= $this->drawBar($item, $bnum, $start, null, $j, ['top' => $top]);
164 }
165
166 $this->barTotals($item, $bnum, $yplus, $yminus, $j);
167 }
168 }
169
170 foreach($legend_entries as $j => $dataset)
171 foreach($dataset as $bnum => $item)
172 $this->setBarLegendEntry($j, $bnum, $item);
173
174 foreach($points as $line_dataset => $line) {
175 if(!empty($line))
176 $line_points[$line_dataset][] = $line;
177 }
178
179 // draw lines clipped to grid
180 $graph_line = '';
181 foreach($line_points as $dataset => $points) {
182 foreach($points as $p) {
183 $graph_line .= $this->linegraph->drawLine($dataset, $p, $y_bottom);
184 }
185 }
186 $group = [];
187 $this->clipGrid($group);
188 list($best_fit_above, $best_fit_below) = $this->linegraph->bestFitLines();
189 $bars .= $best_fit_below;
190 $bars .= $this->element('g', $group, null, $graph_line);
191
192 $group = [];
193 if($this->getOption('semantic_classes'))
194 $group['class'] = 'series';
195 $shadow_id = $this->defs->getShadow();
196 if($shadow_id !== null)
197 $group['filter'] = 'url(#' . $shadow_id . ')';
198 if(!empty($group))
199 $bars = $this->element('g', $group, null, $bars);
200 $body .= $bars;
201
202 $body .= $this->overShapes();
203 $body .= $this->axes();
204
205 // add in the markers created by line graph
206 $body .= $this->linegraph->drawMarkers();
207 $body .= $best_fit_above;
208
209 return $body;
210 }
211
212 /**
213 * Return box or line for legend
214 */
215 public function drawLegendEntry($x, $y, $w, $h, $entry)
216 {
217 if(isset($entry->style['line_style']))
218 return $this->linegraph->drawLegendEntry($x, $y, $w, $h, $entry);
219 return parent::drawLegendEntry($x, $y, $w, $h, $entry);
220 }
221
222 /**
223 * Draws this graph's data labels, and the line graph's data labels
224 */
225 protected function drawDataLabels()
226 {
227 $labels = parent::drawDataLabels();
228 $labels .= $this->linegraph->drawDataLabels();
229 return $labels;
230 }
231
232 /**
233 * Returns the minimum value for an axis
234 */
235 protected function getAxisMinValue($axis)
236 {
237 if($this->min_values === null)
238 $this->calcMinMaxValues();
239 return isset($this->min_values[$axis]) ? $this->min_values[$axis] : null;
240 }
241
242 /**
243 * Returns the maximum value for an axis
244 */
245 protected function getAxisMaxValue($axis)
246 {
247 if($this->max_values === null)
248 $this->calcMinMaxValues();
249 return isset($this->max_values[$axis]) ? $this->max_values[$axis] : null;
250 }
251
252 /**
253 * Finds the minimum and maximum stack or line
254 */
255 private function calcMinMaxValues()
256 {
257 $lines = $this->getOption('line_dataset');
258 if(!is_array($lines))
259 $lines = [$lines];
260 sort($lines);
261 $lines = array_flip($lines);
262
263 $axis_count = $this->yAxisCount();
264 $axis_max = array_fill(0, $axis_count, null);
265 $axis_min = array_fill(0, $axis_count, null);
266 $stack_max = null;
267 $stack_min = null;
268 $datasets = $this->multi_graph->getEnabledDatasets();
269 $bar_datasets = 0;
270 foreach($datasets as $j) {
271 if(!array_key_exists($j, $lines))
272 ++$bar_datasets;
273 }
274 if($bar_datasets === 0)
275 throw new \Exception('No bar datasets enabled');
276
277 foreach($this->multi_graph as $itemlist) {
278
279 $stack_pos = $stack_neg = 0;
280
281 foreach($datasets as $j) {
282 $item = $itemlist[$j];
283 if($item->value === null)
284 continue;
285 if(!is_numeric($item->value))
286 throw new \Exception('Non-numeric value');
287
288 if(array_key_exists($j, $lines)) {
289 // for lines find the global min/max for each axis
290 $axis = $this->datasetYAxis($j);
291 if($axis_min[$axis] === null || $axis_min[$axis] > $item->value)
292 $axis_min[$axis] = $item->value;
293 if($axis_max[$axis] === null || $axis_max[$axis] < $item->value)
294 $axis_max[$axis] = $item->value;
295 } else {
296
297 // for bars need to find min and max stack sizes, using positive
298 // and negative stacks
299 if($item->value < 0)
300 $stack_neg += $item->value;
301 else
302 $stack_pos += $item->value;
303 }
304 }
305
306 if($stack_min === null || $stack_neg < $stack_min)
307 $stack_min = $stack_neg;
308 if($stack_max === null || $stack_neg > $stack_max)
309 $stack_max = $stack_neg;
310
311 if($stack_min === null || $stack_pos < $stack_min)
312 $stack_min = $stack_pos;
313 if($stack_max === null || $stack_pos > $stack_max)
314 $stack_max = $stack_pos;
315 }
316
317 if($axis_min[0] === null || $stack_min < $axis_min[0])
318 $axis_min[0] = $stack_min;
319
320 if($axis_max[0] === null || $stack_max > $axis_max[0])
321 $axis_max[0] = $stack_max;
322
323 $this->min_values = $axis_min;
324 $this->max_values = $axis_max;
325 }
326
327 /**
328 * Returns the order that the datasets should appear in
329 */
330 public function getLegendOrder()
331 {
332 $stack = [];
333 $lines = [];
334 foreach($this->dataset_types as $d => $t)
335 if($t == 'l')
336 $lines[] = $d;
337 else
338 array_unshift($stack, $d);
339
340 return array_merge($stack, $lines);
341 }
342 }
343
344