1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* JPGraph v4.1.0-beta.01 |
5
|
|
|
*/ |
6
|
|
|
|
7
|
|
|
namespace Amenadiel\JpGraph\Graph\Scale; |
8
|
|
|
|
9
|
|
|
use function abs; |
10
|
|
|
use Amenadiel\JpGraph\Graph\Configs; |
11
|
|
|
use Amenadiel\JpGraph\Util; |
12
|
|
|
use function assert; |
13
|
|
|
use function ceil; |
14
|
|
|
use function floor; |
15
|
|
|
use function is_numeric; |
16
|
|
|
use function log10; |
17
|
|
|
use function pow; |
18
|
|
|
use function round; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @class LinearScale |
22
|
|
|
* // Description: Handle linear scaling between screen and world |
23
|
|
|
*/ |
24
|
|
|
class Scale extends Configs |
25
|
|
|
{ |
26
|
|
|
public $textscale = false; // Just a flag to let the Plot class find out if |
27
|
|
|
// we are a textscale or not. This is a cludge since |
28
|
|
|
// this information is available in Graph::axtype but |
29
|
|
|
// we don't have access to the graph object in the Plots |
30
|
|
|
// stroke method. So we let graph store the status here |
31
|
|
|
// when the linear scale is created. A real cludge... |
32
|
|
|
public $type; // is this x or y scale ? |
33
|
|
|
public $ticks; // Store ticks |
34
|
|
|
public $text_scale_off = 0; |
35
|
|
|
public $scale_abs = [0, 0]; |
36
|
|
|
public $scale_factor; // Scale factor between world and screen |
37
|
|
|
public $off; // Offset between image edge and plot area |
38
|
|
|
public $scale = [0, 0]; |
39
|
|
|
public $name = 'lin'; |
40
|
|
|
public $auto_ticks = false; // When using manual scale should the ticks be automatically set? |
41
|
|
|
public $world_abs_size; // Plot area size in pixels (Needed public in jpgraph_radar.php) |
42
|
|
|
public $intscale = false; // Restrict autoscale to integers |
43
|
|
|
protected $autoscale_min = false; // Forced minimum value, auto determine max |
44
|
|
|
protected $autoscale_max = false; // Forced maximum value, auto determine min |
45
|
|
|
private $gracetop = 0; |
46
|
|
|
private $gracebottom = 0; |
47
|
|
|
|
48
|
|
|
private $_world_size; // Plot area size in world coordinates |
49
|
|
|
|
50
|
|
|
public function __construct($aMin = 0, $aMax = 0, $aType = 'y') |
51
|
|
|
{ |
52
|
|
|
assert($aType == 'x' || $aType == 'y'); |
53
|
|
|
assert($aMin <= $aMax); |
54
|
|
|
|
55
|
|
|
$this->type = $aType; |
56
|
|
|
$this->scale = [$aMin, $aMax]; |
57
|
|
|
$this->world_size = $aMax - $aMin; |
58
|
|
|
} |
59
|
|
|
// get maximum value for scale |
60
|
|
|
public function GetMaxVal() |
61
|
|
|
{ |
62
|
|
|
return $this->scale[1]; |
63
|
|
|
} |
64
|
|
|
// Get the minimum value in the scale |
65
|
|
|
public function GetMinVal() |
66
|
|
|
{ |
67
|
|
|
return $this->scale[0]; |
68
|
|
|
} |
69
|
|
|
// Check if scale is set or if we should autoscale |
70
|
|
|
// We should do this is either scale or ticks has not been set |
71
|
|
|
public function IsSpecified() |
72
|
|
|
{ |
73
|
|
|
if ($this->GetMinVal() == $this->GetMaxVal()) { |
74
|
|
|
// Scale not set |
75
|
|
|
return false; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
return true; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
// Initialize the conversion constants for this scale |
82
|
|
|
// This tries to pre-calculate as much as possible to speed up the |
83
|
|
|
// actual conversion (with Translate()) later on |
84
|
|
|
// $start =scale start in absolute pixels (for x-scale this is an y-position |
85
|
|
|
// and for an y-scale this is an x-position |
86
|
|
|
// $len =absolute length in pixels of scale |
87
|
|
|
public function SetConstants($aStart, $aLen) |
88
|
|
|
{ |
89
|
|
|
$this->world_abs_size = $aLen; |
90
|
|
|
$this->off = $aStart; |
91
|
|
|
|
92
|
|
|
if ($this->world_size <= 0) { |
93
|
|
|
// This should never ever happen !! |
94
|
|
|
Util\JpGraphError::RaiseL(25074); |
95
|
|
|
//("You have unfortunately stumbled upon a bug in JpGraph. It seems like the scale range is ".$this->world_size." [for ".$this->type." scale] <br> Please report Bug #01 to [email protected] and include the script that gave this error. This problem could potentially be caused by trying to use \"illegal\" values in the input data arrays (like trying to send in strings or only NULL values) which causes the autoscaling to fail."); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
// scale_factor = number of pixels per world unit |
99
|
|
|
$this->scale_factor = $this->world_abs_size / ($this->world_size * 1.0); |
100
|
|
|
|
101
|
|
|
// scale_abs = start and end points of scale in absolute pixels |
102
|
|
|
$this->scale_abs = [$this->off, $this->off + $this->world_size * $this->scale_factor]; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
// Calculate an integer autoscale |
106
|
|
|
public function IntAutoScale($img, $min, $max, $maxsteps, $majend = true) |
107
|
|
|
{ |
108
|
|
|
// Make sure limits are integers |
109
|
|
|
$min = floor($min); |
110
|
|
|
$max = ceil($max); |
111
|
|
|
if (abs($min - $max) == 0) { |
112
|
|
|
--$min; |
113
|
|
|
++$max; |
114
|
|
|
} |
115
|
|
|
$maxsteps = floor($maxsteps); |
116
|
|
|
|
117
|
|
|
$gracetop = round(($this->gracetop / 100.0) * abs($max - $min)); |
118
|
|
|
$gracebottom = round(($this->gracebottom / 100.0) * abs($max - $min)); |
119
|
|
|
if (is_numeric($this->autoscale_min)) { |
|
|
|
|
120
|
|
|
$min = ceil($this->autoscale_min); |
121
|
|
|
if ($min >= $max) { |
122
|
|
|
Util\JpGraphError::RaiseL(25071); //('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.'); |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
if (is_numeric($this->autoscale_max)) { |
|
|
|
|
127
|
|
|
$max = ceil($this->autoscale_max); |
128
|
|
|
if ($min >= $max) { |
129
|
|
|
Util\JpGraphError::RaiseL(25072); //('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.'); |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
if (abs($min - $max) == 0) { |
134
|
|
|
++$max; |
135
|
|
|
--$min; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
$min -= $gracebottom; |
139
|
|
|
$max += $gracetop; |
140
|
|
|
|
141
|
|
|
// First get tickmarks as multiples of 1, 10, ... |
142
|
|
|
if ($majend) { |
143
|
|
|
list($num1steps, $adj1min, $adj1max, $maj1step) = $this->IntCalcTicks($maxsteps, $min, $max, 1); |
144
|
|
|
} else { |
145
|
|
|
$adj1min = $min; |
146
|
|
|
$adj1max = $max; |
147
|
|
|
list($num1steps, $maj1step) = $this->IntCalcTicksFreeze($maxsteps, $min, $max, 1); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
if (abs($min - $max) > 2) { |
151
|
|
|
// Then get tick marks as 2:s 2, 20, ... |
152
|
|
|
if ($majend) { |
153
|
|
|
list($num2steps, $adj2min, $adj2max, $maj2step) = $this->IntCalcTicks($maxsteps, $min, $max, 5); |
154
|
|
|
} else { |
155
|
|
|
$adj2min = $min; |
156
|
|
|
$adj2max = $max; |
157
|
|
|
list($num2steps, $maj2step) = $this->IntCalcTicksFreeze($maxsteps, $min, $max, 5); |
158
|
|
|
} |
159
|
|
|
} else { |
160
|
|
|
$num2steps = 10000; // Dummy high value so we don't choose this |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
if (abs($min - $max) > 5) { |
164
|
|
|
// Then get tickmarks as 5:s 5, 50, 500, ... |
165
|
|
|
if ($majend) { |
166
|
|
|
list($num5steps, $adj5min, $adj5max, $maj5step) = $this->IntCalcTicks($maxsteps, $min, $max, 2); |
167
|
|
|
} else { |
168
|
|
|
$adj5min = $min; |
169
|
|
|
$adj5max = $max; |
170
|
|
|
list($num5steps, $maj5step) = $this->IntCalcTicksFreeze($maxsteps, $min, $max, 2); |
171
|
|
|
} |
172
|
|
|
} else { |
173
|
|
|
$num5steps = 10000; // Dummy high value so we don't choose this |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
// Check to see whichof 1:s, 2:s or 5:s fit better with |
177
|
|
|
// the requested number of major ticks |
178
|
|
|
$match1 = abs($num1steps - $maxsteps); |
179
|
|
|
$match2 = abs($num2steps - $maxsteps); |
180
|
|
|
if (!empty($maj5step) && $maj5step > 1) { |
181
|
|
|
$match5 = abs($num5steps - $maxsteps); |
182
|
|
|
} else { |
183
|
|
|
$match5 = 10000; // Dummy high value |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
// Compare these three values and see which is the closest match |
187
|
|
|
// We use a 0.6 weight to gravitate towards multiple of 5:s |
188
|
|
|
if ($match1 < $match2) { |
189
|
|
|
if ($match1 < $match5) { |
190
|
|
|
$r = 1; |
191
|
|
|
} else { |
192
|
|
|
$r = 3; |
193
|
|
|
} |
194
|
|
|
} else { |
195
|
|
|
if ($match2 < $match5) { |
196
|
|
|
$r = 2; |
197
|
|
|
} else { |
198
|
|
|
$r = 3; |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
// Minsteps are always the same as maxsteps for integer scale |
202
|
|
|
switch ($r) { |
203
|
|
|
case 1: |
204
|
|
|
$this->ticks->Set($maj1step, $maj1step); |
205
|
|
|
$this->Update($img, $adj1min, $adj1max); |
206
|
|
|
|
207
|
|
|
break; |
208
|
|
|
case 2: |
209
|
|
|
$this->ticks->Set($maj2step, $maj2step); |
|
|
|
|
210
|
|
|
$this->Update($img, $adj2min, $adj2max); |
|
|
|
|
211
|
|
|
|
212
|
|
|
break; |
213
|
|
|
case 3: |
214
|
|
|
$this->ticks->Set($maj5step, $maj5step); |
|
|
|
|
215
|
|
|
$this->Update($img, $adj5min, $adj5max); |
|
|
|
|
216
|
|
|
|
217
|
|
|
break; |
218
|
|
|
default: |
219
|
|
|
Util\JpGraphError::RaiseL(25073, $r); //('Internal error. Integer scale algorithm comparison out of bound (r=$r)'); |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
// Specify a new min/max value for sclae |
224
|
|
|
public function Update($aImg, $aMin, $aMax) |
225
|
|
|
{ |
226
|
|
|
$this->scale = [$aMin, $aMax]; |
227
|
|
|
$this->world_size = $aMax - $aMin; |
228
|
|
|
$this->InitConfigs($aImg); |
229
|
|
|
} |
230
|
|
|
/** |
231
|
|
|
* PRIVATE METHODS. |
232
|
|
|
* |
233
|
|
|
* @param mixed $img |
234
|
|
|
*/ |
235
|
|
|
|
236
|
|
|
// This method recalculates all constants that are depending on the |
237
|
|
|
// margins in the image. If the margins in the image are changed |
238
|
|
|
// this method should be called for every scale that is registred with |
239
|
|
|
// that image. Should really be installed as an observer of that image. |
240
|
|
|
public function InitConfigs($img) |
241
|
|
|
{ |
242
|
|
|
if ($this->type == 'x') { |
243
|
|
|
$this->world_abs_size = $img->width - $img->left_margin - $img->right_margin; |
244
|
|
|
$this->off = $img->left_margin; |
245
|
|
|
$this->scale_factor = 0; |
246
|
|
|
if ($this->world_size > 0) { |
247
|
|
|
$this->scale_factor = $this->world_abs_size / ($this->world_size * 1.0); |
248
|
|
|
} |
249
|
|
|
} else { |
250
|
|
|
// y scale |
251
|
|
|
$this->world_abs_size = $img->height - $img->top_margin - $img->bottom_margin; |
252
|
|
|
$this->off = $img->top_margin + $this->world_abs_size; |
253
|
|
|
$this->scale_factor = 0; |
254
|
|
|
if ($this->world_size > 0) { |
255
|
|
|
$this->scale_factor = -$this->world_abs_size / ($this->world_size * 1.0); |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
$size = $this->world_size * $this->scale_factor; |
259
|
|
|
$this->scale_abs = [$this->off, $this->off + $size]; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// Initialize the conversion constants for this scale |
263
|
|
|
// This tries to pre-calculate as much as possible to speed up the |
264
|
|
|
// actual conversion (with Translate()) later on |
265
|
|
|
// $start =scale start in absolute pixels (for x-scale this is an y-position |
266
|
|
|
// and for an y-scale this is an x-position |
267
|
|
|
// $len =absolute length in pixels of scale |
268
|
|
|
public function SetConfigs($aStart, $aLen) |
269
|
|
|
{ |
270
|
|
|
$this->world_abs_size = $aLen; |
271
|
|
|
$this->off = $aStart; |
272
|
|
|
|
273
|
|
|
if ($this->world_size <= 0) { |
274
|
|
|
// This should never ever happen !! |
275
|
|
|
Util\JpGraphError::RaiseL(25074); |
276
|
|
|
//("You have unfortunately stumbled upon a bug in JpGraph. It seems like the scale range is ".$this->world_size." [for ".$this->type." scale] <br> Please report Bug #01 to [email protected] and include the script that gave this error. This problem could potentially be caused by trying to use \"illegal\" values in the input data arrays (like trying to send in strings or only NULL values) which causes the autoscaling to fail."); |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
// scale_factor = number of pixels per world unit |
280
|
|
|
$this->scale_factor = $this->world_abs_size / ($this->world_size * 1.0); |
281
|
|
|
|
282
|
|
|
// scale_abs = start and end points of scale in absolute pixels |
283
|
|
|
$this->scale_abs = [$this->off, $this->off + $this->world_size * $this->scale_factor]; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
// Calculate number of ticks steps with a specific division |
287
|
|
|
// $a is the divisor of 10**x to generate the first maj tick intervall |
288
|
|
|
// $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,... |
289
|
|
|
// $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,... |
290
|
|
|
// $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,... |
291
|
|
|
// We return a vector of |
292
|
|
|
// [$numsteps,$adjmin,$adjmax,$minstep,$majstep] |
293
|
|
|
// If $majend==true then the first and last marks on the axis will be major |
294
|
|
|
// labeled tick marks otherwise it will be adjusted to the closest min tick mark |
295
|
|
|
public function CalcTicks($maxsteps, $min, $max, $a, $b, $majend = true) |
296
|
|
|
{ |
297
|
|
|
$diff = $max - $min; |
298
|
|
|
if ($diff == 0) { |
299
|
|
|
$ld = 0; |
300
|
|
|
} else { |
301
|
|
|
$ld = floor(log10($diff)); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
// Gravitate min towards zero if we are close |
305
|
|
|
if ($min > 0 && $min < pow(10, $ld)) { |
306
|
|
|
$min = 0; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
//$majstep=pow(10,$ld-1)/$a; |
310
|
|
|
$majstep = pow(10, $ld) / $a; |
311
|
|
|
$minstep = $majstep / $b; |
312
|
|
|
|
313
|
|
|
$adjmax = ceil($max / $minstep) * $minstep; |
314
|
|
|
$adjmin = floor($min / $minstep) * $minstep; |
315
|
|
|
$adjdiff = $adjmax - $adjmin; |
316
|
|
|
$numsteps = $adjdiff / $majstep; |
317
|
|
|
|
318
|
|
|
while ($numsteps > $maxsteps) { |
319
|
|
|
$majstep = pow(10, $ld) / $a; |
320
|
|
|
$numsteps = $adjdiff / $majstep; |
321
|
|
|
++$ld; |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
$minstep = $majstep / $b; |
325
|
|
|
$adjmin = floor($min / $minstep) * $minstep; |
326
|
|
|
$adjdiff = $adjmax - $adjmin; |
|
|
|
|
327
|
|
|
if ($majend) { |
328
|
|
|
$adjmin = floor($min / $majstep) * $majstep; |
329
|
|
|
$adjdiff = $adjmax - $adjmin; |
330
|
|
|
$adjmax = ceil($adjdiff / $majstep) * $majstep + $adjmin; |
331
|
|
|
} else { |
332
|
|
|
$adjmax = ceil($max / $minstep) * $minstep; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
return [$numsteps, $adjmin, $adjmax, $minstep, $majstep]; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
public function CalcTicksFreeze($maxsteps, $min, $max, $a, $b) |
339
|
|
|
{ |
340
|
|
|
// Same as CalcTicks but don't adjust min/max values |
341
|
|
|
$diff = $max - $min; |
342
|
|
|
if ($diff == 0) { |
343
|
|
|
$ld = 0; |
344
|
|
|
} else { |
345
|
|
|
$ld = floor(log10($diff)); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
//$majstep=pow(10,$ld-1)/$a; |
349
|
|
|
$majstep = pow(10, $ld) / $a; |
350
|
|
|
$minstep = $majstep / $b; |
|
|
|
|
351
|
|
|
$numsteps = floor($diff / $majstep); |
352
|
|
|
|
353
|
|
|
while ($numsteps > $maxsteps) { |
354
|
|
|
$majstep = pow(10, $ld) / $a; |
355
|
|
|
$numsteps = floor($diff / $majstep); |
356
|
|
|
++$ld; |
357
|
|
|
} |
358
|
|
|
$minstep = $majstep / $b; |
359
|
|
|
|
360
|
|
|
return [$numsteps, $minstep, $majstep]; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
public function IntCalcTicks($maxsteps, $min, $max, $a, $majend = true) |
364
|
|
|
{ |
365
|
|
|
$diff = $max - $min; |
366
|
|
|
if ($diff == 0) { |
367
|
|
|
Util\JpGraphError::RaiseL(25075); //('Can\'t automatically determine ticks since min==max.'); |
368
|
|
|
} else { |
369
|
|
|
$ld = floor(log10($diff)); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
// Gravitate min towards zero if we are close |
373
|
|
|
if ($min > 0 && $min < pow(10, $ld)) { |
|
|
|
|
374
|
|
|
$min = 0; |
375
|
|
|
} |
376
|
|
|
if ($ld == 0) { |
377
|
|
|
$ld = 1; |
378
|
|
|
} |
379
|
|
|
if ($a == 1) { |
380
|
|
|
$majstep = 1; |
381
|
|
|
} else { |
382
|
|
|
$majstep = pow(10, $ld) / $a; |
383
|
|
|
} |
384
|
|
|
$adjmax = ceil($max / $majstep) * $majstep; |
385
|
|
|
|
386
|
|
|
$adjmin = floor($min / $majstep) * $majstep; |
387
|
|
|
$adjdiff = $adjmax - $adjmin; |
388
|
|
|
$numsteps = $adjdiff / $majstep; |
389
|
|
|
while ($numsteps > $maxsteps) { |
390
|
|
|
$majstep = pow(10, $ld) / $a; |
391
|
|
|
$numsteps = $adjdiff / $majstep; |
392
|
|
|
++$ld; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
$adjmin = floor($min / $majstep) * $majstep; |
396
|
|
|
$adjdiff = $adjmax - $adjmin; |
|
|
|
|
397
|
|
|
if ($majend) { |
398
|
|
|
$adjmin = floor($min / $majstep) * $majstep; |
399
|
|
|
$adjdiff = $adjmax - $adjmin; |
400
|
|
|
$adjmax = ceil($adjdiff / $majstep) * $majstep + $adjmin; |
401
|
|
|
} else { |
402
|
|
|
$adjmax = ceil($max / $majstep) * $majstep; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
return [$numsteps, $adjmin, $adjmax, $majstep]; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
public function IntCalcTicksFreeze($maxsteps, $min, $max, $a) |
409
|
|
|
{ |
410
|
|
|
// Same as IntCalcTick but don't change min/max values |
411
|
|
|
$diff = $max - $min; |
412
|
|
|
if ($diff == 0) { |
413
|
|
|
Util\JpGraphError::RaiseL(25075); //('Can\'t automatically determine ticks since min==max.'); |
414
|
|
|
} else { |
415
|
|
|
$ld = floor(log10($diff)); |
416
|
|
|
} |
417
|
|
|
if ($ld == 0) { |
|
|
|
|
418
|
|
|
$ld = 1; |
419
|
|
|
} |
420
|
|
|
if ($a == 1) { |
421
|
|
|
$majstep = 1; |
422
|
|
|
} else { |
423
|
|
|
$majstep = pow(10, $ld) / $a; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
$numsteps = floor($diff / $majstep); |
427
|
|
|
while ($numsteps > $maxsteps) { |
428
|
|
|
$majstep = pow(10, $ld) / $a; |
429
|
|
|
$numsteps = floor($diff / $majstep); |
430
|
|
|
++$ld; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
return [$numsteps, $majstep]; |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
public function __get($name) |
437
|
|
|
{ |
438
|
|
|
$variable_name = '_' . $name; |
439
|
|
|
|
440
|
|
|
if (isset($this->{$variable_name})) { |
441
|
|
|
return $this->{$variable_name} * Configs::getConfig('SUPERSAMPLING_SCALE'); |
442
|
|
|
} |
443
|
|
|
Util\JpGraphError::RaiseL('25132', $name); |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
public function __set($name, $value) |
447
|
|
|
{ |
448
|
|
|
$this->{'_' . $name} = $value; |
449
|
|
|
} |
450
|
|
|
} // @class |
451
|
|
|
|