1
|
|
|
<?php |
2
|
|
|
namespace Amenadiel\JpGraph\Plot; |
3
|
|
|
|
4
|
|
|
use Amenadiel\JpGraph\Graph; |
5
|
|
|
use Amenadiel\JpGraph\Text; |
6
|
|
|
use Amenadiel\JpGraph\Util; |
7
|
|
|
|
8
|
|
|
/*======================================================================= |
9
|
|
|
// File: JPGRAPH_PIE.PHP |
10
|
|
|
// Description: Pie plot extension for JpGraph |
11
|
|
|
// Created: 2001-02-14 |
12
|
|
|
// Ver: $Id: jpgraph_pie.php 1926 2010-01-11 16:33:07Z ljp $ |
13
|
|
|
// |
14
|
|
|
// Copyright (c) Asial Corporation. All rights reserved. |
15
|
|
|
//======================================================================== |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
// Defines for PiePlot::SetLabelType() |
|
|
|
|
19
|
|
|
define("PIE_VALUE_ABS", 1); |
20
|
|
|
define("PIE_VALUE_PER", 0); |
21
|
|
|
define("PIE_VALUE_PERCENTAGE", 0); |
22
|
|
|
define("PIE_VALUE_ADJPERCENTAGE", 2); |
23
|
|
|
define("PIE_VALUE_ADJPER", 2); |
24
|
|
|
|
25
|
|
|
//=================================================== |
26
|
|
|
// CLASS PiePlot |
27
|
|
|
// Description: Draws a pie plot |
28
|
|
|
//=================================================== |
29
|
|
|
class PiePlot |
30
|
|
|
{ |
31
|
|
|
public $posx = 0.5; |
32
|
|
|
public $posy = 0.5; |
33
|
|
|
public $is_using_plot_theme = false; |
34
|
|
|
public $theme = "earth"; |
35
|
|
|
protected $use_plot_theme_colors = false; |
36
|
|
|
protected $radius = 0.3; |
37
|
|
|
protected $explode_radius = array(); |
38
|
|
|
protected $explode_all = false; |
39
|
|
|
protected $explode_r = 20; |
40
|
|
|
protected $labels = null; |
41
|
|
|
protected $legends = null; |
42
|
|
|
protected $csimtargets = null; |
43
|
|
|
protected $csimwintargets = null; // Array of targets for CSIM |
44
|
|
|
protected $csimareas = ''; // Generated CSIM text |
45
|
|
|
protected $csimalts = null; // ALT tags for corresponding target |
46
|
|
|
protected $data = null; |
47
|
|
|
public $title; |
48
|
|
|
protected $startangle = 0; |
49
|
|
|
protected $weight = 1; |
50
|
|
|
protected $color = "black"; |
51
|
|
|
protected $legend_margin = 6; |
52
|
|
|
protected $show_labels = true; |
53
|
|
|
protected $themearr = array( |
54
|
|
|
"earth" => array(136, 34, 40, 45, 46, 62, 63, 134, 74, 10, 120, 136, 141, 168, 180, 77, 209, 218, 346, 395, 89, 430), |
55
|
|
|
"pastel" => array(27, 415, 128, 59, 66, 79, 105, 110, 42, 147, 152, 230, 236, 240, 331, 337, 405, 38), |
56
|
|
|
"water" => array(8, 370, 24, 40, 335, 56, 213, 237, 268, 14, 326, 387, 10, 388), |
57
|
|
|
"sand" => array(27, 168, 34, 170, 19, 50, 65, 72, 131, 209, 46, 393)); |
58
|
|
|
protected $setslicecolors = array(); |
59
|
|
|
protected $labeltype = 0; // Default to percentage |
60
|
|
|
protected $pie_border = true; |
61
|
|
|
protected $pie_interior_border = true; |
62
|
|
|
public $value; |
63
|
|
|
protected $ishadowcolor = ''; |
64
|
|
|
protected $ishadowdrop = 4; |
65
|
|
|
protected $ilabelposadj = 1; |
66
|
|
|
protected $legendcsimtargets = array(); |
67
|
|
|
protected $legendcsimwintargets = array(); |
68
|
|
|
protected $legendcsimalts = array(); |
69
|
|
|
protected $adjusted_data = array(); |
70
|
|
|
public $guideline = null; |
71
|
|
|
protected $guidelinemargin = 10; |
72
|
|
|
protected $iShowGuideLineForSingle = false; |
73
|
|
|
protected $iGuideLineCurve = false; |
74
|
|
|
protected $iGuideVFactor = 1.4; |
75
|
|
|
protected $iGuideLineRFactor = 0.8; |
76
|
|
|
protected $la = array(); // Holds the exact angle for each label |
77
|
|
|
|
78
|
|
|
//--------------- |
79
|
|
|
// CONSTRUCTOR |
80
|
|
View Code Duplication |
public function __construct($data) |
|
|
|
|
81
|
|
|
{ |
82
|
|
|
$this->data = array_reverse($data); |
83
|
|
|
$this->title = new Text\Text(""); |
84
|
|
|
$this->title->SetFont(FF_DEFAULT, FS_BOLD); |
85
|
|
|
$this->value = new DisplayValue(); |
86
|
|
|
$this->value->Show(); |
87
|
|
|
$this->value->SetFormat('%.1f%%'); |
88
|
|
|
$this->guideline = new Graph\LineProperty(); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
//--------------- |
92
|
|
|
// PUBLIC METHODS |
93
|
|
|
public function SetCenter($x, $y = 0.5) |
94
|
|
|
{ |
95
|
|
|
$this->posx = $x; |
96
|
|
|
$this->posy = $y; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
// Enable guideline and set drwaing policy |
100
|
|
|
public function SetGuideLines($aFlg = true, $aCurved = true, $aAlways = false) |
101
|
|
|
{ |
102
|
|
|
$this->guideline->Show($aFlg); |
103
|
|
|
$this->iShowGuideLineForSingle = $aAlways; |
104
|
|
|
$this->iGuideLineCurve = $aCurved; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
// Adjuste the distance between labels and labels and pie |
108
|
|
|
public function SetGuideLinesAdjust($aVFactor, $aRFactor = 0.8) |
109
|
|
|
{ |
110
|
|
|
$this->iGuideVFactor = $aVFactor; |
111
|
|
|
$this->iGuideLineRFactor = $aRFactor; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
public function SetColor($aColor) |
115
|
|
|
{ |
116
|
|
|
$this->color = $aColor; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
public function SetSliceColors($aColors) |
120
|
|
|
{ |
121
|
|
|
$this->setslicecolors = $aColors; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
public function SetShadow($aColor = 'darkgray', $aDropWidth = 4) |
125
|
|
|
{ |
126
|
|
|
$this->ishadowcolor = $aColor; |
127
|
|
|
$this->ishadowdrop = $aDropWidth; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
public function SetCSIMTargets($aTargets, $aAlts = '', $aWinTargets = '') |
131
|
|
|
{ |
132
|
|
|
$this->csimtargets = array_reverse($aTargets); |
133
|
|
|
if (is_array($aWinTargets)) { |
134
|
|
|
$this->csimwintargets = array_reverse($aWinTargets); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
if (is_array($aAlts)) { |
138
|
|
|
$this->csimalts = array_reverse($aAlts); |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
public function GetCSIMareas() |
143
|
|
|
{ |
144
|
|
|
return $this->csimareas; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
public function AddSliceToCSIM($i, $xc, $yc, $radius, $sa, $ea) |
148
|
|
|
{ |
149
|
|
|
//Slice number, ellipse centre (x,y), height, width, start angle, end angle |
150
|
|
|
while ($sa > 2 * M_PI) { |
151
|
|
|
$sa = $sa - 2 * M_PI; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
while ($ea > 2 * M_PI) { |
155
|
|
|
$ea = $ea - 2 * M_PI; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
$sa = 2 * M_PI - $sa; |
159
|
|
|
$ea = 2 * M_PI - $ea; |
160
|
|
|
|
161
|
|
|
// Special case when we have only one slice since then both start and end |
162
|
|
|
// angle will be == 0 |
163
|
|
View Code Duplication |
if (abs($sa - $ea) < 0.0001) { |
|
|
|
|
164
|
|
|
$sa = 2 * M_PI; |
165
|
|
|
$ea = 0; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
//add coordinates of the centre to the map |
169
|
|
|
$xc = floor($xc); |
170
|
|
|
$yc = floor($yc); |
171
|
|
|
$coords = "$xc, $yc"; |
172
|
|
|
|
173
|
|
|
//add coordinates of the first point on the arc to the map |
174
|
|
|
$xp = floor(($radius * cos($ea)) + $xc); |
175
|
|
|
$yp = floor($yc - $radius * sin($ea)); |
176
|
|
|
$coords .= ", $xp, $yp"; |
177
|
|
|
|
178
|
|
|
//add coordinates every 0.2 radians |
179
|
|
|
$a = $ea + 0.2; |
180
|
|
|
|
181
|
|
|
// If we cross the 360-limit with a slice we need to handle |
182
|
|
|
// the fact that end angle is smaller than start |
183
|
|
View Code Duplication |
if ($sa < $ea) { |
|
|
|
|
184
|
|
|
while ($a <= 2 * M_PI) { |
185
|
|
|
$xp = floor($radius * cos($a) + $xc); |
186
|
|
|
$yp = floor($yc - $radius * sin($a)); |
187
|
|
|
$coords .= ", $xp, $yp"; |
188
|
|
|
$a += 0.2; |
189
|
|
|
} |
190
|
|
|
$a -= 2 * M_PI; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
while ($a < $sa) { |
194
|
|
|
$xp = floor($radius * cos($a) + $xc); |
195
|
|
|
$yp = floor($yc - $radius * sin($a)); |
196
|
|
|
$coords .= ", $xp, $yp"; |
197
|
|
|
$a += 0.2; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
//Add the last point on the arc |
201
|
|
|
$xp = floor($radius * cos($sa) + $xc); |
202
|
|
|
$yp = floor($yc - $radius * sin($sa)); |
203
|
|
|
$coords .= ", $xp, $yp"; |
204
|
|
View Code Duplication |
if (!empty($this->csimtargets[$i])) { |
|
|
|
|
205
|
|
|
$this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"" . $this->csimtargets[$i] . "\""; |
206
|
|
|
$tmp = ""; |
|
|
|
|
207
|
|
|
if (!empty($this->csimwintargets[$i])) { |
208
|
|
|
$this->csimareas .= " target=\"" . $this->csimwintargets[$i] . "\" "; |
209
|
|
|
} |
210
|
|
|
if (!empty($this->csimalts[$i])) { |
211
|
|
|
$tmp = sprintf($this->csimalts[$i], $this->data[$i]); |
212
|
|
|
$this->csimareas .= " title=\"$tmp\" alt=\"$tmp\" "; |
213
|
|
|
} |
214
|
|
|
$this->csimareas .= " />\n"; |
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
public function SetTheme($aTheme) |
219
|
|
|
{ |
220
|
|
|
// Util\JpGraphError::RaiseL(15012,$aTheme); |
|
|
|
|
221
|
|
|
// return; |
222
|
|
|
|
223
|
|
|
if (in_array($aTheme, array_keys($this->themearr))) { |
224
|
|
|
$this->theme = $aTheme; |
225
|
|
|
$this->is_using_plot_theme = true; |
226
|
|
|
} else { |
227
|
|
|
Util\JpGraphError::RaiseL(15001, $aTheme); //("PiePLot::SetTheme() Unknown theme: $aTheme"); |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
public function ExplodeSlice($e, $radius = 20) |
232
|
|
|
{ |
233
|
|
|
if (!is_integer($e)) { |
234
|
|
|
Util\JpGraphError::RaiseL(15002); |
235
|
|
|
} |
236
|
|
|
//('Argument to PiePlot::ExplodeSlice() must be an integer'); |
237
|
|
|
$this->explode_radius[$e] = $radius; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
public function ExplodeAll($radius = 20) |
241
|
|
|
{ |
242
|
|
|
$this->explode_all = true; |
243
|
|
|
$this->explode_r = $radius; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
public function Explode($aExplodeArr) |
247
|
|
|
{ |
248
|
|
|
if (!is_array($aExplodeArr)) { |
249
|
|
|
Util\JpGraphError::RaiseL(15003); |
250
|
|
|
//("Argument to PiePlot::Explode() must be an array with integer distances."); |
251
|
|
|
} |
252
|
|
|
$this->explode_radius = $aExplodeArr; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
public function SetStartAngle($aStart) |
256
|
|
|
{ |
257
|
|
|
if ($aStart < 0 || $aStart > 360) { |
258
|
|
|
Util\JpGraphError::RaiseL(15004); //('Slice start angle must be between 0 and 360 degrees.'); |
259
|
|
|
} |
260
|
|
|
if ($aStart == 0) { |
261
|
|
|
$this->startangle = 0; |
262
|
|
|
} else { |
263
|
|
|
$this->startangle = 360 - $aStart; |
|
|
|
|
264
|
|
|
$this->startangle *= M_PI / 180; |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
// Size in percentage |
269
|
|
|
public function SetSize($aSize) |
270
|
|
|
{ |
271
|
|
|
if (($aSize > 0 && $aSize <= 0.5) || ($aSize > 10 && $aSize < 1000)) { |
272
|
|
|
$this->radius = $aSize; |
273
|
|
|
} else { |
274
|
|
|
Util\JpGraphError::RaiseL(15006); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
//("PiePlot::SetSize() Radius for pie must either be specified as a fraction [0, 0.5] of the size of the image or as an absolute size in pixels in the range [10, 1000]"); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
// Set label arrays |
281
|
|
|
public function SetLegends($aLegend) |
282
|
|
|
{ |
283
|
|
|
$this->legends = $aLegend; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
// Set text labels for slices |
287
|
|
|
public function SetLabels($aLabels, $aLblPosAdj = "auto") |
288
|
|
|
{ |
289
|
|
|
$this->labels = array_reverse($aLabels); |
290
|
|
|
$this->ilabelposadj = $aLblPosAdj; |
|
|
|
|
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
public function SetLabelPos($aLblPosAdj) |
294
|
|
|
{ |
295
|
|
|
$this->ilabelposadj = $aLblPosAdj; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
// Should we display actual value or percentage? |
299
|
|
|
public function SetLabelType($aType) |
300
|
|
|
{ |
301
|
|
|
if ($aType < 0 || $aType > 2) { |
302
|
|
|
Util\JpGraphError::RaiseL(15008, $aType); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
//("PiePlot::SetLabelType() Type for pie plots must be 0 or 1 (not $t)."); |
306
|
|
|
$this->labeltype = $aType; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
// Deprecated. |
310
|
|
|
public function SetValueType($aType) |
311
|
|
|
{ |
312
|
|
|
$this->SetLabelType($aType); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
// Should the circle around a pie plot be displayed |
316
|
|
|
public function ShowBorder($exterior = true, $interior = true) |
317
|
|
|
{ |
318
|
|
|
$this->pie_border = $exterior; |
319
|
|
|
$this->pie_interior_border = $interior; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
// Setup the legends |
323
|
|
|
public function Legend($graph) |
324
|
|
|
{ |
325
|
|
|
$colors = array_keys($graph->img->rgb->rgb_table); |
326
|
|
|
sort($colors); |
327
|
|
|
$ta = $this->themearr[$this->theme]; |
328
|
|
|
$n = count($this->data); |
329
|
|
|
|
330
|
|
|
if ($this->setslicecolors == null) { |
331
|
|
|
$numcolors = count($ta); |
332
|
|
|
if (class_exists('PiePlot3D', false) && ($this instanceof PiePlot3D)) { |
333
|
|
|
$ta = array_reverse(array_slice($ta, 0, $n)); |
334
|
|
|
} |
335
|
|
|
} else { |
336
|
|
|
$this->setslicecolors = array_slice($this->setslicecolors, 0, $n); |
337
|
|
|
$numcolors = count($this->setslicecolors); |
338
|
|
|
if ($graph->pieaa && !($this instanceof PiePlot3D)) { |
339
|
|
|
$this->setslicecolors = array_reverse($this->setslicecolors); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
$sum = 0; |
344
|
|
|
for ($i = 0; $i < $n; ++$i) { |
345
|
|
|
$sum += $this->data[$i]; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
// Bail out with error if the sum is 0 |
349
|
|
|
if ($sum == 0) { |
350
|
|
|
Util\JpGraphError::RaiseL(15009); |
351
|
|
|
} |
352
|
|
|
//("Illegal pie plot. Sum of all data is zero for Pie!"); |
353
|
|
|
|
354
|
|
|
// Make sure we don't plot more values than data points |
355
|
|
|
// (in case the user added more legends than data points) |
356
|
|
|
$n = min(count($this->legends), count($this->data)); |
357
|
|
|
if ($this->legends != "") { |
358
|
|
|
$this->legends = array_reverse(array_slice($this->legends, 0, $n)); |
359
|
|
|
} |
360
|
|
|
for ($i = $n - 1; $i >= 0; --$i) { |
361
|
|
|
$l = $this->legends[$i]; |
362
|
|
|
// Replace possible format with actual values |
363
|
|
|
if (count($this->csimalts) > $i) { |
364
|
|
|
$fmt = $this->csimalts[$i]; |
365
|
|
|
} else { |
366
|
|
|
$fmt = "%d"; // Deafult Alt if no other has been specified |
367
|
|
|
} |
368
|
|
|
if ($this->labeltype == 0) { |
369
|
|
|
$l = sprintf($l, 100 * $this->data[$i] / $sum); |
370
|
|
|
$alt = sprintf($fmt, $this->data[$i]); |
371
|
|
|
} elseif ($this->labeltype == 1) { |
372
|
|
|
$l = sprintf($l, $this->data[$i]); |
373
|
|
|
$alt = sprintf($fmt, $this->data[$i]); |
374
|
|
|
} else { |
375
|
|
|
$l = sprintf($l, $this->adjusted_data[$i]); |
376
|
|
|
$alt = sprintf($fmt, $this->adjusted_data[$i]); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
if (empty($this->csimwintargets[$i])) { |
380
|
|
|
$wintarg = ''; |
381
|
|
|
} else { |
382
|
|
|
$wintarg = $this->csimwintargets[$i]; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
if ($this->setslicecolors == null) { |
386
|
|
|
$graph->legend->Add($l, $colors[$ta[$i % $numcolors]], "", 0, $this->csimtargets[$i], $alt, $wintarg); |
387
|
|
|
} else { |
388
|
|
|
$graph->legend->Add($l, $this->setslicecolors[$i % $numcolors], "", 0, $this->csimtargets[$i], $alt, $wintarg); |
389
|
|
|
} |
390
|
|
|
} |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
// Adjust the rounded percetage value so that the sum of |
394
|
|
|
// of the pie slices are always 100% |
395
|
|
|
// Using the Hare/Niemeyer method |
396
|
|
|
public function AdjPercentage($aData, $aPrec = 0) |
397
|
|
|
{ |
398
|
|
|
$mul = 100; |
399
|
|
|
if ($aPrec > 0 && $aPrec < 3) { |
400
|
|
|
if ($aPrec == 1) { |
401
|
|
|
$mul = 1000; |
402
|
|
|
} else { |
403
|
|
|
$mul = 10000; |
404
|
|
|
} |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
$tmp = array(); |
408
|
|
|
$result = array(); |
409
|
|
|
$quote_sum = 0; |
410
|
|
|
$n = count($aData); |
411
|
|
|
for ($i = 0, $sum = 0; $i < $n; ++$i) { |
412
|
|
|
$sum += $aData[$i]; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
foreach ($aData as $index => $value) { |
416
|
|
|
$tmp_percentage = $value / $sum * $mul; |
417
|
|
|
$result[$index] = floor($tmp_percentage); |
418
|
|
|
$tmp[$index] = $tmp_percentage - $result[$index]; |
419
|
|
|
$quote_sum += $result[$index]; |
420
|
|
|
} |
421
|
|
|
if ($quote_sum == $mul) { |
422
|
|
View Code Duplication |
if ($mul > 100) { |
|
|
|
|
423
|
|
|
$tmp = $mul / 100; |
424
|
|
|
for ($i = 0; $i < $n; ++$i) { |
425
|
|
|
$result[$i] /= $tmp; |
426
|
|
|
} |
427
|
|
|
} |
428
|
|
|
return $result; |
429
|
|
|
} |
430
|
|
|
arsort($tmp, SORT_NUMERIC); |
431
|
|
|
reset($tmp); |
432
|
|
|
for ($i = 0; $i < $mul - $quote_sum; $i++) { |
433
|
|
|
$result[key($tmp)]++; |
434
|
|
|
next($tmp); |
435
|
|
|
} |
436
|
|
View Code Duplication |
if ($mul > 100) { |
|
|
|
|
437
|
|
|
$tmp = $mul / 100; |
438
|
|
|
for ($i = 0; $i < $n; ++$i) { |
439
|
|
|
$result[$i] /= $tmp; |
440
|
|
|
} |
441
|
|
|
} |
442
|
|
|
return $result; |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
public function Stroke($img, $aaoption = 0) |
446
|
|
|
{ |
447
|
|
|
// aaoption is used to handle antialias |
448
|
|
|
// aaoption == 0 a normal pie |
449
|
|
|
// aaoption == 1 just the body |
450
|
|
|
// aaoption == 2 just the values |
451
|
|
|
|
452
|
|
|
// Explode scaling. If anti alias we scale the image |
453
|
|
|
// twice and we also need to scale the exploding distance |
454
|
|
|
$expscale = $aaoption === 1 ? 2 : 1; |
455
|
|
|
|
456
|
|
|
if ($this->labeltype == 2) { |
457
|
|
|
// Adjust the data so that it will add up to 100% |
458
|
|
|
$this->adjusted_data = $this->AdjPercentage($this->data); |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
if ($this->use_plot_theme_colors) { |
462
|
|
|
$this->setslicecolors = null; |
|
|
|
|
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
$colors = array_keys($img->rgb->rgb_table); |
466
|
|
|
sort($colors); |
467
|
|
|
$ta = $this->themearr[$this->theme]; |
468
|
|
|
$n = count($this->data); |
469
|
|
|
|
470
|
|
|
if ($this->setslicecolors == null) { |
471
|
|
|
$numcolors = count($ta); |
472
|
|
|
} else { |
473
|
|
|
// We need to create an array of colors as long as the data |
474
|
|
|
// since we need to reverse it to get the colors in the right order |
475
|
|
|
$numcolors = count($this->setslicecolors); |
476
|
|
|
$i = 2 * $numcolors; |
477
|
|
|
while ($n > $i) { |
478
|
|
|
$this->setslicecolors = array_merge($this->setslicecolors, $this->setslicecolors); |
479
|
|
|
$i += $n; |
480
|
|
|
} |
481
|
|
|
$tt = array_slice($this->setslicecolors, 0, $n % $numcolors); |
482
|
|
|
$this->setslicecolors = array_merge($this->setslicecolors, $tt); |
483
|
|
|
$this->setslicecolors = array_reverse($this->setslicecolors); |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
// Draw the slices |
487
|
|
|
$sum = 0; |
488
|
|
|
for ($i = 0; $i < $n; ++$i) { |
489
|
|
|
$sum += $this->data[$i]; |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
// Bail out with error if the sum is 0 |
493
|
|
|
if ($sum == 0) { |
494
|
|
|
Util\JpGraphError::RaiseL(15009); //("Sum of all data is 0 for Pie."); |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
// Set up the pie-circle |
498
|
|
|
if ($this->radius <= 1) { |
499
|
|
|
$radius = floor($this->radius * min($img->width, $img->height)); |
500
|
|
|
} else { |
501
|
|
|
$radius = $aaoption === 1 ? $this->radius * 2 : $this->radius; |
502
|
|
|
} |
503
|
|
|
|
504
|
|
View Code Duplication |
if ($this->posx <= 1 && $this->posx > 0) { |
|
|
|
|
505
|
|
|
$xc = round($this->posx * $img->width); |
506
|
|
|
} else { |
507
|
|
|
$xc = $this->posx; |
508
|
|
|
} |
509
|
|
|
|
510
|
|
View Code Duplication |
if ($this->posy <= 1 && $this->posy > 0) { |
|
|
|
|
511
|
|
|
$yc = round($this->posy * $img->height); |
512
|
|
|
} else { |
513
|
|
|
$yc = $this->posy; |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
$n = count($this->data); |
517
|
|
|
|
518
|
|
View Code Duplication |
if ($this->explode_all) { |
|
|
|
|
519
|
|
|
for ($i = 0; $i < $n; ++$i) { |
520
|
|
|
$this->explode_radius[$i] = $this->explode_r; |
521
|
|
|
} |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
// If we have a shadow and not just drawing the labels |
525
|
|
|
if ($this->ishadowcolor != "" && $aaoption !== 2) { |
526
|
|
|
$accsum = 0; |
527
|
|
|
$angle2 = $this->startangle; |
528
|
|
|
$img->SetColor($this->ishadowcolor); |
529
|
|
|
for ($i = 0; $sum > 0 && $i < $n; ++$i) { |
530
|
|
|
$j = $n - $i - 1; |
531
|
|
|
$d = $this->data[$i]; |
532
|
|
|
$angle1 = $angle2; |
533
|
|
|
$accsum += $d; |
534
|
|
|
$angle2 = $this->startangle + 2 * M_PI * $accsum / $sum; |
535
|
|
|
if (empty($this->explode_radius[$j])) { |
536
|
|
|
$this->explode_radius[$j] = 0; |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
if ($d < 0.00001) { |
540
|
|
|
continue; |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
$la = 2 * M_PI - (abs($angle2 - $angle1) / 2.0 + $angle1); |
544
|
|
|
|
545
|
|
|
$xcm = $xc + $this->explode_radius[$j] * cos($la) * $expscale; |
546
|
|
|
$ycm = $yc - $this->explode_radius[$j] * sin($la) * $expscale; |
547
|
|
|
|
548
|
|
|
$xcm += $this->ishadowdrop * $expscale; |
549
|
|
|
$ycm += $this->ishadowdrop * $expscale; |
550
|
|
|
|
551
|
|
|
$_sa = round($angle1 * 180 / M_PI); |
552
|
|
|
$_ea = round($angle2 * 180 / M_PI); |
553
|
|
|
|
554
|
|
|
// The CakeSlice method draws a full circle in case of start angle = end angle |
555
|
|
|
// for pie slices we don't want this behaviour unless we only have one |
556
|
|
|
// slice in the pie in case it is the wanted behaviour |
557
|
|
|
if ($_ea - $_sa > 0.1 || $n == 1) { |
558
|
|
|
$img->CakeSlice($xcm, $ycm, $radius - 1, $radius - 1, |
559
|
|
|
$angle1 * 180 / M_PI, $angle2 * 180 / M_PI, $this->ishadowcolor); |
560
|
|
|
} |
561
|
|
|
} |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
//-------------------------------------------------------------------------------- |
565
|
|
|
// This is the main loop to draw each cake slice |
566
|
|
|
//-------------------------------------------------------------------------------- |
567
|
|
|
|
568
|
|
|
// Set up the accumulated sum, start angle for first slice and border color |
569
|
|
|
$accsum = 0; |
570
|
|
|
$angle2 = $this->startangle; |
571
|
|
|
$img->SetColor($this->color); |
572
|
|
|
|
573
|
|
|
// Loop though all the slices if there is a pie to draw (sum>0) |
574
|
|
|
// There are n slices in total |
575
|
|
|
for ($i = 0; $sum > 0 && $i < $n; ++$i) { |
576
|
|
|
|
577
|
|
|
// $j is the actual index used for the slice |
578
|
|
|
$j = $n - $i - 1; |
579
|
|
|
|
580
|
|
|
// Make sure we havea valid distance to explode the slice |
581
|
|
|
if (empty($this->explode_radius[$j])) { |
582
|
|
|
$this->explode_radius[$j] = 0; |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
// The actual numeric value for the slice |
586
|
|
|
$d = $this->data[$i]; |
587
|
|
|
|
588
|
|
|
$angle1 = $angle2; |
589
|
|
|
|
590
|
|
|
// Accumlate the sum |
591
|
|
|
$accsum += $d; |
592
|
|
|
|
593
|
|
|
// The new angle when we add the "size" of this slice |
594
|
|
|
// angle1 is then the start and angle2 the end of this slice |
595
|
|
|
$angle2 = $this->NormAngle($this->startangle + 2 * M_PI * $accsum / $sum); |
596
|
|
|
|
597
|
|
|
// We avoid some trouble by not allowing end angle to be 0, in that case |
598
|
|
|
// we translate to 360 |
599
|
|
|
|
600
|
|
|
// la is used to hold the label angle, which is centered on the slice |
601
|
|
|
if ($angle2 < 0.0001 && $angle1 > 0.0001) { |
602
|
|
|
$this->la[$i] = 2 * M_PI - (abs(2 * M_PI - $angle1) / 2.0 + $angle1); |
603
|
|
|
} elseif ($angle1 > $angle2) { |
604
|
|
|
// The case where the slice crosses the 3 a'clock line |
605
|
|
|
// Remember that the slices are counted clockwise and |
606
|
|
|
// labels are counted counter clockwise so we need to revert with 2 PI |
607
|
|
|
$this->la[$i] = 2 * M_PI - $this->NormAngle($angle1 + ((2 * M_PI - $angle1) + $angle2) / 2); |
608
|
|
|
} else { |
609
|
|
|
$this->la[$i] = 2 * M_PI - (abs($angle2 - $angle1) / 2.0 + $angle1); |
610
|
|
|
} |
611
|
|
|
|
612
|
|
|
// Too avoid rounding problems we skip the slice if it is too small |
613
|
|
|
if ($d < 0.00001) { |
614
|
|
|
continue; |
615
|
|
|
} |
616
|
|
|
|
617
|
|
|
// If the user has specified an array of colors for each slice then use |
618
|
|
|
// that a color otherwise use the theme array (ta) of colors |
619
|
|
|
if ($this->setslicecolors == null) { |
620
|
|
|
$slicecolor = $colors[$ta[$i % $numcolors]]; |
621
|
|
|
} else { |
622
|
|
|
$slicecolor = $this->setslicecolors[$i % $numcolors]; |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
// $_sa = round($angle1*180/M_PI); |
|
|
|
|
626
|
|
|
// $_ea = round($angle2*180/M_PI); |
|
|
|
|
627
|
|
|
// $_la = round($this->la[$i]*180/M_PI); |
|
|
|
|
628
|
|
|
// echo "Slice#$i: ang1=$_sa , ang2=$_ea, la=$_la, color=$slicecolor<br>"; |
629
|
|
|
|
630
|
|
|
// If we have enabled antialias then we don't draw any border so |
631
|
|
|
// make the bordedr color the same as the slice color |
632
|
|
|
if ($this->pie_interior_border && $aaoption === 0) { |
633
|
|
|
$img->SetColor($this->color); |
634
|
|
|
} else { |
635
|
|
|
$img->SetColor($slicecolor); |
636
|
|
|
} |
637
|
|
|
$arccolor = $this->pie_border && $aaoption === 0 ? $this->color : ""; |
638
|
|
|
|
639
|
|
|
// Calculate the x,y coordinates for the base of this slice taking |
640
|
|
|
// the exploded distance into account. Here we use the mid angle as the |
641
|
|
|
// ray of extension and we have the mid angle handy as it is also the |
642
|
|
|
// label angle |
643
|
|
|
$xcm = $xc + $this->explode_radius[$j] * cos($this->la[$i]) * $expscale; |
644
|
|
|
$ycm = $yc - $this->explode_radius[$j] * sin($this->la[$i]) * $expscale; |
645
|
|
|
|
646
|
|
|
// If we are not just drawing the labels then draw this cake slice |
647
|
|
|
if ($aaoption !== 2) { |
648
|
|
|
$_sa = round($angle1 * 180 / M_PI); |
649
|
|
|
$_ea = round($angle2 * 180 / M_PI); |
650
|
|
|
$_la = round($this->la[$i] * 180 / M_PI); |
|
|
|
|
651
|
|
|
//echo "[$i] sa=$_sa, ea=$_ea, la[$i]=$_la, (color=$slicecolor)<br>"; |
652
|
|
|
|
653
|
|
|
// The CakeSlice method draws a full circle in case of start angle = end angle |
654
|
|
|
// for pie slices we want this in case the slice have a value larger than 99% of the |
655
|
|
|
// total sum |
656
|
|
|
if (abs($_ea - $_sa) >= 1 || $d == $sum) { |
657
|
|
|
$img->CakeSlice($xcm, $ycm, $radius - 1, $radius - 1, $_sa, $_ea, $slicecolor, $arccolor); |
658
|
|
|
} |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
// If the CSIM is used then make sure we register a CSIM area for this slice as well |
662
|
|
|
if ($this->csimtargets && $aaoption !== 1) { |
|
|
|
|
663
|
|
|
$this->AddSliceToCSIM($i, $xcm, $ycm, $radius, $angle1, $angle2); |
664
|
|
|
} |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
// Format the titles for each slice |
668
|
|
|
if ($aaoption !== 2) { |
669
|
|
|
for ($i = 0; $i < $n; ++$i) { |
670
|
|
|
if ($this->labeltype == 0) { |
671
|
|
View Code Duplication |
if ($sum != 0) { |
|
|
|
|
672
|
|
|
$l = 100.0 * $this->data[$i] / $sum; |
673
|
|
|
} else { |
674
|
|
|
$l = 0.0; |
675
|
|
|
} |
676
|
|
|
} elseif ($this->labeltype == 1) { |
677
|
|
|
$l = $this->data[$i] * 1.0; |
678
|
|
|
} else { |
679
|
|
|
$l = $this->adjusted_data[$i]; |
680
|
|
|
} |
681
|
|
|
if (isset($this->labels[$i]) && is_string($this->labels[$i])) { |
682
|
|
|
$this->labels[$i] = sprintf($this->labels[$i], $l); |
683
|
|
|
} else { |
684
|
|
|
$this->labels[$i] = $l; |
685
|
|
|
} |
686
|
|
|
} |
687
|
|
|
} |
688
|
|
|
|
689
|
|
|
if ($this->value->show && $aaoption !== 1) { |
690
|
|
|
$this->StrokeAllLabels($img, $xc, $yc, $radius); |
691
|
|
|
} |
692
|
|
|
|
693
|
|
|
// Adjust title position |
694
|
|
View Code Duplication |
if ($aaoption !== 1) { |
|
|
|
|
695
|
|
|
$this->title->SetPos($xc, |
696
|
|
|
$yc - $this->title->GetFontHeight($img) - $radius - $this->title->margin, |
|
|
|
|
697
|
|
|
"center", "bottom"); |
698
|
|
|
$this->title->Stroke($img); |
699
|
|
|
} |
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
//--------------- |
703
|
|
|
// PRIVATE METHODS |
704
|
|
|
|
705
|
|
|
public function NormAngle($a) |
706
|
|
|
{ |
707
|
|
|
while ($a < 0) { |
708
|
|
|
$a += 2 * M_PI; |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
while ($a > 2 * M_PI) { |
712
|
|
|
$a -= 2 * M_PI; |
713
|
|
|
} |
714
|
|
|
|
715
|
|
|
return $a; |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
public function Quadrant($a) |
719
|
|
|
{ |
720
|
|
|
$a = $this->NormAngle($a); |
721
|
|
|
if ($a > 0 && $a <= M_PI / 2) { |
722
|
|
|
return 0; |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
if ($a > M_PI / 2 && $a <= M_PI) { |
726
|
|
|
return 1; |
727
|
|
|
} |
728
|
|
|
|
729
|
|
|
if ($a > M_PI && $a <= 1.5 * M_PI) { |
730
|
|
|
return 2; |
731
|
|
|
} |
732
|
|
|
|
733
|
|
|
if ($a > 1.5 * M_PI) { |
734
|
|
|
return 3; |
735
|
|
|
} |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
public function StrokeGuideLabels($img, $xc, $yc, $radius) |
739
|
|
|
{ |
740
|
|
|
$n = count($this->labels); |
741
|
|
|
|
742
|
|
|
//----------------------------------------------------------------------- |
743
|
|
|
// Step 1 of the algorithm is to construct a number of clusters |
744
|
|
|
// a cluster is defined as all slices within the same quadrant (almost) |
745
|
|
|
// that has an angular distance less than the treshold |
746
|
|
|
//----------------------------------------------------------------------- |
747
|
|
|
$tresh_hold = 25 * M_PI / 180; // 25 degrees difference to be in a cluster |
748
|
|
|
$incluster = false; // flag if we are currently in a cluster or not |
749
|
|
|
$clusters = array(); // array of clusters |
750
|
|
|
$cidx = -1; // running cluster index |
751
|
|
|
|
752
|
|
|
// Go through all the labels and construct a number of clusters |
753
|
|
|
for ($i = 0; $i < $n - 1; ++$i) { |
754
|
|
|
// Calc the angle distance between two consecutive slices |
755
|
|
|
$a1 = $this->la[$i]; |
756
|
|
|
$a2 = $this->la[$i + 1]; |
757
|
|
|
$q1 = $this->Quadrant($a1); |
758
|
|
|
$q2 = $this->Quadrant($a2); |
759
|
|
|
$diff = abs($a1 - $a2); |
760
|
|
|
if ($diff < $tresh_hold) { |
761
|
|
|
if ($incluster) { |
762
|
|
|
$clusters[$cidx][1]++; |
763
|
|
|
// Each cluster can only cover one quadrant |
764
|
|
|
// Do we cross a quadrant ( and must break the cluster) |
765
|
|
|
if ($q1 != $q2) { |
766
|
|
|
// If we cross a quadrant boundary we normally start a |
767
|
|
|
// new cluster. However we need to take the 12'a clock |
768
|
|
|
// and 6'a clock positions into a special consideration. |
769
|
|
|
// Case 1: WE go from q=1 to q=2 if the last slice on |
770
|
|
|
// the cluster for q=1 is close to 12'a clock and the |
771
|
|
|
// first slice in q=0 is small we extend the previous |
772
|
|
|
// cluster |
773
|
|
|
if ($q1 == 1 && $q2 == 0 && $a2 > (90 - 15) * M_PI / 180) { |
|
|
|
|
774
|
|
View Code Duplication |
if ($i < $n - 2) { |
|
|
|
|
775
|
|
|
$a3 = $this->la[$i + 2]; |
776
|
|
|
// If there isn't a cluster coming up with the next-next slice |
777
|
|
|
// we extend the previous cluster to cover this slice as well |
778
|
|
|
if (abs($a3 - $a2) >= $tresh_hold) { |
779
|
|
|
$clusters[$cidx][1]++; |
780
|
|
|
$i++; |
781
|
|
|
} |
782
|
|
|
} |
783
|
|
|
} elseif ($q1 == 3 && $q2 == 2 && $a2 > (270 - 15) * M_PI / 180) { |
784
|
|
View Code Duplication |
if ($i < $n - 2) { |
|
|
|
|
785
|
|
|
$a3 = $this->la[$i + 2]; |
786
|
|
|
// If there isn't a cluster coming up with the next-next slice |
787
|
|
|
// we extend the previous cluster to cover this slice as well |
788
|
|
|
if (abs($a3 - $a2) >= $tresh_hold) { |
789
|
|
|
$clusters[$cidx][1]++; |
790
|
|
|
$i++; |
791
|
|
|
} |
792
|
|
|
} |
793
|
|
|
} |
794
|
|
|
|
795
|
|
|
if ($q1 == 2 && $q2 == 1 && $a2 > (180 - 15) * M_PI / 180) { |
796
|
|
|
$clusters[$cidx][1]++; |
797
|
|
|
$i++; |
798
|
|
|
} |
799
|
|
|
|
800
|
|
|
$incluster = false; |
801
|
|
|
} |
802
|
|
|
} elseif ($q1 == $q2) { |
803
|
|
|
$incluster = true; |
804
|
|
|
// Now we have a special case for quadrant 0. If we previously |
805
|
|
|
// have a cluster of one in quadrant 0 we just extend that |
806
|
|
|
// cluster. If we don't do this then we risk that the label |
807
|
|
|
// for the cluster of one will cross the guide-line |
808
|
|
|
if ($q1 == 0 && $cidx > -1 && |
|
|
|
|
809
|
|
|
$clusters[$cidx][1] == 1 && |
810
|
|
|
$this->Quadrant($this->la[$clusters[$cidx][0]]) == 0) { |
|
|
|
|
811
|
|
|
$clusters[$cidx][1]++; |
812
|
|
|
} else { |
813
|
|
|
$cidx++; |
814
|
|
|
$clusters[$cidx][0] = $i; |
815
|
|
|
$clusters[$cidx][1] = 1; |
816
|
|
|
} |
817
|
|
|
} else { |
818
|
|
|
// Create a "cluster" of one since we are just crossing |
819
|
|
|
// a quadrant |
820
|
|
|
$cidx++; |
821
|
|
|
$clusters[$cidx][0] = $i; |
822
|
|
|
$clusters[$cidx][1] = 1; |
823
|
|
|
} |
824
|
|
View Code Duplication |
} else { |
|
|
|
|
825
|
|
|
if ($incluster) { |
826
|
|
|
// Add the last slice |
827
|
|
|
$clusters[$cidx][1]++; |
828
|
|
|
$incluster = false; |
829
|
|
|
} else { |
830
|
|
|
// Create a "cluster" of one |
831
|
|
|
$cidx++; |
832
|
|
|
$clusters[$cidx][0] = $i; |
833
|
|
|
$clusters[$cidx][1] = 1; |
834
|
|
|
} |
835
|
|
|
} |
836
|
|
|
} |
837
|
|
|
// Handle the very last slice |
838
|
|
View Code Duplication |
if ($incluster) { |
|
|
|
|
839
|
|
|
$clusters[$cidx][1]++; |
840
|
|
|
} else { |
841
|
|
|
// Create a "cluster" of one |
842
|
|
|
$cidx++; |
843
|
|
|
$clusters[$cidx][0] = $i; |
844
|
|
|
$clusters[$cidx][1] = 1; |
845
|
|
|
} |
846
|
|
|
|
847
|
|
|
/* |
|
|
|
|
848
|
|
|
if( true ) { |
849
|
|
|
// Debug printout in labels |
850
|
|
|
for( $i=0; $i <= $cidx; ++$i ) { |
851
|
|
|
for( $j=0; $j < $clusters[$i][1]; ++$j ) { |
852
|
|
|
$a = $this->la[$clusters[$i][0]+$j]; |
853
|
|
|
$aa = round($a*180/M_PI); |
854
|
|
|
$q = $this->Quadrant($a); |
855
|
|
|
$this->labels[$clusters[$i][0]+$j]="[$q:$aa] $i:$j"; |
856
|
|
|
} |
857
|
|
|
} |
858
|
|
|
} |
859
|
|
|
*/ |
860
|
|
|
|
861
|
|
|
//----------------------------------------------------------------------- |
862
|
|
|
// Step 2 of the algorithm is use the clusters and draw the labels |
863
|
|
|
// and guidelines |
864
|
|
|
//----------------------------------------------------------------------- |
865
|
|
|
|
866
|
|
|
// We use the font height as the base factor for how far we need to |
867
|
|
|
// spread the labels in the Y-direction. |
868
|
|
|
$this->value->ApplyFont($img); |
869
|
|
|
$fh = $img->GetFontHeight(); |
870
|
|
|
$origvstep = $fh * $this->iGuideVFactor; |
871
|
|
|
$this->value->SetMargin(0); |
872
|
|
|
|
873
|
|
|
// Number of clusters found |
874
|
|
|
$nc = count($clusters); |
875
|
|
|
|
876
|
|
|
// Walk through all the clusters |
877
|
|
|
for ($i = 0; $i < $nc; ++$i) { |
878
|
|
|
|
879
|
|
|
// Start angle and number of slices in this cluster |
880
|
|
|
$csize = $clusters[$i][1]; |
881
|
|
|
$a = $this->la[$clusters[$i][0]]; |
882
|
|
|
$q = $this->Quadrant($a); |
883
|
|
|
|
884
|
|
|
// Now set up the start and end conditions to make sure that |
885
|
|
|
// in each cluster we walk through the all the slices starting with the slice |
886
|
|
|
// closest to the equator. Since all slices are numbered clockwise from "3'a clock" |
887
|
|
|
// we have different conditions depending on in which quadrant the slice lies within. |
888
|
|
|
if ($q == 0) { |
|
|
|
|
889
|
|
|
$start = $csize - 1; |
890
|
|
|
$idx = $start; |
891
|
|
|
$step = -1; |
892
|
|
|
$vstep = -$origvstep; |
893
|
|
View Code Duplication |
} elseif ($q == 1) { |
|
|
|
|
894
|
|
|
$start = 0; |
895
|
|
|
$idx = $start; |
896
|
|
|
$step = 1; |
897
|
|
|
$vstep = -$origvstep; |
898
|
|
|
} elseif ($q == 2) { |
899
|
|
|
$start = $csize - 1; |
900
|
|
|
$idx = $start; |
901
|
|
|
$step = -1; |
902
|
|
|
$vstep = $origvstep; |
903
|
|
View Code Duplication |
} elseif ($q == 3) { |
|
|
|
|
904
|
|
|
$start = 0; |
905
|
|
|
$idx = $start; |
906
|
|
|
$step = 1; |
907
|
|
|
$vstep = $origvstep; |
908
|
|
|
} |
909
|
|
|
|
910
|
|
|
// Walk through all slices within this cluster |
911
|
|
|
for ($j = 0; $j < $csize; ++$j) { |
912
|
|
|
// Now adjust the position of the labels in each cluster starting |
913
|
|
|
// with the slice that is closest to the equator of the pie |
914
|
|
|
$a = $this->la[$clusters[$i][0] + $idx]; |
|
|
|
|
915
|
|
|
|
916
|
|
|
// Guide line start in the center of the arc of the slice |
917
|
|
|
$r = $radius + $this->explode_radius[$n - 1 - ($clusters[$i][0] + $idx)]; |
918
|
|
|
$x = round($r * cos($a) + $xc); |
919
|
|
|
$y = round($yc - $r * sin($a)); |
920
|
|
|
|
921
|
|
|
// The distance from the arc depends on chosen font and the "R-Factor" |
922
|
|
|
$r += $fh * $this->iGuideLineRFactor; |
923
|
|
|
|
924
|
|
|
// Should the labels be placed curved along the pie or in straight columns |
925
|
|
|
// outside the pie? |
926
|
|
|
if ($this->iGuideLineCurve) { |
927
|
|
|
$xt = round($r * cos($a) + $xc); |
928
|
|
|
} |
929
|
|
|
|
930
|
|
|
// If this is the first slice in the cluster we need some first time |
931
|
|
|
// proessing |
932
|
|
|
if ($idx == $start) { |
|
|
|
|
933
|
|
|
if (!$this->iGuideLineCurve) { |
934
|
|
|
$xt = round($r * cos($a) + $xc); |
935
|
|
|
} |
936
|
|
|
|
937
|
|
|
$yt = round($yc - $r * sin($a)); |
938
|
|
|
|
939
|
|
|
// Some special consideration in case this cluster starts |
940
|
|
|
// in quadrant 1 or 3 very close to the "equator" (< 20 degrees) |
941
|
|
|
// and the previous clusters last slice is within the tolerance. |
942
|
|
|
// In that case we add a font height to this labels Y-position |
943
|
|
|
// so it doesn't collide with |
944
|
|
|
// the slice in the previous cluster |
945
|
|
|
$prevcluster = ($i + ($nc - 1)) % $nc; |
946
|
|
|
$previdx = $clusters[$prevcluster][0] + $clusters[$prevcluster][1] - 1; |
947
|
|
|
if ($q == 1 && $a > 160 * M_PI / 180) { |
948
|
|
|
// Get the angle for the previous clusters last slice |
949
|
|
|
$diff = abs($a - $this->la[$previdx]); |
950
|
|
|
if ($diff < $tresh_hold) { |
951
|
|
|
$yt -= $fh; |
952
|
|
|
} |
953
|
|
|
} elseif ($q == 3 && $a > 340 * M_PI / 180) { |
954
|
|
|
// We need to subtract 360 to compare angle distance between |
955
|
|
|
// q=0 and q=3 |
956
|
|
|
$diff = abs($a - $this->la[$previdx] - 360 * M_PI / 180); |
957
|
|
|
if ($diff < $tresh_hold) { |
958
|
|
|
$yt += $fh; |
959
|
|
|
} |
960
|
|
|
} |
961
|
|
|
} else { |
962
|
|
|
// The step is at minimum $vstep but if the slices are relatively large |
963
|
|
|
// we make sure that we add at least a step that corresponds to the vertical |
964
|
|
|
// distance between the centers at the arc on the slice |
965
|
|
|
$prev_a = $this->la[$clusters[$i][0] + ($idx - $step)]; |
|
|
|
|
966
|
|
|
$dy = abs($radius * (sin($a) - sin($prev_a)) * 1.2); |
967
|
|
|
if ($vstep > 0) { |
968
|
|
|
$yt += max($vstep, $dy); |
|
|
|
|
969
|
|
|
} else { |
970
|
|
|
$yt += min($vstep, -$dy); |
971
|
|
|
} |
972
|
|
|
} |
973
|
|
|
|
974
|
|
|
$label = $this->labels[$clusters[$i][0] + $idx]; |
975
|
|
|
|
976
|
|
|
if ($csize == 1) { |
977
|
|
|
// A "meta" cluster with only one slice |
978
|
|
|
$r = $radius + $this->explode_radius[$n - 1 - ($clusters[$i][0] + $idx)]; |
979
|
|
|
$rr = $r + $img->GetFontHeight() / 2; |
980
|
|
|
$xt = round($rr * cos($a) + $xc); |
981
|
|
|
$yt = round($yc - $rr * sin($a)); |
982
|
|
|
$this->StrokeLabel($label, $img, $xc, $yc, $a, $r); |
983
|
|
|
if ($this->iShowGuideLineForSingle) { |
984
|
|
|
$this->guideline->Stroke($img, $x, $y, $xt, $yt); |
985
|
|
|
} |
986
|
|
|
} else { |
987
|
|
|
$this->guideline->Stroke($img, $x, $y, $xt, $yt); |
|
|
|
|
988
|
|
|
if ($q == 1 || $q == 2) { |
989
|
|
|
// Left side of Pie |
990
|
|
|
$this->guideline->Stroke($img, $xt, $yt, $xt - $this->guidelinemargin, $yt); |
991
|
|
|
$lbladj = -$this->guidelinemargin - 5; |
992
|
|
|
$this->value->halign = "right"; |
993
|
|
|
$this->value->valign = "center"; |
994
|
|
|
} else { |
995
|
|
|
// Right side of pie |
996
|
|
|
$this->guideline->Stroke($img, $xt, $yt, $xt + $this->guidelinemargin, $yt); |
997
|
|
|
$lbladj = $this->guidelinemargin + 5; |
998
|
|
|
$this->value->halign = "left"; |
999
|
|
|
$this->value->valign = "center"; |
1000
|
|
|
} |
1001
|
|
|
$this->value->Stroke($img, $label, $xt + $lbladj, $yt); |
1002
|
|
|
} |
1003
|
|
|
|
1004
|
|
|
// Udate idx to point to next slice in the cluster to process |
1005
|
|
|
$idx += $step; |
1006
|
|
|
} |
1007
|
|
|
} |
1008
|
|
|
} |
1009
|
|
|
|
1010
|
|
|
public function StrokeAllLabels($img, $xc, $yc, $radius) |
1011
|
|
|
{ |
1012
|
|
|
// First normalize all angles for labels |
1013
|
|
|
$n = count($this->la); |
1014
|
|
|
for ($i = 0; $i < $n; ++$i) { |
1015
|
|
|
$this->la[$i] = $this->NormAngle($this->la[$i]); |
1016
|
|
|
} |
1017
|
|
|
if ($this->guideline->iShow) { |
1018
|
|
|
$this->StrokeGuideLabels($img, $xc, $yc, $radius); |
1019
|
|
|
} else { |
1020
|
|
|
$n = count($this->labels); |
1021
|
|
|
for ($i = 0; $i < $n; ++$i) { |
1022
|
|
|
$this->StrokeLabel($this->labels[$i], $img, $xc, $yc, |
1023
|
|
|
$this->la[$i], |
1024
|
|
|
$radius + $this->explode_radius[$n - 1 - $i]); |
1025
|
|
|
} |
1026
|
|
|
} |
1027
|
|
|
} |
1028
|
|
|
|
1029
|
|
|
// Position the labels of each slice |
1030
|
|
|
public function StrokeLabel($label, $img, $xc, $yc, $a, $r) |
1031
|
|
|
{ |
1032
|
|
|
|
1033
|
|
|
// Default value |
1034
|
|
|
if ($this->ilabelposadj === 'auto') { |
|
|
|
|
1035
|
|
|
$this->ilabelposadj = 0.65; |
|
|
|
|
1036
|
|
|
} |
1037
|
|
|
|
1038
|
|
|
// We position the values diferently depending on if they are inside |
1039
|
|
|
// or outside the pie |
1040
|
|
|
if ($this->ilabelposadj < 1.0) { |
1041
|
|
|
$this->value->SetAlign('center', 'center'); |
1042
|
|
|
$this->value->margin = 0; |
1043
|
|
|
|
1044
|
|
|
$xt = round($this->ilabelposadj * $r * cos($a) + $xc); |
1045
|
|
|
$yt = round($yc - $this->ilabelposadj * $r * sin($a)); |
1046
|
|
|
|
1047
|
|
|
$this->value->Stroke($img, $label, $xt, $yt); |
1048
|
|
|
} else { |
1049
|
|
|
$this->value->halign = "left"; |
1050
|
|
|
$this->value->valign = "top"; |
1051
|
|
|
$this->value->margin = 0; |
1052
|
|
|
|
1053
|
|
|
// Position the axis title. |
1054
|
|
|
// dx, dy is the offset from the top left corner of the bounding box that sorrounds the text |
1055
|
|
|
// that intersects with the extension of the corresponding axis. The code looks a little |
1056
|
|
|
// bit messy but this is really the only way of having a reasonable position of the |
1057
|
|
|
// axis titles. |
1058
|
|
|
$this->value->ApplyFont($img); |
1059
|
|
|
$h = $img->GetTextHeight($label); |
1060
|
|
|
// For numeric values the format of the display value |
1061
|
|
|
// must be taken into account |
1062
|
|
View Code Duplication |
if (is_numeric($label)) { |
|
|
|
|
1063
|
|
|
if ($label > 0) { |
1064
|
|
|
$w = $img->GetTextWidth(sprintf($this->value->format, $label)); |
1065
|
|
|
} else { |
1066
|
|
|
$w = $img->GetTextWidth(sprintf($this->value->negformat, $label)); |
1067
|
|
|
} |
1068
|
|
|
} else { |
1069
|
|
|
$w = $img->GetTextWidth($label); |
1070
|
|
|
} |
1071
|
|
|
|
1072
|
|
|
if ($this->ilabelposadj > 1.0 && $this->ilabelposadj < 5.0) { |
1073
|
|
|
$r *= $this->ilabelposadj; |
1074
|
|
|
} |
1075
|
|
|
|
1076
|
|
|
$r += $img->GetFontHeight() / 1.5; |
1077
|
|
|
|
1078
|
|
|
$xt = round($r * cos($a) + $xc); |
1079
|
|
|
$yt = round($yc - $r * sin($a)); |
1080
|
|
|
|
1081
|
|
|
// Normalize angle |
1082
|
|
|
while ($a < 0) { |
1083
|
|
|
$a += 2 * M_PI; |
1084
|
|
|
} |
1085
|
|
|
|
1086
|
|
|
while ($a > 2 * M_PI) { |
1087
|
|
|
$a -= 2 * M_PI; |
1088
|
|
|
} |
1089
|
|
|
|
1090
|
|
|
if ($a >= 7 * M_PI / 4 || $a <= M_PI / 4) { |
1091
|
|
|
$dx = 0; |
1092
|
|
|
} |
1093
|
|
|
|
1094
|
|
|
if ($a >= M_PI / 4 && $a <= 3 * M_PI / 4) { |
1095
|
|
|
$dx = ($a - M_PI / 4) * 2 / M_PI; |
1096
|
|
|
} |
1097
|
|
|
|
1098
|
|
|
if ($a >= 3 * M_PI / 4 && $a <= 5 * M_PI / 4) { |
1099
|
|
|
$dx = 1; |
1100
|
|
|
} |
1101
|
|
|
|
1102
|
|
|
if ($a >= 5 * M_PI / 4 && $a <= 7 * M_PI / 4) { |
1103
|
|
|
$dx = (1 - ($a - M_PI * 5 / 4) * 2 / M_PI); |
1104
|
|
|
} |
1105
|
|
|
|
1106
|
|
|
if ($a >= 7 * M_PI / 4) { |
1107
|
|
|
$dy = (($a - M_PI) - 3 * M_PI / 4) * 2 / M_PI; |
1108
|
|
|
} |
1109
|
|
|
|
1110
|
|
|
if ($a <= M_PI / 4) { |
1111
|
|
|
$dy = (1 - $a * 2 / M_PI); |
1112
|
|
|
} |
1113
|
|
|
|
1114
|
|
|
if ($a >= M_PI / 4 && $a <= 3 * M_PI / 4) { |
1115
|
|
|
$dy = 1; |
1116
|
|
|
} |
1117
|
|
|
|
1118
|
|
|
if ($a >= 3 * M_PI / 4 && $a <= 5 * M_PI / 4) { |
1119
|
|
|
$dy = (1 - ($a - 3 * M_PI / 4) * 2 / M_PI); |
1120
|
|
|
} |
1121
|
|
|
|
1122
|
|
|
if ($a >= 5 * M_PI / 4 && $a <= 7 * M_PI / 4) { |
1123
|
|
|
$dy = 0; |
1124
|
|
|
} |
1125
|
|
|
|
1126
|
|
|
$this->value->Stroke($img, $label, $xt - $dx * $w, $yt - $dy * $h); |
|
|
|
|
1127
|
|
|
} |
1128
|
|
|
} |
1129
|
|
|
|
1130
|
|
|
public function UsePlotThemeColors($flag = true) |
1131
|
|
|
{ |
1132
|
|
|
$this->use_plot_theme_colors = $flag; |
1133
|
|
|
} |
1134
|
|
|
} // Class |
1135
|
|
|
|
1136
|
|
|
/* EOF */ |
1137
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.