1 | <?php |
||
2 | |||
3 | /** |
||
4 | * JPGraph v4.0.3 |
||
5 | */ |
||
6 | |||
7 | namespace Amenadiel\JpGraph\Plot; |
||
8 | |||
9 | use Amenadiel\JpGraph\Util; |
||
10 | |||
11 | /* |
||
12 | * File: JPGRAPH_LINE.PHP |
||
13 | * // Description: Line plot extension for JpGraph |
||
14 | * // Created: 2001-01-08 |
||
15 | * // Ver: $Id: jpgraph_line.php 1921 2009-12-11 11:46:39Z ljp $ |
||
16 | * // |
||
17 | * // Copyright (c) Asial Corporation. All rights reserved. |
||
18 | */ |
||
19 | // constants for the (filled) area |
||
20 | 1 | define('LP_AREA_FILLED', true); |
|
21 | 1 | define('LP_AREA_NOT_FILLED', false); |
|
22 | 1 | define('LP_AREA_BORDER', false); |
|
23 | 1 | define('LP_AREA_NO_BORDER', true); |
|
24 | |||
25 | /** |
||
26 | * @class LinePlot |
||
27 | * // Description: |
||
28 | */ |
||
29 | class LinePlot extends Plot |
||
30 | { |
||
31 | public $mark; |
||
32 | protected $filled = false; |
||
33 | protected $fill_color = 'blue'; |
||
34 | protected $step_style = false; |
||
35 | protected $center = false; |
||
36 | protected $line_style = 1; // Default to solid |
||
37 | protected $filledAreas = []; // array of arrays(with min,max,col,filled in them) |
||
38 | public $barcenter = false; // When we mix line and bar. Should we center the line in the bar. |
||
39 | protected $fillFromMin = false; |
||
40 | protected $fillFromMax = false; |
||
41 | protected $fillgrad = false; |
||
42 | protected $fillgrad_fromcolor = 'navy'; |
||
43 | protected $fillgrad_tocolor = 'silver'; |
||
44 | protected $fillgrad_numcolors = 100; |
||
45 | protected $iFastStroke = false; |
||
46 | |||
47 | /** |
||
48 | * CONSTRUCTOR. |
||
49 | * |
||
50 | * @param mixed $datay |
||
51 | * @param mixed $datax |
||
52 | */ |
||
53 | 16 | public function __construct($datay, $datax = false) |
|
54 | { |
||
55 | 16 | parent::__construct($datay, $datax); |
|
56 | 16 | $this->mark = new PlotMark(); |
|
57 | 16 | $this->color = Util\ColorFactory::getColor(); |
|
58 | 16 | $this->fill_color = $this->color; |
|
59 | 16 | } |
|
60 | |||
61 | /** |
||
62 | * PUBLIC METHODS. |
||
63 | * |
||
64 | * @param mixed $aFlg |
||
65 | */ |
||
66 | public function SetFilled($aFlg = true) |
||
67 | { |
||
68 | $this->filled = $aFlg; |
||
69 | } |
||
70 | |||
71 | public function SetBarCenter($aFlag = true) |
||
72 | { |
||
73 | $this->barcenter = $aFlag; |
||
74 | } |
||
75 | |||
76 | 2 | public function SetStyle($aStyle) |
|
77 | { |
||
78 | 2 | $this->line_style = $aStyle; |
|
79 | 2 | } |
|
80 | |||
81 | 1 | public function SetStepStyle($aFlag = true) |
|
82 | { |
||
83 | 1 | $this->step_style = $aFlag; |
|
84 | 1 | } |
|
85 | |||
86 | 15 | public function SetColor($aColor) |
|
87 | { |
||
88 | 15 | parent::SetColor($aColor); |
|
89 | 15 | } |
|
90 | |||
91 | 2 | public function SetFillFromYMin($f = true) |
|
92 | { |
||
93 | 2 | $this->fillFromMin = $f; |
|
94 | 2 | } |
|
95 | |||
96 | public function SetFillFromYMax($f = true) |
||
97 | { |
||
98 | $this->fillFromMax = $f; |
||
99 | } |
||
100 | |||
101 | 5 | public function SetFillColor($aColor, $aFilled = true) |
|
102 | { |
||
103 | //$this->color = $aColor; |
||
104 | 5 | $this->fill_color = $aColor; |
|
105 | 5 | $this->filled = $aFilled; |
|
106 | 5 | } |
|
107 | |||
108 | public function SetFillGradient($aFromColor, $aToColor, $aNumColors = 100, $aFilled = true) |
||
109 | { |
||
110 | $this->fillgrad_fromcolor = $aFromColor; |
||
111 | $this->fillgrad_tocolor = $aToColor; |
||
112 | $this->fillgrad_numcolors = $aNumColors; |
||
113 | $this->filled = $aFilled; |
||
114 | $this->fillgrad = true; |
||
115 | } |
||
116 | |||
117 | 16 | public function Legend($graph) |
|
118 | { |
||
119 | 16 | if ($this->legend != '') { |
|
120 | 11 | if ($this->filled && !$this->fillgrad) { |
|
121 | 2 | $graph->legend->Add( |
|
122 | 2 | $this->legend, |
|
123 | 2 | $this->fill_color, |
|
124 | 2 | $this->mark, |
|
125 | 2 | 0, |
|
126 | 2 | $this->legendcsimtarget, |
|
127 | 2 | $this->legendcsimalt, |
|
128 | 2 | $this->legendcsimwintarget |
|
129 | ); |
||
130 | 11 | } elseif ($this->fillgrad) { |
|
131 | $color = [$this->fillgrad_fromcolor, $this->fillgrad_tocolor]; |
||
132 | // In order to differentiate between gradients and cooors specified as an Image\RGB triple |
||
133 | $graph->legend->Add( |
||
134 | $this->legend, |
||
135 | $color, |
||
136 | '', |
||
137 | -2/* -GRAD_HOR */, |
||
138 | $this->legendcsimtarget, |
||
139 | $this->legendcsimalt, |
||
140 | $this->legendcsimwintarget |
||
141 | ); |
||
142 | } else { |
||
143 | 11 | $graph->legend->Add( |
|
144 | 11 | $this->legend, |
|
145 | 11 | $this->color, |
|
146 | 11 | $this->mark, |
|
147 | 11 | $this->line_style, |
|
148 | 11 | $this->legendcsimtarget, |
|
149 | 11 | $this->legendcsimalt, |
|
150 | 11 | $this->legendcsimwintarget |
|
151 | ); |
||
152 | } |
||
153 | } |
||
154 | 16 | } |
|
155 | |||
156 | 1 | public function AddArea($aMin = 0, $aMax = 0, $aFilled = LP_AREA_NOT_FILLED, $aColor = 'gray9', $aBorder = LP_AREA_BORDER) |
|
157 | { |
||
158 | 1 | if ($aMin > $aMax) { |
|
159 | // swap |
||
160 | $tmp = $aMin; |
||
161 | $aMin = $aMax; |
||
162 | $aMax = $tmp; |
||
163 | } |
||
164 | 1 | $this->filledAreas[] = [$aMin, $aMax, $aColor, $aFilled, $aBorder]; |
|
165 | 1 | } |
|
166 | |||
167 | // Gets called before any axis are stroked |
||
168 | 16 | public function PreStrokeAdjust($graph) |
|
169 | { |
||
170 | // If another plot type have already adjusted the |
||
171 | // offset we don't touch it. |
||
172 | // (We check for empty in case the scale is a log scale |
||
173 | // and hence doesn't contain any xlabel_offset) |
||
174 | 16 | if (empty($graph->xaxis->scale->ticks->xlabel_offset) || $graph->xaxis->scale->ticks->xlabel_offset == 0) { |
|
175 | 15 | if ($this->center) { |
|
176 | 4 | ++$this->numpoints; |
|
177 | 4 | $a = 0.5; |
|
178 | 4 | $b = 0.5; |
|
179 | } else { |
||
180 | 12 | $a = 0; |
|
181 | 12 | $b = 0; |
|
182 | } |
||
183 | 15 | $graph->xaxis->scale->ticks->SetXLabelOffset($a); |
|
184 | 15 | $graph->SetTextScaleOff($b); |
|
185 | //$graph->xaxis->scale->ticks->SupressMinorTickMarks(); |
||
186 | } |
||
187 | 16 | } |
|
188 | |||
189 | public function SetFastStroke($aFlg = true) |
||
190 | { |
||
191 | $this->iFastStroke = $aFlg; |
||
192 | } |
||
193 | |||
194 | public function FastStroke($img, $xscale, $yscale, $aStartPoint = 0, $exist_x = true) |
||
195 | { |
||
196 | // An optimized stroke for many data points with no extra |
||
197 | // features but 60% faster. You can't have values or line styles, or null |
||
198 | // values in plots. |
||
199 | $numpoints = safe_count($this->coords[0]); |
||
200 | if ($this->barcenter) { |
||
201 | $textadj = 0.5 - $xscale->text_scale_off; |
||
202 | } else { |
||
203 | $textadj = 0; |
||
204 | } |
||
205 | |||
206 | $img->SetColor($this->color); |
||
207 | $img->SetLineWeight($this->weight); |
||
208 | $pnts = $aStartPoint; |
||
209 | while ($pnts < $numpoints) { |
||
210 | if ($exist_x) { |
||
211 | $x = $this->coords[1][$pnts]; |
||
212 | } else { |
||
213 | $x = $pnts + $textadj; |
||
214 | } |
||
215 | $xt = $xscale->Translate($x); |
||
216 | $y = $this->coords[0][$pnts]; |
||
217 | $yt = $yscale->Translate($y); |
||
218 | if (is_numeric($y)) { |
||
219 | $cord[] = $xt; |
||
220 | $cord[] = $yt; |
||
221 | } elseif ($y == '-' && $pnts > 0) { |
||
222 | // Just ignore |
||
223 | } else { |
||
224 | Util\JpGraphError::RaiseL(10002); //('Plot too complicated for fast line Stroke. Use standard Stroke()'); |
||
225 | } |
||
226 | ++$pnts; |
||
227 | } // WHILE |
||
228 | |||
229 | $img->Polygon($cord, false, true); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||
230 | } |
||
231 | |||
232 | 16 | public function Stroke($img, $xscale, $yscale) |
|
233 | { |
||
234 | 16 | $idx = 0; |
|
235 | 16 | $numpoints = safe_count($this->coords[0]); |
|
236 | 16 | if (isset($this->coords[1])) { |
|
237 | 5 | if (safe_count($this->coords[1]) != $numpoints) { |
|
238 | Util\JpGraphError::RaiseL(2003, safe_count($this->coords[1]), $numpoints); |
||
239 | //("Number of X and Y points are not equal. Number of X-points:". safe_count($this->coords[1])." Number of Y-points:$numpoints"); |
||
240 | } else { |
||
241 | 5 | $exist_x = true; |
|
242 | } |
||
243 | } else { |
||
244 | 13 | $exist_x = false; |
|
245 | } |
||
246 | |||
247 | 16 | if ($this->barcenter) { |
|
248 | $textadj = 0.5 - $xscale->text_scale_off; |
||
249 | } else { |
||
250 | 16 | $textadj = 0; |
|
251 | } |
||
252 | |||
253 | // Find the first numeric data point |
||
254 | 16 | $startpoint = 0; |
|
255 | 16 | while ($startpoint < $numpoints && !is_numeric($this->coords[0][$startpoint])) { |
|
256 | ++$startpoint; |
||
257 | } |
||
258 | |||
259 | // Bail out if no data points |
||
260 | 16 | if ($startpoint == $numpoints) { |
|
261 | return; |
||
262 | } |
||
263 | |||
264 | 16 | if ($this->iFastStroke) { |
|
265 | $this->FastStroke($img, $xscale, $yscale, $startpoint, $exist_x); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
266 | |||
267 | return; |
||
268 | } |
||
269 | |||
270 | 16 | if ($exist_x) { |
|
271 | 5 | $xs = $this->coords[1][$startpoint]; |
|
272 | } else { |
||
273 | 13 | $xs = $textadj + $startpoint; |
|
274 | } |
||
275 | |||
276 | 16 | $img->SetStartPoint( |
|
277 | 16 | $xscale->Translate($xs), |
|
278 | 16 | $yscale->Translate($this->coords[0][$startpoint]) |
|
279 | ); |
||
280 | |||
281 | 16 | if ($this->filled) { |
|
282 | 5 | if ($this->fillFromMax) { |
|
283 | //$max = $yscale->GetMaxVal(); |
||
284 | $cord[$idx++] = $xscale->Translate($xs); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
285 | $cord[$idx++] = $yscale->scale_abs[1]; |
||
286 | } else { |
||
287 | 5 | $min = $yscale->GetMinVal(); |
|
288 | 5 | if ($min > 0 || $this->fillFromMin) { |
|
289 | 2 | $fillmin = $yscale->scale_abs[0]; //Translate($min); |
|
290 | } else { |
||
291 | 4 | $fillmin = $yscale->Translate(0); |
|
292 | } |
||
293 | |||
294 | 5 | $cord[$idx++] = $xscale->Translate($xs); |
|
295 | 5 | $cord[$idx++] = $fillmin; |
|
296 | } |
||
297 | } |
||
298 | 16 | $xt = $xscale->Translate($xs); |
|
299 | 16 | $yt = $yscale->Translate($this->coords[0][$startpoint]); |
|
300 | 16 | $cord[$idx++] = $xt; |
|
301 | 16 | $cord[$idx++] = $yt; |
|
302 | 16 | $yt_old = $yt; |
|
303 | 16 | $xt_old = $xt; |
|
304 | 16 | $y_old = $this->coords[0][$startpoint]; |
|
305 | |||
306 | 16 | $this->value->Stroke($img, $this->coords[0][$startpoint], $xt, $yt); |
|
307 | |||
308 | 16 | $img->SetColor($this->color); |
|
309 | 16 | $img->SetLineWeight($this->weight); |
|
310 | 16 | $img->SetLineStyle($this->line_style); |
|
311 | 16 | $pnts = $startpoint + 1; |
|
312 | 16 | $firstnonumeric = false; |
|
313 | |||
314 | 16 | while ($pnts < $numpoints) { |
|
315 | 16 | if ($exist_x) { |
|
316 | 5 | $x = $this->coords[1][$pnts]; |
|
317 | } else { |
||
318 | 13 | $x = $pnts + $textadj; |
|
319 | } |
||
320 | 16 | $xt = $xscale->Translate($x); |
|
321 | 16 | $yt = $yscale->Translate($this->coords[0][$pnts]); |
|
322 | |||
323 | 16 | $y = $this->coords[0][$pnts]; |
|
324 | 16 | if ($this->step_style) { |
|
325 | // To handle null values within step style we need to record the |
||
326 | // first non numeric value so we know from where to start if the |
||
327 | // non value is '-'. |
||
328 | 1 | if (is_numeric($y)) { |
|
329 | 1 | $firstnonumeric = false; |
|
330 | 1 | if (is_numeric($y_old)) { |
|
331 | 1 | $img->StyleLine($xt_old, $yt_old, $xt, $yt_old); |
|
332 | 1 | $img->StyleLine($xt, $yt_old, $xt, $yt); |
|
333 | } elseif ($y_old == '-') { |
||
334 | $img->StyleLine($xt_first, $yt_first, $xt, $yt_first); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Comprehensibility
Best Practice
introduced
by
|
|||
335 | $img->StyleLine($xt, $yt_first, $xt, $yt); |
||
336 | } else { |
||
337 | $yt_old = $yt; |
||
338 | $xt_old = $xt; |
||
0 ignored issues
–
show
|
|||
339 | } |
||
340 | 1 | $cord[$idx++] = $xt; |
|
341 | 1 | $cord[$idx++] = $yt_old; |
|
342 | 1 | $cord[$idx++] = $xt; |
|
343 | 1 | $cord[$idx++] = $yt; |
|
344 | } elseif ($firstnonumeric == false) { |
||
0 ignored issues
–
show
|
|||
345 | $firstnonumeric = true; |
||
346 | $yt_first = $yt_old; |
||
347 | 1 | $xt_first = $xt_old; |
|
348 | } |
||
349 | } else { |
||
350 | 16 | $tmp1 = $y; |
|
351 | 16 | $prev = $this->coords[0][$pnts - 1]; |
|
352 | 16 | if ($tmp1 === '' || $tmp1 === null || $tmp1 === 'X') { |
|
353 | 2 | $tmp1 = 'x'; |
|
354 | } |
||
355 | |||
356 | 16 | if ($prev === '' || $prev === null || $prev === 'X') { |
|
357 | 2 | $prev = 'x'; |
|
358 | } |
||
359 | |||
360 | 16 | if (is_numeric($y) || (is_string($y) && $y != '-')) { |
|
361 | 16 | if (is_numeric($y) && (is_numeric($prev) || $prev === '-')) { |
|
362 | 16 | $img->StyleLineTo($xt, $yt); |
|
363 | } else { |
||
364 | 2 | $img->SetStartPoint($xt, $yt); |
|
365 | } |
||
366 | } |
||
367 | 16 | if ($this->filled && $tmp1 !== '-') { |
|
368 | 5 | if ($tmp1 === 'x') { |
|
369 | $cord[$idx++] = $cord[$idx - 3]; |
||
370 | $cord[$idx++] = $fillmin; |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
371 | 5 | } elseif ($prev === 'x') { |
|
372 | $cord[$idx++] = $xt; |
||
373 | $cord[$idx++] = $fillmin; |
||
374 | $cord[$idx++] = $xt; |
||
375 | $cord[$idx++] = $yt; |
||
376 | } else { |
||
377 | 5 | $cord[$idx++] = $xt; |
|
378 | 5 | $cord[$idx++] = $yt; |
|
379 | } |
||
380 | } else { |
||
381 | 16 | if (is_numeric($tmp1) && (is_numeric($prev) || $prev === '-')) { |
|
382 | 16 | $cord[$idx++] = $xt; |
|
383 | 16 | $cord[$idx++] = $yt; |
|
384 | } |
||
385 | } |
||
386 | } |
||
387 | 16 | $yt_old = $yt; |
|
388 | 16 | $xt_old = $xt; |
|
389 | 16 | $y_old = $y; |
|
390 | |||
391 | 16 | $this->StrokeDataValue($img, $this->coords[0][$pnts], $xt, $yt); |
|
392 | |||
393 | 16 | ++$pnts; |
|
394 | } |
||
395 | |||
396 | 16 | if ($this->filled) { |
|
397 | 5 | $cord[$idx++] = $xt; |
|
398 | 5 | if ($this->fillFromMax) { |
|
399 | $cord[$idx++] = $yscale->scale_abs[1]; |
||
400 | } else { |
||
401 | 5 | if ($min > 0 || $this->fillFromMin) { |
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
402 | 2 | $cord[$idx++] = $yscale->Translate($min); |
|
403 | } else { |
||
404 | 4 | $cord[$idx++] = $yscale->Translate(0); |
|
405 | } |
||
406 | } |
||
407 | 5 | if ($this->fillgrad) { |
|
408 | $img->SetLineWeight(1); |
||
409 | $grad = new Gradient($img); |
||
410 | $grad->SetNumColors($this->fillgrad_numcolors); |
||
411 | $grad->FilledFlatPolygon($cord, $this->fillgrad_fromcolor, $this->fillgrad_tocolor); |
||
412 | $img->SetLineWeight($this->weight); |
||
413 | } else { |
||
414 | 5 | $img->SetColor($this->fill_color); |
|
415 | 5 | $img->FilledPolygon($cord); |
|
416 | } |
||
417 | 5 | if ($this->weight > 0) { |
|
418 | 5 | $img->SetLineWeight($this->weight); |
|
419 | 5 | $img->SetColor($this->color); |
|
420 | // Remove first and last coordinate before drawing the line |
||
421 | // sine we otherwise get the vertical start and end lines which |
||
422 | // doesn't look appropriate |
||
423 | 5 | $img->Polygon(array_slice($cord, 2, safe_count($cord) - 4)); |
|
424 | } |
||
425 | } |
||
426 | |||
427 | 16 | if (!empty($this->filledAreas)) { |
|
428 | 1 | $minY = $yscale->Translate($yscale->GetMinVal()); |
|
429 | 1 | $factor = ($this->step_style ? 4 : 2); |
|
430 | |||
431 | 1 | for ($i = 0; $i < safe_count($this->filledAreas); ++$i) { |
|
432 | // go through all filled area elements ordered by insertion |
||
433 | // fill polygon array |
||
434 | 1 | $areaCoords[] = $cord[$this->filledAreas[$i][0] * $factor]; |
|
435 | 1 | $areaCoords[] = $minY; |
|
436 | |||
437 | $areaCoords = |
||
438 | 1 | array_merge( |
|
439 | 1 | $areaCoords, |
|
440 | 1 | array_slice( |
|
441 | 1 | $cord, |
|
442 | 1 | $this->filledAreas[$i][0] * $factor, |
|
443 | 1 | ($this->filledAreas[$i][1] - $this->filledAreas[$i][0] + ($this->step_style ? 0 : 1)) * $factor |
|
444 | ) |
||
445 | ); |
||
446 | 1 | $areaCoords[] = $areaCoords[safe_count($areaCoords) - 2]; // last x |
|
447 | 1 | $areaCoords[] = $minY; // last y |
|
448 | |||
449 | 1 | if ($this->filledAreas[$i][3]) { |
|
450 | 1 | $img->SetColor($this->filledAreas[$i][2]); |
|
451 | 1 | $img->FilledPolygon($areaCoords); |
|
452 | 1 | $img->SetColor($this->color); |
|
453 | } |
||
454 | // Check if we should draw the frame. |
||
455 | // If not we still re-draw the line since it might have been |
||
456 | // partially overwritten by the filled area and it doesn't look |
||
457 | // very good. |
||
458 | 1 | if ($this->filledAreas[$i][4]) { |
|
459 | $img->Polygon($areaCoords); |
||
460 | } else { |
||
461 | 1 | $img->Polygon($cord); |
|
462 | } |
||
463 | |||
464 | 1 | $areaCoords = []; |
|
465 | } |
||
466 | } |
||
467 | |||
468 | 16 | if (!is_object($this->mark) || $this->mark->type == -1 || $this->mark->show == false) { |
|
0 ignored issues
–
show
|
|||
469 | 11 | return; |
|
470 | } |
||
471 | |||
472 | 8 | for ($pnts = 0; $pnts < $numpoints; ++$pnts) { |
|
473 | 8 | if ($exist_x) { |
|
474 | 2 | $x = $this->coords[1][$pnts]; |
|
475 | } else { |
||
476 | 7 | $x = $pnts + $textadj; |
|
477 | } |
||
478 | 8 | $xt = $xscale->Translate($x); |
|
479 | 8 | $yt = $yscale->Translate($this->coords[0][$pnts]); |
|
480 | |||
481 | 8 | if (is_numeric($this->coords[0][$pnts])) { |
|
482 | 8 | if (!empty($this->csimtargets[$pnts])) { |
|
483 | if (!empty($this->csimwintargets[$pnts])) { |
||
484 | $this->mark->SetCSIMTarget($this->csimtargets[$pnts], $this->csimwintargets[$pnts]); |
||
485 | } else { |
||
486 | $this->mark->SetCSIMTarget($this->csimtargets[$pnts]); |
||
487 | } |
||
488 | $this->mark->SetCSIMAlt($this->csimalts[$pnts]); |
||
489 | } |
||
490 | 8 | if ($exist_x) { |
|
491 | 2 | $x = $this->coords[1][$pnts]; |
|
492 | } else { |
||
493 | 7 | $x = $pnts; |
|
494 | } |
||
495 | 8 | $this->mark->SetCSIMAltVal($this->coords[0][$pnts], $x); |
|
496 | 8 | $this->mark->Stroke($img, $xt, $yt); |
|
497 | 8 | $this->csimareas .= $this->mark->GetCSIMAreas(); |
|
498 | } |
||
499 | } |
||
500 | 8 | } |
|
501 | } // @class |
||
502 |