xref: /plugin/statdisplay/vendor/szymach/c-pchart/src/Chart/Surface.php (revision 03cdebb71906645bc452782ad23f052261c2cb18)
1<?php
2
3namespace CpChart\Chart;
4
5use CpChart\Image;
6
7class Surface
8{
9    /**
10     * @var Image
11     */
12    public $pChartObject;
13
14    /**
15     * @var int
16     */
17    public $GridSizeX;
18
19    /**
20     * @var int
21     */
22    public $GridSizeY;
23
24    /**
25     * @var array
26     */
27    public $Points = [];
28
29    public function __construct(Image $pChartObject)
30    {
31        $this->pChartObject = $pChartObject;
32    }
33
34    /**
35     * Define the grid size and initialise the 2D matrix
36     * @param int $XSize
37     * @param int $YSize
38     */
39    public function setGrid($XSize = 10, $YSize = 10)
40    {
41        for ($X = 0; $X <= $XSize; $X++) {
42            for ($Y = 0; $Y <= $YSize; $Y++) {
43                $this->Points[$X][$Y] = UNKNOWN;
44            }
45        }
46
47        $this->GridSizeX = $XSize;
48        $this->GridSizeY = $YSize;
49    }
50
51    /**
52     * Add a point on the grid
53     * @param int $X
54     * @param int $Y
55     * @param int|float $Value
56     * @param bool $Force
57     * @return null
58     */
59    public function addPoint($X, $Y, $Value, $Force = true)
60    {
61        if ($X < 0 || $X > $this->GridSizeX) {
62            return 0;
63        }
64        if ($Y < 0 || $Y > $this->GridSizeY) {
65            return 0;
66        }
67
68        if ($this->Points[$X][$Y] == UNKNOWN || $Force) {
69            $this->Points[$X][$Y] = $Value;
70        } elseif ($this->Points[$X][$Y] == UNKNOWN) {
71            $this->Points[$X][$Y] = $Value;
72        } else {
73            $this->Points[$X][$Y] = ($this->Points[$X][$Y] + $Value) / 2;
74        }
75    }
76
77    /**
78     * Write the X labels
79     * @param array $Format
80     * @return null|int
81     */
82    public function writeXLabels(array $Format = [])
83    {
84        $R = isset($Format["R"]) ? $Format["R"] : $this->pChartObject->FontColorR;
85        $G = isset($Format["G"]) ? $Format["G"] : $this->pChartObject->FontColorG;
86        $B = isset($Format["B"]) ? $Format["B"] : $this->pChartObject->FontColorB;
87        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : $this->pChartObject->FontColorA;
88        $Angle = isset($Format["Angle"]) ? $Format["Angle"] : 0;
89        $Padding = isset($Format["Padding"]) ? $Format["Padding"] : 5;
90        $Position = isset($Format["Position"]) ? $Format["Position"] : LABEL_POSITION_TOP;
91        $Labels = isset($Format["Labels"]) ? $Format["Labels"] : null;
92        $CountOffset = isset($Format["CountOffset"]) ? $Format["CountOffset"] : 0;
93
94        if ($Labels != null && !is_array($Labels)) {
95            $Label = $Labels;
96            $Labels = [$Label];
97        }
98
99        $X0 = $this->pChartObject->GraphAreaX1;
100        $XSize = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1) / ($this->GridSizeX + 1);
101
102        $Settings = ["Angle" => $Angle, "R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
103        if ($Position == LABEL_POSITION_TOP) {
104            $YPos = $this->pChartObject->GraphAreaY1 - $Padding;
105            if ($Angle == 0) {
106                $Settings["Align"] = TEXT_ALIGN_BOTTOMMIDDLE;
107            }
108            if ($Angle != 0) {
109                $Settings["Align"] = TEXT_ALIGN_MIDDLELEFT;
110            }
111        } elseif ($Position == LABEL_POSITION_BOTTOM) {
112            $YPos = $this->pChartObject->GraphAreaY2 + $Padding;
113            if ($Angle == 0) {
114                $Settings["Align"] = TEXT_ALIGN_TOPMIDDLE;
115            }
116            if ($Angle != 0) {
117                $Settings["Align"] = TEXT_ALIGN_MIDDLERIGHT;
118            }
119        } else {
120            return -1;
121        }
122        for ($X = 0; $X <= $this->GridSizeX; $X++) {
123            $XPos = floor($X0 + $X * $XSize + $XSize / 2);
124
125            if ($Labels == null || !isset($Labels[$X])) {
126                $Value = $X + $CountOffset;
127            } else {
128                $Value = $Labels[$X];
129            }
130            $this->pChartObject->drawText($XPos, $YPos, $Value, $Settings);
131        }
132    }
133
134    /**
135     * Write the Y labels
136     * @param array $Format
137     * @return type
138     */
139    public function writeYLabels(array $Format = [])
140    {
141        $R = isset($Format["R"]) ? $Format["R"] : $this->pChartObject->FontColorR;
142        $G = isset($Format["G"]) ? $Format["G"] : $this->pChartObject->FontColorG;
143        $B = isset($Format["B"]) ? $Format["B"] : $this->pChartObject->FontColorB;
144        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : $this->pChartObject->FontColorA;
145        $Angle = isset($Format["Angle"]) ? $Format["Angle"] : 0;
146        $Padding = isset($Format["Padding"]) ? $Format["Padding"] : 5;
147        $Position = isset($Format["Position"]) ? $Format["Position"] : LABEL_POSITION_LEFT;
148        $Labels = isset($Format["Labels"]) ? $Format["Labels"] : null;
149        $CountOffset = isset($Format["CountOffset"]) ? $Format["CountOffset"] : 0;
150
151        if ($Labels != null && !is_array($Labels)) {
152            $Label = $Labels;
153            $Labels = [$Label];
154        }
155
156        $Y0 = $this->pChartObject->GraphAreaY1;
157        $YSize = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1) / ($this->GridSizeY + 1);
158
159        $Settings = ["Angle" => $Angle, "R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
160        if ($Position == LABEL_POSITION_LEFT) {
161            $XPos = $this->pChartObject->GraphAreaX1 - $Padding;
162            $Settings["Align"] = TEXT_ALIGN_MIDDLERIGHT;
163        } elseif ($Position == LABEL_POSITION_RIGHT) {
164            $XPos = $this->pChartObject->GraphAreaX2 + $Padding;
165            $Settings["Align"] = TEXT_ALIGN_MIDDLELEFT;
166        } else {
167            return -1;
168        }
169        for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
170            $YPos = floor($Y0 + $Y * $YSize + $YSize / 2);
171
172            if ($Labels == null || !isset($Labels[$Y])) {
173                $Value = $Y + $CountOffset;
174            } else {
175                $Value = $Labels[$Y];
176            }
177            $this->pChartObject->drawText($XPos, $YPos, $Value, $Settings);
178        }
179    }
180
181    /**
182     * Draw the area arround the specified Threshold
183     * @param int|float $Threshold
184     * @param array $Format
185     */
186    public function drawContour($Threshold, array $Format = [])
187    {
188        $R = isset($Format["R"]) ? $Format["R"] : 0;
189        $G = isset($Format["G"]) ? $Format["G"] : 0;
190        $B = isset($Format["B"]) ? $Format["B"] : 0;
191        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
192        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : 3;
193        $Padding = isset($Format["Padding"]) ? $Format["Padding"] : 0;
194
195        $X0 = $this->pChartObject->GraphAreaX1;
196        $Y0 = $this->pChartObject->GraphAreaY1;
197        $XSize = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1) / ($this->GridSizeX + 1);
198        $YSize = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1) / ($this->GridSizeY + 1);
199
200        $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks];
201
202        for ($X = 0; $X <= $this->GridSizeX; $X++) {
203            for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
204                $Value = $this->Points[$X][$Y];
205
206                if ($Value != UNKNOWN && $Value != IGNORED && $Value >= $Threshold) {
207                    $X1 = floor($X0 + $X * $XSize) + $Padding;
208                    $Y1 = floor($Y0 + $Y * $YSize) + $Padding;
209                    $X2 = floor($X0 + $X * $XSize + $XSize);
210                    $Y2 = floor($Y0 + $Y * $YSize + $YSize);
211
212                    if (
213                        $X > 0 && $this->Points[$X - 1][$Y] != UNKNOWN
214                        && $this->Points[$X - 1][$Y] != IGNORED
215                        && $this->Points[$X - 1][$Y] < $Threshold
216                    ) {
217                        $this->pChartObject->drawLine($X1, $Y1, $X1, $Y2, $Color);
218                    }
219                    if (
220                        $Y > 0 && $this->Points[$X][$Y - 1] != UNKNOWN
221                        && $this->Points[$X][$Y - 1] != IGNORED
222                        && $this->Points[$X][$Y - 1] < $Threshold
223                    ) {
224                        $this->pChartObject->drawLine($X1, $Y1, $X2, $Y1, $Color);
225                    }
226                    if (
227                        $X < $this->GridSizeX
228                        && $this->Points[$X + 1][$Y] != UNKNOWN
229                        && $this->Points[$X + 1][$Y] != IGNORED
230                        && $this->Points[$X + 1][$Y] < $Threshold
231                    ) {
232                        $this->pChartObject->drawLine($X2, $Y1, $X2, $Y2, $Color);
233                    }
234                    if (
235                        $Y < $this->GridSizeY
236                        && $this->Points[$X][$Y + 1] != UNKNOWN
237                        && $this->Points[$X][$Y + 1] != IGNORED
238                        && $this->Points[$X][$Y + 1] < $Threshold
239                    ) {
240                        $this->pChartObject->drawLine($X1, $Y2, $X2, $Y2, $Color);
241                    }
242                }
243            }
244        }
245    }
246
247    /**
248     * Draw the surface chart
249     * @param array $Format
250     */
251    public function drawSurface(array $Format = [])
252    {
253        $Palette = isset($Format["Palette"]) ? $Format["Palette"] : null;
254        $ShadeR1 = isset($Format["ShadeR1"]) ? $Format["ShadeR1"] : 77;
255        $ShadeG1 = isset($Format["ShadeG1"]) ? $Format["ShadeG1"] : 205;
256        $ShadeB1 = isset($Format["ShadeB1"]) ? $Format["ShadeB1"] : 21;
257        $ShadeA1 = isset($Format["ShadeA1"]) ? $Format["ShadeA1"] : 40;
258        $ShadeR2 = isset($Format["ShadeR2"]) ? $Format["ShadeR2"] : 227;
259        $ShadeG2 = isset($Format["ShadeG2"]) ? $Format["ShadeG2"] : 135;
260        $ShadeB2 = isset($Format["ShadeB2"]) ? $Format["ShadeB2"] : 61;
261        $ShadeA2 = isset($Format["ShadeA2"]) ? $Format["ShadeA2"] : 100;
262        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
263        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 0;
264        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 0;
265        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 0;
266        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : -1;
267        $Padding = isset($Format["Padding"]) ? $Format["Padding"] : 1;
268
269        $X0 = $this->pChartObject->GraphAreaX1;
270        $Y0 = $this->pChartObject->GraphAreaY1;
271        $XSize = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1) / ($this->GridSizeX + 1);
272        $YSize = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1) / ($this->GridSizeY + 1);
273
274        for ($X = 0; $X <= $this->GridSizeX; $X++) {
275            for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
276                $Value = $this->Points[$X][$Y];
277
278                if ($Value != UNKNOWN && $Value != IGNORED) {
279                    $X1 = floor($X0 + $X * $XSize) + $Padding;
280                    $Y1 = floor($Y0 + $Y * $YSize) + $Padding;
281                    $X2 = floor($X0 + $X * $XSize + $XSize);
282                    $Y2 = floor($Y0 + $Y * $YSize + $YSize);
283
284                    if ($Palette != null) {
285                        if (isset($Palette[$Value]) && isset($Palette[$Value]["R"])) {
286                            $R = $Palette[$Value]["R"];
287                        } else {
288                            $R = 0;
289                        }
290
291                        if (isset($Palette[$Value]) && isset($Palette[$Value]["G"])) {
292                            $G = $Palette[$Value]["G"];
293                        } else {
294                            $G = 0;
295                        }
296
297                        if (isset($Palette[$Value]) && isset($Palette[$Value]["B"])) {
298                            $B = $Palette[$Value]["B"];
299                        } else {
300                            $B = 0;
301                        }
302                        if (isset($Palette[$Value]) && isset($Palette[$Value]["Alpha"])) {
303                            $Alpha = $Palette[$Value]["Alpha"];
304                        } else {
305                            $Alpha = 1000;
306                        }
307                    } else {
308                        $R = (($ShadeR2 - $ShadeR1) / 100) * $Value + $ShadeR1;
309                        $G = (($ShadeG2 - $ShadeG1) / 100) * $Value + $ShadeG1;
310                        $B = (($ShadeB2 - $ShadeB1) / 100) * $Value + $ShadeB1;
311                        $Alpha = (($ShadeA2 - $ShadeA1) / 100) * $Value + $ShadeA1;
312                    }
313
314                    $Settings = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
315                    if ($Border) {
316                        $Settings["BorderR"] = $BorderR;
317                        $Settings["BorderG"] = $BorderG;
318                        $Settings["BorderB"] = $BorderB;
319                    }
320                    if ($Surrounding != -1) {
321                        $Settings["BorderR"] = $R + $Surrounding;
322                        $Settings["BorderG"] = $G + $Surrounding;
323                        $Settings["BorderB"] = $B + $Surrounding;
324                    }
325
326                    $this->pChartObject->drawFilledRectangle($X1, $Y1, $X2 - 1, $Y2 - 1, $Settings);
327                }
328            }
329        }
330    }
331
332    /**
333     * Compute the missing points
334     */
335    public function computeMissing()
336    {
337        $Missing = [];
338        for ($X = 0; $X <= $this->GridSizeX; $X++) {
339            for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
340                if ($this->Points[$X][$Y] == UNKNOWN) {
341                    $Missing[] = $X . "," . $Y;
342                }
343            }
344        }
345        shuffle($Missing);
346
347        foreach ($Missing as $Pos) {
348            $Pos = preg_split("/,/", $Pos);
349            $X = $Pos[0];
350            $Y = $Pos[1];
351
352            if ($this->Points[$X][$Y] == UNKNOWN) {
353                $NearestNeighbor = $this->getNearestNeighbor($X, $Y);
354
355                $Value = 0;
356                $Points = 0;
357                for ($Xi = $X - $NearestNeighbor; $Xi <= $X + $NearestNeighbor; $Xi++) {
358                    for ($Yi = $Y - $NearestNeighbor; $Yi <= $Y + $NearestNeighbor; $Yi++) {
359                        if (
360                            $Xi >= 0
361                            && $Yi >= 0
362                            && $Xi <= $this->GridSizeX
363                            && $Yi <= $this->GridSizeY
364                            && $this->Points[$Xi][$Yi] != UNKNOWN
365                            && $this->Points[$Xi][$Yi] != IGNORED
366                        ) {
367                            $Value = $Value + $this->Points[$Xi][$Yi];
368                            $Points++;
369                        }
370                    }
371                }
372
373                if ($Points != 0) {
374                    $this->Points[$X][$Y] = $Value / $Points;
375                }
376            }
377        }
378    }
379
380    /**
381     * Return the nearest Neighbor distance of a point
382     * @param int $Xp
383     * @param int $Yp
384     * @return int
385     */
386    public function getNearestNeighbor($Xp, $Yp)
387    {
388        $Nearest = UNKNOWN;
389        for ($X = 0; $X <= $this->GridSizeX; $X++) {
390            for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
391                if ($this->Points[$X][$Y] != UNKNOWN && $this->Points[$X][$Y] != IGNORED) {
392                    $DistanceX = max($Xp, $X) - min($Xp, $X);
393                    $DistanceY = max($Yp, $Y) - min($Yp, $Y);
394                    $Distance = max($DistanceX, $DistanceY);
395                    if ($Distance < $Nearest || $Nearest == UNKNOWN) {
396                        $Nearest = $Distance;
397                    }
398                }
399            }
400        }
401        return $Nearest;
402    }
403}
404