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