1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* JPGraph v4.0.3 |
5
|
|
|
*/ |
6
|
|
|
|
7
|
|
|
namespace Amenadiel\JpGraph\Graph; |
8
|
|
|
|
9
|
|
|
use Amenadiel\JpGraph\Util; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* @class LinearTicks |
13
|
|
|
* // Description: Draw linear ticks on axis |
14
|
|
|
*/ |
15
|
|
|
class LinearTicks extends Ticks |
16
|
|
|
{ |
17
|
|
|
public $minor_step = 1; |
18
|
|
|
public $major_step = 2; |
19
|
|
|
public $xlabel_offset = 0; |
20
|
|
|
public $xtick_offset = 0; |
21
|
|
|
private $label_offset = 0; // What offset should the displayed label have |
22
|
|
|
// i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc |
23
|
|
|
private $text_label_start = 0; |
24
|
|
|
private $iManualTickPos; |
25
|
|
|
private $iManualMinTickPos; |
26
|
|
|
private $iManualTickLabels; |
27
|
|
|
private $iAdjustForDST = false; // If a date falls within the DST period add one hour to the diaplyed time |
28
|
|
|
|
29
|
19 |
|
public function __construct() |
30
|
|
|
{ |
31
|
19 |
|
$this->precision = -1; |
32
|
19 |
|
} |
33
|
|
|
|
34
|
|
|
// Return major step size in world coordinates |
35
|
19 |
|
public function GetMajor() |
36
|
|
|
{ |
37
|
19 |
|
return $this->major_step; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
// Return minor step size in world coordinates |
41
|
|
|
public function GetMinor() |
42
|
|
|
{ |
43
|
|
|
return $this->minor_step; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
// Set Minor and Major ticks (in world coordinates) |
47
|
19 |
|
public function Set($aMajStep, $aMinStep = false) |
48
|
|
|
{ |
49
|
19 |
|
if ($aMinStep == false) { |
50
|
|
|
$aMinStep = $aMajStep; |
51
|
|
|
} |
52
|
|
|
|
53
|
19 |
|
if ($aMajStep <= 0 || $aMinStep <= 0) { |
54
|
|
|
Util\JpGraphError::RaiseL(25064); |
55
|
|
|
//(" Minor or major step size is 0. Check that you haven't got an accidental SetTextTicks(0) in your code. If this is not the case you might have stumbled upon a bug in JpGraph. Please report this and if possible include the data that caused the problem."); |
56
|
|
|
} |
57
|
|
|
|
58
|
19 |
|
$this->major_step = $aMajStep; |
59
|
19 |
|
$this->minor_step = $aMinStep; |
60
|
19 |
|
$this->is_set = true; |
61
|
19 |
|
} |
62
|
|
|
|
63
|
|
|
public function SetMajTickPositions($aMajPos, $aLabels = null) |
64
|
|
|
{ |
65
|
|
|
$this->SetTickPositions($aMajPos, null, $aLabels); |
66
|
|
|
} |
67
|
|
|
|
68
|
2 |
|
public function SetTickPositions($aMajPos, $aMinPos = null, $aLabels = null) |
69
|
|
|
{ |
70
|
2 |
|
if (!is_array($aMajPos) || ($aMinPos !== null && !is_array($aMinPos))) { |
71
|
|
|
Util\JpGraphError::RaiseL(25065); //('Tick positions must be specifued as an array()'); |
72
|
|
|
return; |
73
|
|
|
} |
74
|
2 |
|
$n = safe_count($aMajPos); |
75
|
2 |
|
if (is_array($aLabels) && (safe_count($aLabels) != $n)) { |
76
|
|
|
Util\JpGraphError::RaiseL(25066); //('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.'); |
77
|
|
|
} |
78
|
2 |
|
$this->iManualTickPos = $aMajPos; |
79
|
2 |
|
$this->iManualMinTickPos = $aMinPos; |
80
|
2 |
|
$this->iManualTickLabels = $aLabels; |
81
|
2 |
|
} |
82
|
|
|
|
83
|
7 |
|
public function HaveManualLabels() |
84
|
|
|
{ |
85
|
7 |
|
return safe_count($this->iManualTickLabels) > 0; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
// Specify all the tick positions manually and possible also the exact labels |
89
|
2 |
|
public function _doManualTickPos($aScale) |
90
|
|
|
{ |
91
|
2 |
|
$n = safe_count($this->iManualTickPos); |
92
|
2 |
|
$m = safe_count($this->iManualMinTickPos); |
93
|
2 |
|
$doLbl = safe_count($this->iManualTickLabels) > 0; |
94
|
|
|
|
95
|
2 |
|
$this->maj_ticks_pos = []; |
96
|
2 |
|
$this->maj_ticklabels_pos = []; |
97
|
2 |
|
$this->ticks_pos = []; |
98
|
|
|
|
99
|
|
|
// Now loop through the supplied positions and translate them to screen coordinates |
100
|
|
|
// and store them in the maj_label_positions |
101
|
2 |
|
$minScale = $aScale->scale[0]; |
102
|
2 |
|
$maxScale = $aScale->scale[1]; |
103
|
2 |
|
$j = 0; |
104
|
2 |
|
for ($i = 0; $i < $n; ++$i) { |
105
|
|
|
// First make sure that the first tick is not lower than the lower scale value |
106
|
2 |
|
if (!isset($this->iManualTickPos[$i]) || $this->iManualTickPos[$i] < $minScale || $this->iManualTickPos[$i] > $maxScale) { |
107
|
2 |
|
continue; |
108
|
|
|
} |
109
|
|
|
|
110
|
2 |
|
$this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]); |
111
|
2 |
|
$this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j]; |
112
|
|
|
|
113
|
|
|
// Set the minor tick marks the same as major if not specified |
114
|
2 |
|
if ($m <= 0) { |
115
|
2 |
|
$this->ticks_pos[$j] = $this->maj_ticks_pos[$j]; |
116
|
|
|
} |
117
|
2 |
|
if ($doLbl) { |
118
|
1 |
|
$this->maj_ticks_label[$j] = $this->iManualTickLabels[$i]; |
119
|
|
|
} else { |
120
|
1 |
|
$this->maj_ticks_label[$j] = $this->_doLabelFormat($this->iManualTickPos[$i], $i, $n); |
121
|
|
|
} |
122
|
2 |
|
++$j; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
// Some sanity check |
126
|
2 |
|
if (safe_count($this->maj_ticks_pos) < 2) { |
127
|
|
|
Util\JpGraphError::RaiseL(25067); //('Your manually specified scale and ticks is not correct. The scale seems to be too small to hold any of the specified tickl marks.'); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
// Setup the minor tick marks |
131
|
2 |
|
$j = 0; |
132
|
2 |
|
for ($i = 0; $i < $m; ++$i) { |
133
|
|
|
if (empty($this->iManualMinTickPos[$i]) || $this->iManualMinTickPos[$i] < $minScale || $this->iManualMinTickPos[$i] > $maxScale) { |
134
|
|
|
continue; |
135
|
|
|
} |
136
|
|
|
$this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]); |
137
|
|
|
++$j; |
138
|
|
|
} |
139
|
2 |
|
} |
140
|
|
|
|
141
|
19 |
|
public function _doAutoTickPos($aScale) |
142
|
|
|
{ |
143
|
19 |
|
$maj_step_abs = $aScale->scale_factor * $this->major_step; |
144
|
19 |
|
$min_step_abs = $aScale->scale_factor * $this->minor_step; |
145
|
|
|
|
146
|
19 |
|
if ($min_step_abs == 0 || $maj_step_abs == 0) { |
147
|
|
|
Util\JpGraphError::RaiseL(25068); //("A plot has an illegal scale. This could for example be that you are trying to use text autoscaling to draw a line plot with only one point or that the plot area is too small. It could also be that no input data value is numeric (perhaps only '-' or 'x')"); |
148
|
|
|
} |
149
|
|
|
// We need to make this an int since comparing it below |
150
|
|
|
// with the result from round() can give wrong result, such that |
151
|
|
|
// (40 < 40) == TRUE !!! |
152
|
19 |
|
$limit = (int) $aScale->scale_abs[1]; |
153
|
|
|
|
154
|
19 |
|
if ($aScale->textscale) { |
155
|
|
|
// This can only be true for a X-scale (horizontal) |
156
|
|
|
// Define ticks for a text scale. This is slightly different from a |
157
|
|
|
// normal linear type of scale since the position might be adjusted |
158
|
|
|
// and the labels start at on |
159
|
13 |
|
$label = (float) $aScale->GetMinVal() + $this->text_label_start + $this->label_offset; |
160
|
13 |
|
$start_abs = $aScale->scale_factor * $this->text_label_start; |
161
|
13 |
|
$nbrmajticks = round(($aScale->GetMaxVal() - $aScale->GetMinVal() - $this->text_label_start) / $this->major_step) + 1; |
162
|
|
|
|
163
|
13 |
|
$x = $aScale->scale_abs[0] + $start_abs + $this->xlabel_offset * $min_step_abs; |
164
|
13 |
|
for ($i = 0; $label <= $aScale->GetMaxVal() + $this->label_offset; ++$i) { |
165
|
|
|
// Apply format to label |
166
|
13 |
|
$this->maj_ticks_label[$i] = $this->_doLabelFormat($label, $i, $nbrmajticks); |
167
|
13 |
|
$label += $this->major_step; |
168
|
|
|
|
169
|
|
|
// The x-position of the tick marks can be different from the labels. |
170
|
|
|
// Note that we record the tick position (not the label) so that the grid |
171
|
|
|
// happen upon tick marks and not labels. |
172
|
13 |
|
$xtick = $aScale->scale_abs[0] + $start_abs + $this->xtick_offset * $min_step_abs + $i * $maj_step_abs; |
173
|
13 |
|
$this->maj_ticks_pos[$i] = $xtick; |
174
|
13 |
|
$this->maj_ticklabels_pos[$i] = round($x); |
175
|
13 |
|
$x += $maj_step_abs; |
176
|
|
|
} |
177
|
|
|
} else { |
178
|
19 |
|
$label = $aScale->GetMinVal(); |
179
|
19 |
|
$abs_pos = $aScale->scale_abs[0]; |
180
|
19 |
|
$j = 0; |
181
|
19 |
|
$i = 0; |
182
|
19 |
|
$step = round($maj_step_abs / $min_step_abs); |
183
|
19 |
|
if ($aScale->type == 'x') { |
184
|
|
|
// For a normal linear type of scale the major ticks will always be multiples |
185
|
|
|
// of the minor ticks. In order to avoid any rounding issues the major ticks are |
186
|
|
|
// defined as every "step" minor ticks and not calculated separately |
187
|
8 |
|
$nbrmajticks = round(($aScale->GetMaxVal() - $aScale->GetMinVal() - $this->text_label_start) / $this->major_step) + 1; |
188
|
8 |
|
while (round($abs_pos) <= $limit) { |
189
|
8 |
|
$this->ticks_pos[] = round($abs_pos); |
190
|
8 |
|
$this->ticks_label[] = $label; |
191
|
8 |
|
if ($step == 0 || $i % $step == 0 && $j < $nbrmajticks) { |
192
|
8 |
|
$this->maj_ticks_pos[$j] = round($abs_pos); |
193
|
8 |
|
$this->maj_ticklabels_pos[$j] = round($abs_pos); |
194
|
8 |
|
$this->maj_ticks_label[$j] = $this->_doLabelFormat($label, $j, $nbrmajticks); |
195
|
8 |
|
++$j; |
196
|
|
|
} |
197
|
8 |
|
++$i; |
198
|
8 |
|
$abs_pos += $min_step_abs; |
199
|
8 |
|
$label += $this->minor_step; |
200
|
|
|
} |
201
|
18 |
|
} elseif ($aScale->type == 'y') { |
202
|
|
|
//@todo s=2:20,12 s=1:50,6 $this->major_step:$nbr |
203
|
|
|
// abs_point,limit s=1:270,80 s=2:540,160 |
204
|
|
|
// $this->major_step = 50; |
205
|
18 |
|
$nbrmajticks = round(($aScale->GetMaxVal() - $aScale->GetMinVal()) / $this->major_step) + 1; |
206
|
|
|
// $step = 5; |
207
|
18 |
|
while (round($abs_pos) >= $limit) { |
208
|
18 |
|
$this->ticks_pos[$i] = round($abs_pos); |
209
|
18 |
|
$this->ticks_label[$i] = $label; |
210
|
18 |
|
if ($step == 0 || $i % $step == 0 && $j < $nbrmajticks) { |
211
|
18 |
|
$this->maj_ticks_pos[$j] = round($abs_pos); |
212
|
18 |
|
$this->maj_ticklabels_pos[$j] = round($abs_pos); |
213
|
18 |
|
$this->maj_ticks_label[$j] = $this->_doLabelFormat($label, $j, $nbrmajticks); |
214
|
18 |
|
++$j; |
215
|
|
|
} |
216
|
18 |
|
++$i; |
217
|
18 |
|
$abs_pos += $min_step_abs; |
218
|
18 |
|
$label += $this->minor_step; |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
} |
222
|
19 |
|
} |
223
|
|
|
|
224
|
|
|
public function AdjustForDST($aFlg = true) |
225
|
|
|
{ |
226
|
|
|
$this->iAdjustForDST = $aFlg; |
227
|
|
|
} |
228
|
|
|
|
229
|
19 |
|
public function _doLabelFormat($aVal, $aIdx, $aNbrTicks) |
230
|
|
|
{ |
231
|
|
|
// If precision hasn't been specified set it to a sensible value |
232
|
19 |
|
if ($this->precision == -1) { |
233
|
19 |
|
$t = log10($this->minor_step); |
234
|
19 |
|
if ($t > 0) { |
235
|
16 |
|
$precision = 0; |
236
|
|
|
} else { |
237
|
19 |
|
$precision = -floor($t); |
238
|
|
|
} |
239
|
|
|
} else { |
240
|
|
|
$precision = $this->precision; |
241
|
|
|
} |
242
|
|
|
|
243
|
19 |
|
if ($this->label_formfunc != '') { |
244
|
2 |
|
$f = $this->label_formfunc; |
245
|
2 |
|
if ($this->label_formatstr == '') { |
246
|
2 |
|
$l = call_user_func($f, $aVal); |
247
|
|
|
} else { |
248
|
2 |
|
$l = sprintf($this->label_formatstr, call_user_func($f, $aVal)); |
249
|
|
|
} |
250
|
19 |
|
} elseif ($this->label_formatstr != '' || $this->label_dateformatstr != '') { |
251
|
2 |
|
if ($this->label_usedateformat) { |
252
|
|
|
// Adjust the value to take daylight savings into account |
253
|
1 |
|
if (date('I', $aVal) == 1 && $this->iAdjustForDST) { |
254
|
|
|
// DST |
255
|
|
|
$aVal += 3600; |
256
|
|
|
} |
257
|
|
|
|
258
|
1 |
|
$l = date($this->label_formatstr, $aVal); |
259
|
1 |
|
if ($this->label_formatstr == 'W') { |
260
|
|
|
// If we use week formatting then add a single 'w' in front of the |
261
|
|
|
// week number to differentiate it from dates |
262
|
1 |
|
$l = 'w' . $l; |
263
|
|
|
} |
264
|
|
|
} else { |
265
|
2 |
|
if ($this->label_dateformatstr !== '') { |
266
|
|
|
// Adjust the value to take daylight savings into account |
267
|
1 |
|
if (date('I', $aVal) == 1 && $this->iAdjustForDST) { |
268
|
|
|
// DST |
269
|
|
|
$aVal += 3600; |
270
|
|
|
} |
271
|
|
|
|
272
|
1 |
|
$l = date($this->label_dateformatstr, $aVal); |
273
|
1 |
|
if ($this->label_formatstr == 'W') { |
274
|
|
|
// If we use week formatting then add a single 'w' in front of the |
275
|
|
|
// week number to differentiate it from dates |
276
|
1 |
|
$l = 'w' . $l; |
277
|
|
|
} |
278
|
|
|
} else { |
279
|
2 |
|
$l = sprintf($this->label_formatstr, $aVal); |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
} else { |
283
|
|
|
//FIX: if negative precision is returned "0f" , instead of formatted values |
284
|
19 |
|
$format = $precision > 0 ? '%01.' . $precision . 'f' : '%01.0f'; |
285
|
19 |
|
$l = sprintf($format, round($aVal, $precision)); |
|
|
|
|
286
|
|
|
} |
287
|
|
|
|
288
|
19 |
|
if (($this->supress_zerolabel && $l == 0) || ($this->supress_first && $aIdx == 0) || ($this->supress_last && $aIdx == $aNbrTicks - 1)) { |
289
|
9 |
|
$l = ''; |
290
|
|
|
} |
291
|
|
|
|
292
|
19 |
|
return $l; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
// Stroke ticks on either X or Y axis |
296
|
19 |
|
public function _StrokeTicks($aImg, $aScale, $aPos) |
297
|
|
|
{ |
298
|
19 |
|
$hor = $aScale->type == 'x'; |
299
|
19 |
|
$aImg->SetLineWeight($this->weight); |
300
|
|
|
|
301
|
|
|
// We need to make this an int since comparing it below |
302
|
|
|
// with the result from round() can give wrong result, such that |
303
|
|
|
// (40 < 40) == TRUE !!! |
304
|
19 |
|
$limit = (int) $aScale->scale_abs[1]; |
|
|
|
|
305
|
|
|
|
306
|
|
|
// A text scale doesn't have any minor ticks |
307
|
19 |
|
if (!$aScale->textscale) { |
308
|
|
|
// Stroke minor ticks |
309
|
19 |
|
$yu = $aPos - $this->direction * $this->GetMinTickAbsSize(); |
310
|
19 |
|
$xr = $aPos + $this->direction * $this->GetMinTickAbsSize(); |
311
|
19 |
|
$n = safe_count($this->ticks_pos); |
312
|
19 |
|
for ($i = 0; $i < $n; ++$i) { |
313
|
19 |
|
if (!$this->supress_tickmarks && !$this->supress_minor_tickmarks) { |
314
|
3 |
|
if ($this->mincolor != '') { |
315
|
|
|
$aImg->PushColor($this->mincolor); |
316
|
|
|
} |
317
|
3 |
|
if ($hor) { |
318
|
|
|
//if( $this->ticks_pos[$i] <= $limit ) |
319
|
1 |
|
$aImg->Line($this->ticks_pos[$i], $aPos, $this->ticks_pos[$i], $yu); |
320
|
|
|
} else { |
321
|
|
|
//if( $this->ticks_pos[$i] >= $limit ) |
322
|
3 |
|
$aImg->Line($aPos, $this->ticks_pos[$i], $xr, $this->ticks_pos[$i]); |
323
|
|
|
} |
324
|
3 |
|
if ($this->mincolor != '') { |
325
|
|
|
$aImg->PopColor(); |
326
|
|
|
} |
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
// Stroke major ticks |
332
|
19 |
|
$yu = $aPos - $this->direction * $this->GetMajTickAbsSize(); |
333
|
19 |
|
$xr = $aPos + $this->direction * $this->GetMajTickAbsSize(); |
334
|
19 |
|
$nbrmajticks = round(($aScale->GetMaxVal() - $aScale->GetMinVal() - $this->text_label_start) / $this->major_step) + 1; |
335
|
19 |
|
$n = safe_count($this->maj_ticks_pos); |
336
|
19 |
|
for ($i = 0; $i < $n; ++$i) { |
337
|
19 |
|
if (!($this->xtick_offset > 0 && $i == $nbrmajticks - 1) && !$this->supress_tickmarks) { |
338
|
5 |
|
if ($this->majcolor != '') { |
339
|
|
|
$aImg->PushColor($this->majcolor); |
340
|
|
|
} |
341
|
5 |
|
if ($hor) { |
342
|
|
|
//if( $this->maj_ticks_pos[$i] <= $limit ) |
343
|
2 |
|
$aImg->Line($this->maj_ticks_pos[$i], $aPos, $this->maj_ticks_pos[$i], $yu); |
344
|
|
|
} else { |
345
|
|
|
//if( $this->maj_ticks_pos[$i] >= $limit ) |
346
|
4 |
|
$aImg->Line($aPos, $this->maj_ticks_pos[$i], $xr, $this->maj_ticks_pos[$i]); |
347
|
|
|
} |
348
|
5 |
|
if ($this->majcolor != '') { |
349
|
|
|
$aImg->PopColor(); |
350
|
|
|
} |
351
|
|
|
} |
352
|
|
|
} |
353
|
19 |
|
} |
354
|
|
|
|
355
|
|
|
// Draw linear ticks |
356
|
19 |
|
public function Stroke($aImg, $aScale, $aPos) |
357
|
|
|
{ |
358
|
19 |
|
if ($this->iManualTickPos != null) { |
359
|
2 |
|
$this->_doManualTickPos($aScale); |
360
|
|
|
} else { |
361
|
19 |
|
$this->_doAutoTickPos($aScale); |
362
|
|
|
} |
363
|
19 |
|
$this->_StrokeTicks($aImg, $aScale, $aPos, $aScale->type == 'x'); |
|
|
|
|
364
|
19 |
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* PRIVATE METHODS. |
368
|
|
|
* |
369
|
|
|
* @param mixed $aLabelOff |
370
|
|
|
* @param mixed $aTickOff |
371
|
|
|
*/ |
372
|
|
|
// Spoecify the offset of the displayed tick mark with the tick "space" |
373
|
|
|
// Legal values for $o is [0,1] used to adjust where the tick marks and label |
374
|
|
|
// should be positioned within the major tick-size |
375
|
|
|
// $lo specifies the label offset and $to specifies the tick offset |
376
|
|
|
// this comes in handy for example in bar graphs where we wont no offset for the |
377
|
|
|
// tick but have the labels displayed halfway under the bars. |
378
|
18 |
|
public function SetXLabelOffset($aLabelOff, $aTickOff = -1) |
379
|
|
|
{ |
380
|
18 |
|
$this->xlabel_offset = $aLabelOff; |
381
|
18 |
|
if ($aTickOff == -1) { |
382
|
|
|
// Same as label offset |
383
|
16 |
|
$this->xtick_offset = $aLabelOff; |
384
|
|
|
} else { |
385
|
3 |
|
$this->xtick_offset = $aTickOff; |
386
|
|
|
} |
387
|
18 |
|
if ($aLabelOff > 0) { |
388
|
8 |
|
$this->SupressLast(); // The last tick wont fit |
389
|
|
|
} |
390
|
18 |
|
} |
391
|
|
|
|
392
|
|
|
// Which tick label should we start with? |
393
|
2 |
|
public function SetTextLabelStart($aTextLabelOff) |
394
|
|
|
{ |
395
|
2 |
|
$this->text_label_start = $aTextLabelOff; |
396
|
2 |
|
} |
397
|
|
|
} // @class |
398
|
|
|
|