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

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

208 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Copyright (C) 2020-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 * Class for drawing best-fit lines
26 */
27 class BestFit {
28
29 protected $graph;
30 protected $bbox;
31 protected $lines_below = [];
32 protected $lines_above = [];
33
34 public function __construct(Graph &$graph, BoundingBox $bbox)
35 {
36 $this->graph =& $graph;
37 $this->bbox = $bbox;
38 }
39
40 /**
41 * Adds a line
42 */
43 public function add($dataset, $points)
44 {
45 $type = $this->graph->getOption(['best_fit', $dataset]);
46 if($type !== 'straight' && $type !== 'curve')
47 return;
48
49 // range and projection
50 $r_start = $r_end = $p_start = $p_end = null;
51 list($start, $end) = $this->getRange($dataset);
52
53 if($start !== null || $end !== null) {
54 if($start !== null)
55 $r_start = $this->graph->unitsX($start);
56 if($end !== null)
57 $r_end = $this->graph->unitsX($end);
58 $project = $this->graph->getOption(['best_fit_project', $dataset]);
59 $p_start = $project == 'start' || $project == 'both';
60 $p_end = $project == 'end' || $project == 'both';
61 $project = $p_start || $p_end;
62
63 $points = $this->filterPoints($points, $r_start, $r_end);
64 }
65
66 $class = '\\Goat1000\\SVGGraph\\' . ($type === 'straight' ? 'BestFitLine' : 'BestFitCurve');
67 $subtypes = $this->graph->getOption(['best_fit_type', $dataset]);
68
69 $best_fit = new $class($this->graph, $points, $subtypes);
70 $best_fit->calculate($this->bbox, $r_start, $r_end, $p_start, $p_end);
71 if($this->graph->getOption(['best_fit_above', $dataset]))
72 $this->lines_above[$dataset] = $best_fit;
73 else
74 $this->lines_below[$dataset] = $best_fit;
75 }
76
77 /**
78 * Returns the start and end of selection range
79 */
80 protected function getRange($dataset)
81 {
82 $range = $this->graph->getOption(['best_fit_range', $dataset]);
83 if(!is_array($range))
84 $range = $this->graph->getOption('best_fit_range');
85 if(!is_array($range))
86 return [null, null];
87 if(count($range) !== 2)
88 throw new \Exception('Best fit range must contain start and end values');
89 if($range[0] !== null && !is_numeric($range[0]))
90 throw new \Exception('Best fit range start not numeric or NULL');
91 if($range[1] !== null && !is_numeric($range[1]))
92 throw new \Exception('Best fit range end not numeric or NULL');
93 if($range[0] !== null && $range[1] !== null && $range[1] <= $range[0])
94 throw new \Exception('Best fit range start >= end');
95 return $range;
96 }
97
98 /**
99 * Filters out points outside the range
100 */
101 protected function filterPoints($points, $start, $end)
102 {
103 if($start === null && $end === null)
104 return $points;
105
106 if($start === null)
107 $callback = function($p) use ($end) { return $p->x <= $end; };
108 elseif($end === null)
109 $callback = function($p) use ($start) { return $p->x >= $start; };
110 else
111 $callback = function($p) use ($start, $end) { return $p->x <= $end && $p->x >= $start; };
112
113 return array_filter($points, $callback);
114 }
115
116 /**
117 * Returns the lines that go above the graph
118 */
119 public function getAbove()
120 {
121 return $this->getLines('lines_above');
122 }
123
124 /**
125 * Returns the lines below the graph
126 */
127 public function getBelow()
128 {
129 return $this->getLines('lines_below');
130 }
131
132 /**
133 * Creates the markup for a group of lines
134 */
135 private function getLines($which_lines)
136 {
137 $lines = '';
138 foreach($this->{$which_lines} as $dataset => $best_fit) {
139 $line_path = $best_fit->getLine();
140 if($line_path->isEmpty())
141 continue;
142
143 $proj_path = $best_fit->getProjection();
144 $lines .= $this->getLinePath($dataset, $line_path, $proj_path);
145 }
146 if($lines == '')
147 return $lines;
148
149 if($this->graph->getOption('semantic_classes')) {
150 $cls = ['class' => 'bestfit'];
151 $lines = $this->graph->element('g', $cls, null, $lines);
152 }
153 return $lines;
154 }
155
156 /**
157 * Wraps up the PathData with SVG
158 */
159 protected function getLinePath($dataset, $line_path, $proj_path)
160 {
161 // use ColourGroup to support fill and fillColour
162 $cg = new ColourGroup($this->graph, null, 0, $dataset, 'best_fit_colour');
163 $colour = $cg->stroke();
164 $stroke_width = $this->graph->getOption(['best_fit_width', $dataset]);
165 $dash = $this->graph->getOption(['best_fit_dash', $dataset]);
166 $opacity = $this->graph->getOption(['best_fit_opacity', $dataset]);
167 $above = $this->graph->getOption(['best_fit_above', $dataset]);
168 $type = $this->graph->getOption(['best_fit', $dataset]);
169 $path = [
170 'd' => $line_path,
171 'stroke' => $colour->isNone() ? '#000' : $colour,
172 ];
173 if($stroke_width != 1 && $stroke_width > 0)
174 $path['stroke-width'] = $stroke_width;
175 if(!empty($dash))
176 $path['stroke-dasharray'] = $dash;
177 if($opacity != 1)
178 $path['opacity'] = $opacity;
179 if($type != 'straight')
180 $path['fill'] = 'none'; // don't fill curves
181
182 $line = $this->graph->element('path', $path);
183 if($proj_path->isEmpty())
184 return $line;
185
186 // append the projection path
187 $path['d'] = $proj_path;
188 $cg = new ColourGroup($this->graph, null, 0, $dataset, 'best_fit_project_colour');
189 $colour = $cg->stroke();
190 $stroke_width = $this->graph->getOption(['best_fit_project_width', $dataset]);
191 $dash = $this->graph->getOption(['best_fit_project_dash', $dataset]);
192 $opacity = $this->graph->getOption(['best_fit_project_opacity', $dataset]);
193
194 if(!$colour->isNone())
195 $path['stroke'] = $colour;
196 if($stroke_width > 0)
197 $path['stroke-width'] = $stroke_width;
198 if(!empty($dash))
199 $path['stroke-dasharray'] = $dash;
200 if($opacity > 0)
201 $path['opacity'] = $opacity;
202
203 $line .= $this->graph->element('path', $path);
204 return $line;
205 }
206 }
207
208