1<?php
2
3namespace CpChart\Chart;
4
5use CpChart\Data;
6use CpChart\Image;
7
8/**
9 *  Bubble - class to draw bubble charts
10 *
11 *  Version     : 2.1.4
12 *  Made by     : Jean-Damien POGOLOTTI
13 *  Last Update : 19/01/2014
14 *
15 *  This file can be distributed under the license you can find at :
16 *
17 *  http://www.pchart.net/license
18 *
19 *  You can find the whole class documentation on the pChart web site.
20 */
21class Bubble
22{
23    /**
24     * @var Image
25     */
26    public $pChartObject;
27
28    /**
29     * @var Data
30     */
31    public $pDataObject;
32
33    /**
34     * @param Image $pChartObject
35     * @param Data $pDataObject
36     */
37    public function __construct(Image $pChartObject, Data $pDataObject)
38    {
39        $this->pChartObject = $pChartObject;
40        $this->pDataObject = $pDataObject;
41    }
42
43    /**
44     * Prepare the scale
45     *
46     * @param mixed $DataSeries
47     * @param mixed $WeightSeries
48     */
49    public function bubbleScale($DataSeries, $WeightSeries)
50    {
51        if (!is_array($DataSeries)) {
52            $DataSeries = [$DataSeries];
53        }
54        if (!is_array($WeightSeries)) {
55            $WeightSeries = [$WeightSeries];
56        }
57
58        /* Parse each data series to find the new min & max boundaries to scale */
59        $NewPositiveSerie = [];
60        $NewNegativeSerie = [];
61        $MaxValues = 0;
62        $LastPositive = 0;
63        $LastNegative = 0;
64
65        foreach ($DataSeries as $Key => $SerieName) {
66            $SerieWeightName = $WeightSeries[$Key];
67
68            $this->pDataObject->setSerieDrawable($SerieWeightName, false);
69            $serieData = $this->pDataObject->Data["Series"][$SerieName]["Data"];
70            if (count($serieData) > $MaxValues) {
71                $MaxValues = count($serieData);
72            }
73
74            foreach ($serieData as $Key => $Value) {
75                if ($Value >= 0) {
76                    $BubbleBounds = $Value + $this->pDataObject->Data["Series"][$SerieWeightName]["Data"][$Key];
77
78                    if (!isset($NewPositiveSerie[$Key])) {
79                        $NewPositiveSerie[$Key] = $BubbleBounds;
80                    } elseif ($NewPositiveSerie[$Key] < $BubbleBounds) {
81                        $NewPositiveSerie[$Key] = $BubbleBounds;
82                    }
83                    $LastPositive = $BubbleBounds;
84                } else {
85                    $BubbleBounds = $Value - $this->pDataObject->Data["Series"][$SerieWeightName]["Data"][$Key];
86
87                    if (!isset($NewNegativeSerie[$Key])) {
88                        $NewNegativeSerie[$Key] = $BubbleBounds;
89                    } elseif ($NewNegativeSerie[$Key] > $BubbleBounds) {
90                        $NewNegativeSerie[$Key] = $BubbleBounds;
91                    }
92                    $LastNegative = $BubbleBounds;
93                }
94            }
95        }
96
97        /* Check for missing values and all the fake positive serie */
98        if (count($NewPositiveSerie)) {
99            for ($i = 0; $i < $MaxValues; $i++) {
100                if (!isset($NewPositiveSerie[$i])) {
101                    $NewPositiveSerie[$i] = $LastPositive;
102                }
103            }
104            $this->pDataObject->addPoints($NewPositiveSerie, "BubbleFakePositiveSerie");
105        }
106
107        /* Check for missing values and all the fake negative serie */
108        if (count($NewNegativeSerie)) {
109            for ($i = 0; $i < $MaxValues; $i++) {
110                if (!isset($NewNegativeSerie[$i])) {
111                    $NewNegativeSerie[$i] = $LastNegative;
112                }
113            }
114
115            $this->pDataObject->addPoints($NewNegativeSerie, "BubbleFakeNegativeSerie");
116        }
117    }
118
119    public function resetSeriesColors()
120    {
121        $Data = $this->pDataObject->getData();
122        $Palette = $this->pDataObject->getPalette();
123
124        $ID = 0;
125        foreach ($Data["Series"] as $SerieName => $SeriesParameters) {
126            if ($SeriesParameters["isDrawable"]) {
127                $this->pDataObject->Data["Series"][$SerieName]["Color"]["R"] = $Palette[$ID]["R"];
128                $this->pDataObject->Data["Series"][$SerieName]["Color"]["G"] = $Palette[$ID]["G"];
129                $this->pDataObject->Data["Series"][$SerieName]["Color"]["B"] = $Palette[$ID]["B"];
130                $this->pDataObject->Data["Series"][$SerieName]["Color"]["Alpha"] = $Palette[$ID]["Alpha"];
131                $ID++;
132            }
133        }
134    }
135
136    /* Prepare the scale */
137
138    public function drawBubbleChart($DataSeries, $WeightSeries, $Format = "")
139    {
140        $ForceAlpha = isset($Format["ForceAlpha"]) ? $Format["ForceAlpha"] : VOID;
141        $DrawBorder = isset($Format["DrawBorder"]) ? $Format["DrawBorder"] : true;
142        $BorderWidth = isset($Format["BorderWidth"]) ? $Format["BorderWidth"] : 1;
143        $Shape = isset($Format["Shape"]) ? $Format["Shape"] : BUBBLE_SHAPE_ROUND;
144        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
145        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 0;
146        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 0;
147        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 0;
148        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : 30;
149        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
150
151        if (!is_array($DataSeries)) {
152            $DataSeries = [$DataSeries];
153        }
154        if (!is_array($WeightSeries)) {
155            $WeightSeries = [$WeightSeries];
156        }
157
158        $Data = $this->pDataObject->getData();
159        $Palette = $this->pDataObject->getPalette();
160
161        if (isset($Data["Series"]["BubbleFakePositiveSerie"])) {
162            $this->pDataObject->setSerieDrawable("BubbleFakePositiveSerie", false);
163        }
164        if (isset($Data["Series"]["BubbleFakeNegativeSerie"])) {
165            $this->pDataObject->setSerieDrawable("BubbleFakeNegativeSerie", false);
166        }
167
168        $this->resetSeriesColors();
169
170        list($XMargin, $XDivs) = $this->pChartObject->scaleGetXSettings();
171
172        foreach ($DataSeries as $Key => $SerieName) {
173            $AxisID = $Data["Series"][$SerieName]["Axis"];
174            $Mode = $Data["Axis"][$AxisID]["Display"];
175            $Format = $Data["Axis"][$AxisID]["Format"];
176            $Unit = $Data["Axis"][$AxisID]["Unit"];
177
178            if (isset($Data["Series"][$SerieName]["Description"])) {
179                $SerieDescription = $Data["Series"][$SerieName]["Description"];
180            } else {
181                $SerieDescription = $SerieName;
182            }
183
184            $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2) / $XDivs;
185
186            $X = $this->pChartObject->GraphAreaX1 + $XMargin;
187            $Y = $this->pChartObject->GraphAreaY1 + $XMargin;
188
189            $Color = [
190                "R" => $Palette[$Key]["R"],
191                "G" => $Palette[$Key]["G"],
192                "B" => $Palette[$Key]["B"],
193                "Alpha" => $Palette[$Key]["Alpha"]
194            ];
195
196            if ($ForceAlpha != VOID) {
197                $Color["Alpha"] = $ForceAlpha;
198            }
199
200            if ($DrawBorder) {
201                if ($BorderWidth != 1) {
202                    if ($Surrounding != null) {
203                        $BorderR = $Palette[$Key]["R"] + $Surrounding;
204                        $BorderG = $Palette[$Key]["G"] + $Surrounding;
205                        $BorderB = $Palette[$Key]["B"] + $Surrounding;
206                    }
207                    if ($ForceAlpha != VOID) {
208                        $BorderAlpha = $ForceAlpha / 2;
209                    }
210                    $BorderColor = [
211                        "R" => $BorderR,
212                        "G" => $BorderG,
213                        "B" => $BorderB,
214                        "Alpha" => $BorderAlpha
215                    ];
216                } else {
217                    $Color["BorderAlpha"] = $BorderAlpha;
218
219                    if ($Surrounding != null) {
220                        $Color["BorderR"] = $Palette[$Key]["R"] + $Surrounding;
221                        $Color["BorderG"] = $Palette[$Key]["G"] + $Surrounding;
222                        $Color["BorderB"] = $Palette[$Key]["B"] + $Surrounding;
223                    } else {
224                        $Color["BorderR"] = $BorderR;
225                        $Color["BorderG"] = $BorderG;
226                        $Color["BorderB"] = $BorderB;
227                    }
228                    if ($ForceAlpha != VOID) {
229                        $Color["BorderAlpha"] = $ForceAlpha / 2;
230                    }
231                }
232            }
233
234            foreach ($Data["Series"][$SerieName]["Data"] as $iKey => $Point) {
235                $Weight = $Point + $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey];
236
237                $PosArray = $this->pChartObject->scaleComputeY(
238                    $Point,
239                    ["AxisID" => $AxisID]
240                );
241                $WeightArray = $this->pChartObject->scaleComputeY(
242                    $Weight,
243                    ["AxisID" => $AxisID]
244                );
245
246                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
247                    if ($XDivs == 0) {
248                        $XStep = 0;
249                    } else {
250                        $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2)
251                            / $XDivs
252                        ;
253                    }
254                    $Y = floor($PosArray);
255                    $CircleRadius = floor(abs($PosArray - $WeightArray) / 2);
256
257                    if ($Shape == BUBBLE_SHAPE_SQUARE) {
258                        if ($RecordImageMap) {
259                            $this->pChartObject->addToImageMap(
260                                "RECT",
261                                (
262                                    floor($X - $CircleRadius)
263                                    . "," . floor($Y - $CircleRadius)
264                                    . "," . floor($X + $CircleRadius)
265                                    . "," . floor($Y + $CircleRadius)
266                                ),
267                                $this->pChartObject->toHTMLColor(
268                                    $Palette[$Key]["R"],
269                                    $Palette[$Key]["G"],
270                                    $Palette[$Key]["B"]
271                                ),
272                                $SerieDescription,
273                                $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey]
274                            );
275                        }
276
277                        if ($BorderWidth != 1) {
278                            $this->pChartObject->drawFilledRectangle(
279                                $X - $CircleRadius - $BorderWidth,
280                                $Y - $CircleRadius - $BorderWidth,
281                                $X + $CircleRadius + $BorderWidth,
282                                $Y + $CircleRadius + $BorderWidth,
283                                $BorderColor
284                            );
285                            $this->pChartObject->drawFilledRectangle(
286                                $X - $CircleRadius,
287                                $Y - $CircleRadius,
288                                $X + $CircleRadius,
289                                $Y + $CircleRadius,
290                                $Color
291                            );
292                        } else {
293                            $this->pChartObject->drawFilledRectangle(
294                                $X - $CircleRadius,
295                                $Y - $CircleRadius,
296                                $X + $CircleRadius,
297                                $Y + $CircleRadius,
298                                $Color
299                            );
300                        }
301                    } elseif ($Shape == BUBBLE_SHAPE_ROUND) {
302                        if ($RecordImageMap) {
303                            $this->pChartObject->addToImageMap(
304                                "CIRCLE",
305                                floor($X) . "," . floor($Y) . "," . floor($CircleRadius),
306                                $this->pChartObject->toHTMLColor(
307                                    $Palette[$Key]["R"],
308                                    $Palette[$Key]["G"],
309                                    $Palette[$Key]["B"]
310                                ),
311                                $SerieDescription,
312                                $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey]
313                            );
314                        }
315
316                        if ($BorderWidth != 1) {
317                            $this->pChartObject->drawFilledCircle(
318                                $X,
319                                $Y,
320                                $CircleRadius + $BorderWidth,
321                                $BorderColor
322                            );
323                            $this->pChartObject->drawFilledCircle($X, $Y, $CircleRadius, $Color);
324                        } else {
325                            $this->pChartObject->drawFilledCircle($X, $Y, $CircleRadius, $Color);
326                        }
327                    }
328
329                    $X = $X + $XStep;
330                } elseif ($Data["Orientation"] == SCALE_POS_TOPBOTTOM) {
331                    if ($XDivs == 0) {
332                        $XStep = 0;
333                    } else {
334                        $XStep = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1 - $XMargin * 2)
335                            / $XDivs
336                        ;
337                    }
338                    $X = floor($PosArray);
339                    $CircleRadius = floor(abs($PosArray - $WeightArray) / 2);
340
341                    if ($Shape == BUBBLE_SHAPE_SQUARE) {
342                        if ($RecordImageMap) {
343                            $this->pChartObject->addToImageMap(
344                                "RECT",
345                                (
346                                    floor($X - $CircleRadius) . ","
347                                    . floor($Y - $CircleRadius) . ","
348                                    . floor($X + $CircleRadius) . ","
349                                    . floor($Y + $CircleRadius)
350                                ),
351                                $this->pChartObject->toHTMLColor(
352                                    $Palette[$Key]["R"],
353                                    $Palette[$Key]["G"],
354                                    $Palette[$Key]["B"]
355                                ),
356                                $SerieDescription,
357                                $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey]
358                            );
359                        }
360
361                        if ($BorderWidth != 1) {
362                            $this->pChartObject->drawFilledRectangle(
363                                $X - $CircleRadius - $BorderWidth,
364                                $Y - $CircleRadius - $BorderWidth,
365                                $X + $CircleRadius + $BorderWidth,
366                                $Y + $CircleRadius + $BorderWidth,
367                                $BorderColor
368                            );
369                            $this->pChartObject->drawFilledRectangle(
370                                $X - $CircleRadius,
371                                $Y - $CircleRadius,
372                                $X + $CircleRadius,
373                                $Y + $CircleRadius,
374                                $Color
375                            );
376                        } else {
377                            $this->pChartObject->drawFilledRectangle(
378                                $X - $CircleRadius,
379                                $Y - $CircleRadius,
380                                $X + $CircleRadius,
381                                $Y + $CircleRadius,
382                                $Color
383                            );
384                        }
385                    } elseif ($Shape == BUBBLE_SHAPE_ROUND) {
386                        if ($RecordImageMap) {
387                            $this->pChartObject->addToImageMap(
388                                "CIRCLE",
389                                floor($X) . "," . floor($Y) . "," . floor($CircleRadius),
390                                $this->pChartObject->toHTMLColor(
391                                    $Palette[$Key]["R"],
392                                    $Palette[$Key]["G"],
393                                    $Palette[$Key]["B"]
394                                ),
395                                $SerieDescription,
396                                $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey]
397                            );
398                        }
399
400                        if ($BorderWidth != 1) {
401                            $this->pChartObject->drawFilledCircle(
402                                $X,
403                                $Y,
404                                $CircleRadius + $BorderWidth,
405                                $BorderColor
406                            );
407                            $this->pChartObject->drawFilledCircle($X, $Y, $CircleRadius, $Color);
408                        } else {
409                            $this->pChartObject->drawFilledCircle($X, $Y, $CircleRadius, $Color);
410                        }
411                    }
412
413                    $Y = $Y + $XStep;
414                }
415            }
416        }
417    }
418
419    public function writeBubbleLabel($SerieName, $SerieWeightName, $Points, $Format = "")
420    {
421        $DrawPoint = isset($Format["DrawPoint"]) ? $Format["DrawPoint"] : LABEL_POINT_BOX;
422
423        if (!is_array($Points)) {
424            $Point = $Points;
425            $Points = [];
426            $Points[] = $Point;
427        }
428
429        $Data = $this->pDataObject->getData();
430
431        if (!isset($Data["Series"][$SerieName])
432            || !isset($Data["Series"][$SerieWeightName])
433        ) {
434            return(0);
435        }
436        list($XMargin, $XDivs) = $this->pChartObject->scaleGetXSettings();
437
438        $AxisID = $Data["Series"][$SerieName]["Axis"];
439        $AxisMode = $Data["Axis"][$AxisID]["Display"];
440        $AxisFormat = $Data["Axis"][$AxisID]["Format"];
441        $AxisUnit = $Data["Axis"][$AxisID]["Unit"];
442        $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2) / $XDivs;
443
444        $X = $InitialX = $this->pChartObject->GraphAreaX1 + $XMargin;
445        $Y = $InitialY = $this->pChartObject->GraphAreaY1 + $XMargin;
446
447        $Color = [
448            "R" => $Data["Series"][$SerieName]["Color"]["R"],
449            "G" => $Data["Series"][$SerieName]["Color"]["G"],
450            "B" => $Data["Series"][$SerieName]["Color"]["B"],
451            "Alpha" => $Data["Series"][$SerieName]["Color"]["Alpha"]
452        ];
453
454        foreach ($Points as $Key => $Point) {
455            $Value = $Data["Series"][$SerieName]["Data"][$Point];
456            $PosArray = $this->pChartObject->scaleComputeY($Value, ["AxisID" => $AxisID]);
457
458            if (isset($Data["Abscissa"]) && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Point])) {
459                $Abscissa = $Data["Series"][$Data["Abscissa"]]["Data"][$Point] . " : ";
460            } else {
461                $Abscissa = "";
462            }
463            $Value = $this->pChartObject->scaleFormat($Value, $AxisMode, $AxisFormat, $AxisUnit);
464            $Weight = $Data["Series"][$SerieWeightName]["Data"][$Point];
465            $Caption = $Abscissa . $Value . " / " . $Weight;
466
467            if (isset($Data["Series"][$SerieName]["Description"])) {
468                $Description = $Data["Series"][$SerieName]["Description"];
469            } else {
470                $Description = "No description";
471            }
472            $Series = [["Format" => $Color, "Caption" => $Caption]];
473
474            if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
475                if ($XDivs == 0) {
476                    $XStep = 0;
477                } else {
478                    $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2)
479                        / $XDivs
480                    ;
481                }
482
483                $X = floor($InitialX + $Point * $XStep);
484                $Y = floor($PosArray);
485            } else {
486                if ($XDivs == 0) {
487                    $YStep = 0;
488                } else {
489                    $YStep = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1 - $XMargin * 2)
490                        / $XDivs
491                    ;
492                }
493
494                $X = floor($PosArray);
495                $Y = floor($InitialY + $Point * $YStep);
496            }
497
498            if ($DrawPoint == LABEL_POINT_CIRCLE) {
499                $this->pChartObject->drawFilledCircle(
500                    $X,
501                    $Y,
502                    3,
503                    [
504                        "R" => 255,
505                        "G" => 255,
506                        "B" => 255,
507                        "BorderR" => 0,
508                        "BorderG" => 0,
509                        "BorderB" => 0
510                    ]
511                );
512            } elseif ($DrawPoint == LABEL_POINT_BOX) {
513                $this->pChartObject->drawFilledRectangle(
514                    $X - 2,
515                    $Y - 2,
516                    $X + 2,
517                    $Y + 2,
518                    [
519                        "R" => 255,
520                        "G" => 255,
521                        "B" => 255,
522                        "BorderR" => 0,
523                        "BorderG" => 0,
524                        "BorderB" => 0
525                    ]
526                );
527            }
528
529            $this->pChartObject->drawLabelBox($X, $Y - 3, $Description, $Series, $Format);
530        }
531    }
532}
533