1<?php 2 3namespace CpChart\Chart; 4 5use CpChart\Data; 6use CpChart\Image; 7 8/** 9 * Pie - class to draw pie 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 Pie 22{ 23 /** 24 * @var Image 25 */ 26 public $pChartObject; 27 28 /** 29 * @var Data 30 */ 31 public $pDataObject; 32 33 /** 34 * @var array 35 */ 36 public $LabelPos = []; 37 38 /** 39 * @param Image $pChartObject 40 * @param Data $pDataObject 41 */ 42 public function __construct(Image $pChartObject, Data $pDataObject) 43 { 44 $this->pChartObject = $pChartObject; 45 $this->pDataObject = $pDataObject; 46 } 47 48 /** 49 * Draw a pie chart 50 * @param int $X 51 * @param int $Y 52 * @param array $Format 53 * @return int 54 */ 55 public function draw2DPie($X, $Y, array $Format = []) 56 { 57 $Radius = isset($Format["Radius"]) ? $Format["Radius"] : 60; 58 $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0; 59 $DataGapAngle = isset($Format["DataGapAngle"]) ? $Format["DataGapAngle"] : 0; 60 $DataGapRadius = isset($Format["DataGapRadius"]) ? $Format["DataGapRadius"] : 0; 61 $SecondPass = isset($Format["SecondPass"]) ? $Format["SecondPass"] : true; 62 $Border = isset($Format["Border"]) ? $Format["Border"] : false; 63 $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255; 64 $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255; 65 $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255; 66 $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false; 67 $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false; 68 $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false; 69 $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL; 70 $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0; 71 $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0; 72 $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0; 73 $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100; 74 $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : null; 75 $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_OUTSIDE; 76 $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 15; 77 $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : ""; 78 $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255; 79 $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255; 80 $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255; 81 $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100; 82 $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false; 83 84 $Data = $this->pDataObject->getData(); 85 $Palette = $this->pDataObject->getPalette(); 86 87 /* Do we have an abscissa serie defined? */ 88 if ($Data["Abscissa"] == "") { 89 return PIE_NO_ABSCISSA; 90 } 91 92 /* Try to find the data serie */ 93 $DataSerie = null; 94 foreach (array_keys($Data["Series"]) as $SerieName) { 95 if ($SerieName != $Data["Abscissa"]) { 96 $DataSerie = $SerieName; 97 } 98 } 99 100 /* Do we have data to compute? */ 101 if (!$DataSerie) { 102 return PIE_NO_DATASERIE; 103 } 104 105 /* Remove unused data */ 106 list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]); 107 108 /* Compute the pie sum */ 109 $SerieSum = $this->pDataObject->getSum($DataSerie); 110 111 /* Do we have data to draw? */ 112 if ($SerieSum == 0) { 113 return PIE_SUMISNULL; 114 } 115 116 /* Dump the real number of data to draw */ 117 $Values = []; 118 foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) { 119 if ($Value != 0) { 120 $Values[] = $Value; 121 } 122 } 123 124 /* Compute the wasted angular space between series */ 125 if (count($Values) == 1) { 126 $WastedAngular = 0; 127 } else { 128 $WastedAngular = count($Values) * $DataGapAngle; 129 } 130 131 /* Compute the scale */ 132 $ScaleFactor = (360 - $WastedAngular) / $SerieSum; 133 134 $RestoreShadow = $this->pChartObject->Shadow; 135 if ($this->pChartObject->Shadow) { 136 $this->pChartObject->Shadow = false; 137 138 $ShadowFormat = $Format; 139 $ShadowFormat["Shadow"] = true; 140 $this->draw2DPie( 141 $X + $this->pChartObject->ShadowX, 142 $Y + $this->pChartObject->ShadowY, 143 $ShadowFormat 144 ); 145 } 146 147 /* Draw the polygon pie elements */ 148 $Step = 360 / (2 * PI * $Radius); 149 $Offset = 0; 150 $ID = 0; 151 foreach ($Values as $Key => $Value) { 152 if ($Shadow) { 153 $Settings = [ 154 "R" => $this->pChartObject->ShadowR, 155 "G" => $this->pChartObject->ShadowG, 156 "B" => $this->pChartObject->ShadowB, 157 "Alpha" => $this->pChartObject->Shadowa 158 ]; 159 } else { 160 if (!isset($Palette[$ID]["R"])) { 161 $Color = $this->pChartObject->getRandomColor(); 162 $Palette[$ID] = $Color; 163 $this->pDataObject->savePalette($ID, $Color); 164 } 165 $Settings = [ 166 "R" => $Palette[$ID]["R"], 167 "G" => $Palette[$ID]["G"], 168 "B" => $Palette[$ID]["B"], 169 "Alpha" => $Palette[$ID]["Alpha"] 170 ]; 171 } 172 173 if (!$SecondPass && !$Shadow) { 174 if (!$Border) { 175 $Settings["Surrounding"] = 10; 176 } else { 177 $Settings["BorderR"] = $BorderR; 178 $Settings["BorderG"] = $BorderG; 179 $Settings["BorderB"] = $BorderB; 180 } 181 } 182 183 $EndAngle = $Offset + ($Value * $ScaleFactor); 184 if ($EndAngle > 360) { 185 $EndAngle = 360; 186 } 187 188 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 189 if ($DataGapAngle == 0) { 190 $X0 = $X; 191 $Y0 = $Y; 192 } else { 193 $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X; 194 $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius + $Y; 195 } 196 197 $Plots = [$X0, $Y0]; 198 199 for ($i = $Offset; $i <= $EndAngle; $i = $i + $Step) { 200 $Xc = cos(($i - 90) * PI / 180) * $Radius + $X; 201 $Yc = sin(($i - 90) * PI / 180) * $Radius + $Y; 202 203 if ($SecondPass && ($i < 90)) { 204 $Yc++; 205 } 206 if ($SecondPass && ($i > 180 && $i < 270)) { 207 $Xc++; 208 } 209 if ($SecondPass && ($i >= 270)) { 210 $Xc++; 211 $Yc++; 212 } 213 214 $Plots[] = $Xc; 215 $Plots[] = $Yc; 216 } 217 218 $this->pChartObject->drawPolygon($Plots, $Settings); 219 if ($RecordImageMap && !$Shadow) { 220 $this->pChartObject->addToImageMap( 221 "POLY", 222 $this->arraySerialize($Plots), 223 $this->pChartObject->toHTMLColor( 224 $Palette[$ID]["R"], 225 $Palette[$ID]["G"], 226 $Palette[$ID]["B"] 227 ), 228 $Data["Series"][$Data["Abscissa"]]["Data"][$Key], 229 $Value 230 ); 231 } 232 233 if ($DrawLabels && !$Shadow && !$SecondPass) { 234 if ($LabelColor == PIE_LABEL_COLOR_AUTO) { 235 $Settings = [ 236 "FillR" => $Palette[$ID]["R"], 237 "FillG" => $Palette[$ID]["G"], 238 "FillB" => $Palette[$ID]["B"], 239 "Alpha" => $Palette[$ID]["Alpha"] 240 ]; 241 } else { 242 $Settings = [ 243 "FillR" => $LabelR, 244 "FillG" => $LabelG, 245 "FillB" => $LabelB, 246 "Alpha" => $LabelAlpha 247 ]; 248 } 249 250 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 251 $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X; 252 $Yc = sin(($Angle - 90) * PI / 180) * $Radius + $Y; 253 254 $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key]; 255 256 if ($LabelStacked) { 257 $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $Radius); 258 } else { 259 $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false); 260 } 261 } 262 263 $Offset = $i + $DataGapAngle; 264 $ID++; 265 } 266 267 /* Second pass to smooth the angles */ 268 if ($SecondPass) { 269 $Step = 360 / (2 * PI * $Radius); 270 $Offset = 0; 271 $ID = 0; 272 foreach ($Values as $Key => $Value) { 273 $FirstPoint = true; 274 if ($Shadow) { 275 $Settings = [ 276 "R" => $this->pChartObject->ShadowR, 277 "G" => $this->pChartObject->ShadowG, 278 "B" => $this->pChartObject->ShadowB, 279 "Alpha" => $this->pChartObject->Shadowa 280 ]; 281 } else { 282 if ($Border) { 283 $Settings = ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB]; 284 } else { 285 $Settings = [ 286 "R" => $Palette[$ID]["R"], 287 "G" => $Palette[$ID]["G"], 288 "B" => $Palette[$ID]["B"], 289 "Alpha" => $Palette[$ID]["Alpha"] 290 ]; 291 } 292 } 293 294 $EndAngle = $Offset + ($Value * $ScaleFactor); 295 if ($EndAngle > 360) { 296 $EndAngle = 360; 297 } 298 299 if ($DataGapAngle == 0) { 300 $X0 = $X; 301 $Y0 = $Y; 302 } else { 303 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 304 $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X; 305 $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius + $Y; 306 } 307 $Plots[] = $X0; 308 $Plots[] = $Y0; 309 310 for ($i = $Offset; $i <= $EndAngle; $i = $i + $Step) { 311 $Xc = cos(($i - 90) * PI / 180) * $Radius + $X; 312 $Yc = sin(($i - 90) * PI / 180) * $Radius + $Y; 313 314 if ($FirstPoint) { 315 $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings); 316 $FirstPoint = false; 317 } 318 319 $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings); 320 } 321 $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings); 322 323 if ($DrawLabels && !$Shadow) { 324 if ($LabelColor == PIE_LABEL_COLOR_AUTO) { 325 $Settings = [ 326 "FillR" => $Palette[$ID]["R"], 327 "FillG" => $Palette[$ID]["G"], 328 "FillB" => $Palette[$ID]["B"], 329 "Alpha" => $Palette[$ID]["Alpha"] 330 ]; 331 } else { 332 $Settings = [ 333 "FillR" => $LabelR, 334 "FillG" => $LabelG, 335 "FillB" => $LabelB, 336 "Alpha" => $LabelAlpha 337 ]; 338 } 339 340 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 341 $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X; 342 $Yc = sin(($Angle - 90) * PI / 180) * $Radius + $Y; 343 344 $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key]; 345 346 if ($LabelStacked) { 347 $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $Radius); 348 } else { 349 $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false); 350 } 351 } 352 353 $Offset = $i + $DataGapAngle; 354 $ID++; 355 } 356 } 357 358 if ($WriteValues != null && !$Shadow) { 359 $Step = 360 / (2 * PI * $Radius); 360 $Offset = 0; 361 $ID = count($Values) - 1; 362 $Settings = [ 363 "Align" => TEXT_ALIGN_MIDDLEMIDDLE, 364 "R" => $ValueR, 365 "G" => $ValueG, 366 "B" => $ValueB, 367 "Alpha" => $ValueAlpha 368 ]; 369 foreach ($Values as $Key => $Value) { 370 $EndAngle = ($Value * $ScaleFactor) + $Offset; 371 if ((int) $EndAngle > 360) { 372 $EndAngle = 0; 373 } 374 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 375 376 if ($ValuePosition == PIE_VALUE_OUTSIDE) { 377 $Xc = cos(($Angle - 90) * PI / 180) * ($Radius + $ValuePadding) + $X; 378 $Yc = sin(($Angle - 90) * PI / 180) * ($Radius + $ValuePadding) + $Y; 379 } else { 380 $Xc = cos(($Angle - 90) * PI / 180) * ($Radius) / 2 + $X; 381 $Yc = sin(($Angle - 90) * PI / 180) * ($Radius) / 2 + $Y; 382 } 383 384 if ($WriteValues == PIE_VALUE_PERCENTAGE) { 385 $Display = round((100 / $SerieSum) * $Value, $Precision) . "%"; 386 } elseif ($WriteValues == PIE_VALUE_NATURAL) { 387 $Display = $Value . $ValueSuffix; 388 } 389 $this->pChartObject->drawText($Xc, $Yc, $Display, $Settings); 390 391 $Offset = $EndAngle + $DataGapAngle; 392 $ID--; 393 } 394 } 395 396 if ($DrawLabels && $LabelStacked) { 397 $this->writeShiftedLabels(); 398 } 399 400 $this->pChartObject->Shadow = $RestoreShadow; 401 402 return PIE_RENDERED; 403 } 404 405 /** 406 * Draw a 3D pie chart 407 * @param int $X 408 * @param int $Y 409 * @param array $Format 410 * @return int 411 */ 412 public function draw3DPie($X, $Y, array $Format = []) 413 { 414 /* Rendering layout */ 415 $Radius = isset($Format["Radius"]) ? $Format["Radius"] : 80; 416 $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0; 417 $SkewFactor = isset($Format["SkewFactor"]) ? $Format["SkewFactor"] : .5; 418 $SliceHeight = isset($Format["SliceHeight"]) ? $Format["SliceHeight"] : 20; 419 $DataGapAngle = isset($Format["DataGapAngle"]) ? $Format["DataGapAngle"] : 0; 420 $DataGapRadius = isset($Format["DataGapRadius"]) ? $Format["DataGapRadius"] : 0; 421 $SecondPass = isset($Format["SecondPass"]) ? $Format["SecondPass"] : true; 422 $Border = isset($Format["Border"]) ? $Format["Border"] : false; 423 $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false; 424 $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false; 425 $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false; 426 $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL; 427 $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0; 428 $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0; 429 $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0; 430 $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100; 431 $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : null; //PIE_VALUE_PERCENTAGE 432 $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_INSIDE; 433 $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 15; 434 $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : ""; 435 $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255; 436 $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255; 437 $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255; 438 $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100; 439 $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false; 440 441 /* Error correction for overlaying rounded corners */ 442 if ($SkewFactor < .5) { 443 $SkewFactor = .5; 444 } 445 446 /* Data Processing */ 447 $Data = $this->pDataObject->getData(); 448 $Palette = $this->pDataObject->getPalette(); 449 450 /* Do we have an abscissa serie defined? */ 451 if ($Data["Abscissa"] == "") { 452 return PIE_NO_ABSCISSA; 453 } 454 455 /* Try to find the data serie */ 456 $DataSerie = null; 457 foreach ($Data["Series"] as $SerieName => $SerieData) { 458 if ($SerieName != $Data["Abscissa"]) { 459 $DataSerie = $SerieName; 460 } 461 } 462 463 /* Do we have data to compute? */ 464 if (!$DataSerie) { 465 return PIE_NO_DATASERIE; 466 } 467 468 /* Remove unused data */ 469 list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]); 470 471 /* Compute the pie sum */ 472 $SerieSum = $this->pDataObject->getSum($DataSerie); 473 474 /* Do we have data to draw? */ 475 if ($SerieSum == 0) { 476 return PIE_SUMISNULL; 477 } 478 479 /* Dump the real number of data to draw */ 480 $Values = []; 481 foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) { 482 if ($Value != 0) { 483 $Values[] = $Value; 484 } 485 } 486 487 /* Compute the wasted angular space between series */ 488 if (count($Values) == 1) { 489 $WastedAngular = 0; 490 } else { 491 $WastedAngular = count($Values) * $DataGapAngle; 492 } 493 494 /* Compute the scale */ 495 $ScaleFactor = (360 - $WastedAngular) / $SerieSum; 496 497 $RestoreShadow = $this->pChartObject->Shadow; 498 if ($this->pChartObject->Shadow) { 499 $this->pChartObject->Shadow = false; 500 } 501 502 /* Draw the polygon pie elements */ 503 $Step = 360 / (2 * PI * $Radius); 504 $Offset = 360; 505 $ID = count($Values) - 1; 506 $Values = array_reverse($Values); 507 $Slice = 0; 508 $Slices = []; 509 $SliceColors = []; 510 $Visible = []; 511 $SliceAngle = []; 512 foreach ($Values as $Key => $Value) { 513 if (!isset($Palette[$ID]["R"])) { 514 $Color = $this->pChartObject->getRandomColor(); 515 $Palette[$ID] = $Color; 516 $this->pDataObject->savePalette($ID, $Color); 517 } 518 $Settings = [ 519 "R" => $Palette[$ID]["R"], 520 "G" => $Palette[$ID]["G"], 521 "B" => $Palette[$ID]["B"], 522 "Alpha" => $Palette[$ID]["Alpha"] 523 ]; 524 525 $SliceColors[$Slice] = $Settings; 526 527 $StartAngle = $Offset; 528 $EndAngle = $Offset - ($Value * $ScaleFactor); 529 if ($EndAngle < 0) { 530 $EndAngle = 0; 531 } 532 533 if ($StartAngle > 180) { 534 $Visible[$Slice]["Start"] = true; 535 } else { 536 $Visible[$Slice]["Start"] = true; 537 } 538 if ($EndAngle < 180) { 539 $Visible[$Slice]["End"] = false; 540 } else { 541 $Visible[$Slice]["End"] = true; 542 } 543 544 if ($DataGapAngle == 0) { 545 $X0 = $X; 546 $Y0 = $Y; 547 } else { 548 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 549 $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X; 550 $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius * $SkewFactor + $Y; 551 } 552 $Slices[$Slice][] = $X0; 553 $Slices[$Slice][] = $Y0; 554 $SliceAngle[$Slice][] = 0; 555 556 for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) { 557 $Xc = cos(($i - 90) * PI / 180) * $Radius + $X; 558 $Yc = sin(($i - 90) * PI / 180) * $Radius * $SkewFactor + $Y; 559 560 if (($SecondPass || $RestoreShadow) && ($i < 90)) { 561 $Yc++; 562 } 563 if (($SecondPass || $RestoreShadow) && ($i > 90 && $i < 180)) { 564 $Xc++; 565 } 566 if (($SecondPass || $RestoreShadow) && ($i > 180 && $i < 270)) { 567 $Xc++; 568 } 569 if (($SecondPass || $RestoreShadow) && ($i >= 270)) { 570 $Xc++; 571 $Yc++; 572 } 573 574 $Slices[$Slice][] = $Xc; 575 $Slices[$Slice][] = $Yc; 576 $SliceAngle[$Slice][] = $i; 577 } 578 579 $Offset = $i - $DataGapAngle; 580 $ID--; 581 $Slice++; 582 } 583 584 /* Draw the bottom shadow if needed */ 585 if ($RestoreShadow && ($this->pChartObject->ShadowX != 0 || $this->pChartObject->ShadowY != 0)) { 586 foreach ($Slices as $SliceID => $Plots) { 587 $ShadowPie = []; 588 for ($i = 0; $i < count($Plots); $i = $i + 2) { 589 $ShadowPie[] = $Plots[$i] + $this->pChartObject->ShadowX; 590 $ShadowPie[] = $Plots[$i + 1] + $this->pChartObject->ShadowY; 591 } 592 593 $Settings = [ 594 "R" => $this->pChartObject->ShadowR, 595 "G" => $this->pChartObject->ShadowG, 596 "B" => $this->pChartObject->ShadowB, 597 "Alpha" => $this->pChartObject->Shadowa, 598 "NoBorder" => true 599 ]; 600 $this->pChartObject->drawPolygon($ShadowPie, $Settings); 601 } 602 603 $Step = 360 / (2 * PI * $Radius); 604 $Offset = 360; 605 foreach ($Values as $Key => $Value) { 606 $EndAngle = $Offset - ($Value * $ScaleFactor); 607 if ($EndAngle < 0) { 608 $EndAngle = 0; 609 } 610 611 for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) { 612 $Xc = cos(($i - 90) * PI / 180) * $Radius + $X + $this->pChartObject->ShadowX; 613 $Yc = sin(($i - 90) * PI / 180) * $Radius * $SkewFactor + $Y + $this->pChartObject->ShadowY; 614 615 $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings); 616 } 617 618 $Offset = $i - $DataGapAngle; 619 $ID--; 620 } 621 } 622 623 /* Draw the bottom pie splice */ 624 foreach ($Slices as $SliceID => $Plots) { 625 $Settings = $SliceColors[$SliceID]; 626 $Settings["NoBorder"] = true; 627 $this->pChartObject->drawPolygon($Plots, $Settings); 628 629 if ($SecondPass) { 630 $Settings = $SliceColors[$SliceID]; 631 if ($Border) { 632 $Settings["R"] += 30; 633 $Settings["G"] += 30; 634 $Settings["B"] += 30; 635 } 636 /* Empty error handling */ 637 if (isset($SliceAngle[$SliceID][1])) { 638 $Angle = $SliceAngle[$SliceID][1]; 639 $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X; 640 $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y; 641 $this->pChartObject->drawLine($Plots[0], $Plots[1], $Xc, $Yc, $Settings); 642 643 $Angle = $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1]; 644 $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X; 645 $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y; 646 $this->pChartObject->drawLine($Plots[0], $Plots[1], $Xc, $Yc, $Settings); 647 } 648 } 649 } 650 651 /* Draw the two vertical edges */ 652 $Slices = array_reverse($Slices); 653 $SliceColors = array_reverse($SliceColors); 654 foreach ($Slices as $SliceID => $Plots) { 655 $Settings = $SliceColors[$SliceID]; 656 $Settings["R"] += 10; 657 $Settings["G"] += 10; 658 $Settings["B"] += 10; 659 $Settings["NoBorder"] = true; 660 /* Empty error handling */ 661 if ($Visible[$SliceID]["Start"] && isset($Plots[2])) { 662 $this->pChartObject->drawLine( 663 $Plots[2], 664 $Plots[3], 665 $Plots[2], 666 $Plots[3] - $SliceHeight, 667 ["R" => $Settings["R"], "G" => $Settings["G"], "B" => $Settings["B"]] 668 ); 669 $Border = []; 670 $Border[] = $Plots[0]; 671 $Border[] = $Plots[1]; 672 $Border[] = $Plots[0]; 673 $Border[] = $Plots[1] - $SliceHeight; 674 $Border[] = $Plots[2]; 675 $Border[] = $Plots[3] - $SliceHeight; 676 $Border[] = $Plots[2]; 677 $Border[] = $Plots[3]; 678 $this->pChartObject->drawPolygon($Border, $Settings); 679 } 680 } 681 682 $Slices = array_reverse($Slices); 683 $SliceColors = array_reverse($SliceColors); 684 foreach ($Slices as $SliceID => $Plots) { 685 $Settings = $SliceColors[$SliceID]; 686 $Settings["R"] += 10; 687 $Settings["G"] += 10; 688 $Settings["B"] += 10; 689 $Settings["NoBorder"] = true; 690 if ($Visible[$SliceID]["End"]) { 691 $this->pChartObject->drawLine( 692 $Plots[count($Plots) - 2], 693 $Plots[count($Plots) - 1], 694 $Plots[count($Plots) - 2], 695 $Plots[count($Plots) - 1] - $SliceHeight, 696 ["R" => $Settings["R"], "G" => $Settings["G"], "B" => $Settings["B"]] 697 ); 698 699 $Border = []; 700 $Border[] = $Plots[0]; 701 $Border[] = $Plots[1]; 702 $Border[] = $Plots[0]; 703 $Border[] = $Plots[1] - $SliceHeight; 704 $Border[] = $Plots[count($Plots) - 2]; 705 $Border[] = $Plots[count($Plots) - 1] - $SliceHeight; 706 $Border[] = $Plots[count($Plots) - 2]; 707 $Border[] = $Plots[count($Plots) - 1]; 708 $this->pChartObject->drawPolygon($Border, $Settings); 709 } 710 } 711 712 /* Draw the rounded edges */ 713 foreach ($Slices as $SliceID => $Plots) { 714 $Settings = $SliceColors[$SliceID]; 715 $Settings["R"] += 10; 716 $Settings["G"] += 10; 717 $Settings["B"] += 10; 718 $Settings["NoBorder"] = true; 719 720 for ($j = 2; $j < count($Plots) - 2; $j = $j + 2) { 721 $Angle = $SliceAngle[$SliceID][$j / 2]; 722 if ($Angle < 270 && $Angle > 90) { 723 $Border = []; 724 $Border[] = $Plots[$j]; 725 $Border[] = $Plots[$j + 1]; 726 $Border[] = $Plots[$j + 2]; 727 $Border[] = $Plots[$j + 3]; 728 $Border[] = $Plots[$j + 2]; 729 $Border[] = $Plots[$j + 3] - $SliceHeight; 730 $Border[] = $Plots[$j]; 731 $Border[] = $Plots[$j + 1] - $SliceHeight; 732 $this->pChartObject->drawPolygon($Border, $Settings); 733 } 734 } 735 736 if ($SecondPass) { 737 $Settings = $SliceColors[$SliceID]; 738 if (count($Border)) { 739 $Settings["R"] += 30; 740 $Settings["G"] += 30; 741 $Settings["B"] += 30; 742 } 743 744 /* Empty error handling */ 745 if (isset($SliceAngle[$SliceID][1])) { 746 $Angle = $SliceAngle[$SliceID][1]; 747 if ($Angle < 270 && $Angle > 90) { 748 $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X; 749 $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y; 750 $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings); 751 } 752 } 753 754 $Angle = $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1]; 755 if ($Angle < 270 && $Angle > 90) { 756 $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X; 757 $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y; 758 $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings); 759 } 760 761 if (isset($SliceAngle[$SliceID][1]) 762 && $SliceAngle[$SliceID][1] > 270 763 && $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1] < 270 764 ) { 765 $Xc = cos((270 - 90) * PI / 180) * $Radius + $X; 766 $Yc = sin((270 - 90) * PI / 180) * $Radius * $SkewFactor + $Y; 767 $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings); 768 } 769 770 if (isset($SliceAngle[$SliceID][1]) 771 && $SliceAngle[$SliceID][1] > 90 772 && $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1] < 90 773 ) { 774 $Xc = cos((0) * PI / 180) * $Radius + $X; 775 $Yc = sin((0) * PI / 180) * $Radius * $SkewFactor + $Y; 776 $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings); 777 } 778 } 779 } 780 781 /* Draw the top splice */ 782 foreach ($Slices as $SliceID => $Plots) { 783 $Settings = $SliceColors[$SliceID]; 784 $Settings["R"] += 20; 785 $Settings["G"] += 20; 786 $Settings["B"] += 20; 787 788 $Top = []; 789 for ($j = 0; $j < count($Plots); $j = $j + 2) { 790 $Top[] = $Plots[$j]; 791 $Top[] = $Plots[$j + 1] - $SliceHeight; 792 } 793 $this->pChartObject->drawPolygon($Top, $Settings); 794 795 if ($RecordImageMap && !$Shadow) { 796 $this->pChartObject->addToImageMap( 797 "POLY", 798 $this->arraySerialize($Top), 799 $this->pChartObject->toHTMLColor( 800 $Settings["R"], 801 $Settings["G"], 802 $Settings["B"] 803 ), 804 $Data["Series"][$Data["Abscissa"]]["Data"][count($Slices) - $SliceID - 1], 805 $Values[$SliceID] 806 ); 807 } 808 } 809 810 811 /* Second pass to smooth the angles */ 812 if ($SecondPass) { 813 $Step = 360 / (2 * PI * $Radius); 814 $Offset = 360; 815 $ID = count($Values) - 1; 816 foreach ($Values as $Key => $Value) { 817 $FirstPoint = true; 818 if ($Shadow) { 819 $Settings = [ 820 "R" => $this->pChartObject->ShadowR, 821 "G" => $this->pChartObject->ShadowG, 822 "B" => $this->pChartObject->ShadowB, 823 "Alpha" => $this->pChartObject->Shadowa 824 ]; 825 } else { 826 if ($Border) { 827 $Settings = [ 828 "R" => $Palette[$ID]["R"] + 30, 829 "G" => $Palette[$ID]["G"] + 30, 830 "B" => $Palette[$ID]["B"] + 30, 831 "Alpha" => $Palette[$ID]["Alpha"] 832 ]; 833 } else { 834 $Settings = [ 835 "R" => $Palette[$ID]["R"], 836 "G" => $Palette[$ID]["G"], 837 "B" => $Palette[$ID]["B"], 838 "Alpha" => $Palette[$ID]["Alpha"] 839 ]; 840 } 841 } 842 843 $EndAngle = $Offset - ($Value * $ScaleFactor); 844 if ($EndAngle < 0) { 845 $EndAngle = 0; 846 } 847 848 if ($DataGapAngle == 0) { 849 $X0 = $X; 850 $Y0 = $Y - $SliceHeight; 851 } else { 852 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 853 $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X; 854 $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius * $SkewFactor + $Y - $SliceHeight; 855 } 856 $Plots[] = $X0; 857 $Plots[] = $Y0; 858 859 for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) { 860 $Xc = cos(($i - 90) * PI / 180) * $Radius + $X; 861 $Yc = sin(($i - 90) * PI / 180) * $Radius * $SkewFactor + $Y - $SliceHeight; 862 863 if ($FirstPoint) { 864 $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings); 865 $FirstPoint = false; 866 } 867 868 $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings); 869 if ($i < 270 && $i > 90) { 870 $this->pChartObject->drawAntialiasPixel($Xc, $Yc + $SliceHeight, $Settings); 871 } 872 } 873 $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings); 874 875 $Offset = $i - $DataGapAngle; 876 $ID--; 877 } 878 } 879 880 if ($WriteValues != null) { 881 $Step = 360 / (2 * PI * $Radius); 882 $Offset = 360; 883 $ID = count($Values) - 1; 884 $Settings = [ 885 "Align" => TEXT_ALIGN_MIDDLEMIDDLE, 886 "R" => $ValueR, 887 "G" => $ValueG, 888 "B" => $ValueB, 889 "Alpha" => $ValueAlpha 890 ]; 891 foreach ($Values as $Key => $Value) { 892 $EndAngle = $Offset - ($Value * $ScaleFactor); 893 if ($EndAngle < 0) { 894 $EndAngle = 0; 895 } 896 897 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 898 899 if ($ValuePosition == PIE_VALUE_OUTSIDE) { 900 $Xc = cos(($Angle - 90) * PI / 180) * ($Radius + $ValuePadding) + $X; 901 $Yc = sin(($Angle - 90) * PI / 180) 902 * (($Radius * $SkewFactor) + $ValuePadding) 903 + $Y - $SliceHeight 904 ; 905 } else { 906 $Xc = cos(($Angle - 90) * PI / 180) * ($Radius) / 2 + $X; 907 $Yc = sin(($Angle - 90) * PI / 180) * ($Radius * $SkewFactor) / 2 + $Y - $SliceHeight; 908 } 909 910 if ($WriteValues == PIE_VALUE_PERCENTAGE) { 911 $Display = round((100 / $SerieSum) * $Value, $Precision) . "%"; 912 } elseif ($WriteValues == PIE_VALUE_NATURAL) { 913 $Display = $Value . $ValueSuffix; 914 } 915 916 $this->pChartObject->drawText($Xc, $Yc, $Display, $Settings); 917 918 $Offset = $EndAngle - $DataGapAngle; 919 $ID--; 920 } 921 } 922 923 if ($DrawLabels) { 924 $Step = 360 / (2 * PI * $Radius); 925 $Offset = 360; 926 $ID = count($Values) - 1; 927 foreach ($Values as $Key => $Value) { 928 if ($LabelColor == PIE_LABEL_COLOR_AUTO) { 929 $Settings = [ 930 "FillR" => $Palette[$ID]["R"], 931 "FillG" => $Palette[$ID]["G"], 932 "FillB" => $Palette[$ID]["B"], 933 "Alpha" => $Palette[$ID]["Alpha"] 934 ]; 935 } else { 936 $Settings = [ 937 "FillR" => $LabelR, 938 "FillG" => $LabelG, 939 "FillB" => $LabelB, 940 "Alpha" => $LabelAlpha 941 ]; 942 } 943 944 $EndAngle = $Offset - ($Value * $ScaleFactor); 945 if ($EndAngle < 0) { 946 $EndAngle = 0; 947 } 948 949 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 950 $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X; 951 $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y - $SliceHeight; 952 953 if (isset($Data["Series"][$Data["Abscissa"]]["Data"][$ID])) { 954 $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$ID]; 955 956 if ($LabelStacked) { 957 $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $Radius, true); 958 } else { 959 $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false); 960 } 961 } 962 963 $Offset = $EndAngle - $DataGapAngle; 964 $ID--; 965 } 966 } 967 968 if ($DrawLabels && $LabelStacked) { 969 $this->writeShiftedLabels(); 970 } 971 972 $this->pChartObject->Shadow = $RestoreShadow; 973 974 return PIE_RENDERED; 975 } 976 977 /** 978 * Draw the legend of pie chart 979 * @param int $X 980 * @param int $Y 981 * @param array $Format 982 * @return int 983 */ 984 public function drawPieLegend($X, $Y, array $Format = []) 985 { 986 $FontName = isset($Format["FontName"]) ? $Format["FontName"] : $this->pChartObject->FontName; 987 $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->pChartObject->FontSize; 988 $FontR = isset($Format["FontR"]) ? $Format["FontR"] : $this->pChartObject->FontColorR; 989 $FontG = isset($Format["FontG"]) ? $Format["FontG"] : $this->pChartObject->FontColorG; 990 $FontB = isset($Format["FontB"]) ? $Format["FontB"] : $this->pChartObject->FontColorB; 991 $BoxSize = isset($Format["BoxSize"]) ? $Format["BoxSize"] : 5; 992 $Margin = isset($Format["Margin"]) ? $Format["Margin"] : 5; 993 $R = isset($Format["R"]) ? $Format["R"] : 200; 994 $G = isset($Format["G"]) ? $Format["G"] : 200; 995 $B = isset($Format["B"]) ? $Format["B"] : 200; 996 $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100; 997 $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255; 998 $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255; 999 $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255; 1000 $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null; 1001 $Style = isset($Format["Style"]) ? $Format["Style"] : LEGEND_ROUND; 1002 $Mode = isset($Format["Mode"]) ? $Format["Mode"] : LEGEND_VERTICAL; 1003 1004 if ($Surrounding != null) { 1005 $BorderR = $R + $Surrounding; 1006 $BorderG = $G + $Surrounding; 1007 $BorderB = $B + $Surrounding; 1008 } 1009 1010 $YStep = max($this->pChartObject->FontSize, $BoxSize) + 5; 1011 $XStep = $BoxSize + 5; 1012 1013 /* Data Processing */ 1014 $Data = $this->pDataObject->getData(); 1015 $Palette = $this->pDataObject->getPalette(); 1016 1017 /* Do we have an abscissa serie defined? */ 1018 if ($Data["Abscissa"] == "") { 1019 return PIE_NO_ABSCISSA; 1020 } 1021 1022 $Boundaries = []; 1023 $Boundaries["L"] = $X; 1024 $Boundaries["T"] = $Y; 1025 $Boundaries["R"] = 0; 1026 $Boundaries["B"] = 0; 1027 $vY = $Y; 1028 $vX = $X; 1029 foreach ($Data["Series"][$Data["Abscissa"]]["Data"] as $Key => $Value) { 1030 $BoxArray = $this->pChartObject->getTextBox( 1031 $vX + $BoxSize + 4, 1032 $vY + $BoxSize / 2, 1033 $FontName, 1034 $FontSize, 1035 0, 1036 $Value 1037 ); 1038 1039 if ($Mode == LEGEND_VERTICAL) { 1040 if ($Boundaries["T"] > $BoxArray[2]["Y"] + $BoxSize / 2) { 1041 $Boundaries["T"] = $BoxArray[2]["Y"] + $BoxSize / 2; 1042 } 1043 if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) { 1044 $Boundaries["R"] = $BoxArray[1]["X"] + 2; 1045 } 1046 if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $BoxSize / 2) { 1047 $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $BoxSize / 2; 1048 } 1049 $vY = $vY + $YStep; 1050 } elseif ($Mode == LEGEND_HORIZONTAL) { 1051 if ($Boundaries["T"] > $BoxArray[2]["Y"] + $BoxSize / 2) { 1052 $Boundaries["T"] = $BoxArray[2]["Y"] + $BoxSize / 2; 1053 } 1054 if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) { 1055 $Boundaries["R"] = $BoxArray[1]["X"] + 2; 1056 } 1057 if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $BoxSize / 2) { 1058 $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $BoxSize / 2; 1059 } 1060 $vX = $Boundaries["R"] + $XStep; 1061 } 1062 } 1063 $vY = $vY - $YStep; 1064 $vX = $vX - $XStep; 1065 1066 $TopOffset = $Y - $Boundaries["T"]; 1067 if ($Boundaries["B"] - ($vY + $BoxSize) < $TopOffset) { 1068 $Boundaries["B"] = $vY + $BoxSize + $TopOffset; 1069 } 1070 1071 if ($Style == LEGEND_ROUND) { 1072 $this->pChartObject->drawRoundedFilledRectangle( 1073 $Boundaries["L"] - $Margin, 1074 $Boundaries["T"] - $Margin, 1075 $Boundaries["R"] + $Margin, 1076 $Boundaries["B"] + $Margin, 1077 $Margin, 1078 [ 1079 "R" => $R, 1080 "G" => $G, 1081 "B" => $B, 1082 "Alpha" => $Alpha, 1083 "BorderR" => $BorderR, 1084 "BorderG" => $BorderG, 1085 "BorderB" => $BorderB 1086 ] 1087 ); 1088 } elseif ($Style == LEGEND_BOX) { 1089 $this->pChartObject->drawFilledRectangle( 1090 $Boundaries["L"] - $Margin, 1091 $Boundaries["T"] - $Margin, 1092 $Boundaries["R"] + $Margin, 1093 $Boundaries["B"] + $Margin, 1094 [ 1095 "R" => $R, 1096 "G" => $G, 1097 "B" => $B, 1098 "Alpha" => $Alpha, 1099 "BorderR" => $BorderR, 1100 "BorderG" => $BorderG, 1101 "BorderB" => $BorderB 1102 ] 1103 ); 1104 } 1105 $RestoreShadow = $this->pChartObject->Shadow; 1106 $this->pChartObject->Shadow = false; 1107 foreach ($Data["Series"][$Data["Abscissa"]]["Data"] as $Key => $Value) { 1108 $R = $Palette[$Key]["R"]; 1109 $G = $Palette[$Key]["G"]; 1110 $B = $Palette[$Key]["B"]; 1111 1112 $this->pChartObject->drawFilledRectangle( 1113 $X + 1, 1114 $Y + 1, 1115 $X + $BoxSize + 1, 1116 $Y + $BoxSize + 1, 1117 ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20] 1118 ); 1119 $this->pChartObject->drawFilledRectangle( 1120 $X, 1121 $Y, 1122 $X + $BoxSize, 1123 $Y + $BoxSize, 1124 ["R" => $R, "G" => $G, "B" => $B, "Surrounding" => 20] 1125 ); 1126 if ($Mode == LEGEND_VERTICAL) { 1127 $this->pChartObject->drawText( 1128 $X + $BoxSize + 4, 1129 $Y + $BoxSize / 2, 1130 $Value, 1131 [ 1132 "R" => $FontR, 1133 "G" => $FontG, 1134 "B" => $FontB, 1135 "Align" => TEXT_ALIGN_MIDDLELEFT, 1136 "FontName" => $FontName, 1137 "FontSize" => $FontSize 1138 ] 1139 ); 1140 $Y = $Y + $YStep; 1141 } elseif ($Mode == LEGEND_HORIZONTAL) { 1142 $BoxArray = $this->pChartObject->drawText( 1143 $X + $BoxSize + 4, 1144 $Y + $BoxSize / 2, 1145 $Value, 1146 [ 1147 "R" => $FontR, 1148 "G" => $FontG, 1149 "B" => $FontB, 1150 "Align" => TEXT_ALIGN_MIDDLELEFT, 1151 "FontName" => $FontName, 1152 "FontSize" => $FontSize 1153 ] 1154 ); 1155 $X = $BoxArray[1]["X"] + 2 + $XStep; 1156 } 1157 } 1158 1159 $this->Shadow = $RestoreShadow; 1160 } 1161 1162 /** 1163 * Set the color of the specified slice 1164 * @param mixed $SliceID 1165 * @param array $Format 1166 */ 1167 public function setSliceColor($SliceID, array $Format = []) 1168 { 1169 $R = isset($Format["R"]) ? $Format["R"] : 0; 1170 $G = isset($Format["G"]) ? $Format["G"] : 0; 1171 $B = isset($Format["B"]) ? $Format["B"] : 0; 1172 $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100; 1173 1174 $this->pDataObject->Palette[$SliceID]["R"] = $R; 1175 $this->pDataObject->Palette[$SliceID]["G"] = $G; 1176 $this->pDataObject->Palette[$SliceID]["B"] = $B; 1177 $this->pDataObject->Palette[$SliceID]["Alpha"] = $Alpha; 1178 } 1179 1180 /** 1181 * Internally used compute the label positions 1182 * @param int $X 1183 * @param int $Y 1184 * @param string $Label 1185 * @param int|float $Angle 1186 * @param array $Settings 1187 * @param boolean $Stacked 1188 * @param int $Xc 1189 * @param int $Yc 1190 * @param int $Radius 1191 * @param boolean $Reversed 1192 */ 1193 public function writePieLabel( 1194 $X, 1195 $Y, 1196 $Label, 1197 $Angle, 1198 $Settings, 1199 $Stacked, 1200 $Xc = 0, 1201 $Yc = 0, 1202 $Radius = 0, 1203 $Reversed = false 1204 ) { 1205 $LabelOffset = 30; 1206 $FontName = $this->pChartObject->FontName; 1207 $FontSize = $this->pChartObject->FontSize; 1208 1209 if (!$Stacked) { 1210 $Settings["Angle"] = 360 - $Angle; 1211 $Settings["Length"] = 25; 1212 $Settings["Size"] = 8; 1213 1214 $this->pChartObject->drawArrowLabel($X, $Y, " " . $Label . " ", $Settings); 1215 } else { 1216 $X2 = cos(deg2rad($Angle - 90)) * 20 + $X; 1217 $Y2 = sin(deg2rad($Angle - 90)) * 20 + $Y; 1218 1219 $TxtPos = $this->pChartObject->getTextBox($X, $Y, $FontName, $FontSize, 0, $Label); 1220 $Height = $TxtPos[0]["Y"] - $TxtPos[2]["Y"]; 1221 $YTop = $Y2 - $Height / 2 - 2; 1222 $YBottom = $Y2 + $Height / 2 + 2; 1223 1224 if (count($this->LabelPos)) { 1225 $Done = false; 1226 foreach ($this->LabelPos as $Key => $Settings) { 1227 if (!$Done) { 1228 $yTopAboveTopBelowBottom = ($YTop >= $Settings["YTop"] && $YTop <= $Settings["YBottom"]); 1229 $yBottomAboveTopBelowBottom = ($YBottom >= $Settings["YTop"] 1230 && $YBottom <= $Settings["YBottom"] 1231 ); 1232 1233 if ($Angle <= 90 1234 && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom) 1235 ) { 1236 $this->shift(0, 180, -($Height + 2), $Reversed); 1237 $Done = true; 1238 } 1239 if ($Angle > 90 1240 && $Angle <= 180 1241 && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom) 1242 ) { 1243 $this->shift(0, 180, -($Height + 2), $Reversed); 1244 $Done = true; 1245 } 1246 if ($Angle > 180 1247 && $Angle <= 270 1248 && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom) 1249 ) { 1250 $this->shift(180, 360, ($Height + 2), $Reversed); 1251 $Done = true; 1252 } 1253 if ($Angle > 270 1254 && $Angle <= 360 1255 && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom) 1256 ) { 1257 $this->shift(180, 360, ($Height + 2), $Reversed); 1258 $Done = true; 1259 } 1260 } 1261 } 1262 } 1263 1264 $LabelSettings = [ 1265 "YTop" => $YTop, 1266 "YBottom" => $YBottom, 1267 "Label" => $Label, 1268 "Angle" => $Angle, 1269 "X1" => $X, 1270 "Y1" => $Y, 1271 "X2" => $X2, 1272 "Y2" => $Y2 1273 ]; 1274 if ($Angle <= 180) { 1275 $LabelSettings["X3"] = $Xc + $Radius + $LabelOffset; 1276 } 1277 if ($Angle > 180) { 1278 $LabelSettings["X3"] = $Xc - $Radius - $LabelOffset; 1279 } 1280 $this->LabelPos[] = $LabelSettings; 1281 } 1282 } 1283 1284 /** 1285 * Internally used to shift label positions 1286 * @param int $StartAngle 1287 * @param int $EndAngle 1288 * @param int $Offset 1289 * @param boolean $Reversed 1290 */ 1291 public function shift($StartAngle, $EndAngle, $Offset, $Reversed) 1292 { 1293 if ($Reversed) { 1294 $Offset = -$Offset; 1295 } 1296 foreach ($this->LabelPos as $Key => $Settings) { 1297 if ($Settings["Angle"] > $StartAngle && $Settings["Angle"] <= $EndAngle) { 1298 $this->LabelPos[$Key]["YTop"] = $Settings["YTop"] + $Offset; 1299 $this->LabelPos[$Key]["YBottom"] = $Settings["YBottom"] + $Offset; 1300 $this->LabelPos[$Key]["Y2"] = $Settings["Y2"] + $Offset; 1301 } 1302 } 1303 } 1304 1305 /** 1306 * Internally used to write the re-computed labels 1307 * @return null|int 1308 */ 1309 public function writeShiftedLabels() 1310 { 1311 if (!count($this->LabelPos)) { 1312 return 0; 1313 } 1314 foreach ($this->LabelPos as $Settings) { 1315 $X1 = $Settings["X1"]; 1316 $Y1 = $Settings["Y1"]; 1317 $X2 = $Settings["X2"]; 1318 $Y2 = $Settings["Y2"]; 1319 $X3 = $Settings["X3"]; 1320 $Angle = $Settings["Angle"]; 1321 $Label = $Settings["Label"]; 1322 1323 $this->pChartObject->drawArrow($X2, $Y2, $X1, $Y1, ["Size" => 8]); 1324 if ($Angle <= 180) { 1325 $this->pChartObject->drawLine($X2, $Y2, $X3, $Y2); 1326 $this->pChartObject->drawText($X3 + 2, $Y2, $Label, ["Align" => TEXT_ALIGN_MIDDLELEFT]); 1327 } else { 1328 $this->pChartObject->drawLine($X2, $Y2, $X3, $Y2); 1329 $this->pChartObject->drawText($X3 - 2, $Y2, $Label, ["Align" => TEXT_ALIGN_MIDDLERIGHT]); 1330 } 1331 } 1332 } 1333 1334 /** 1335 * Draw a ring chart 1336 * @param int $X 1337 * @param int $Y 1338 * @param array $Format 1339 * @return int 1340 */ 1341 public function draw2DRing($X, $Y, array $Format = []) 1342 { 1343 $OuterRadius = isset($Format["Radius"]) ? $Format["Radius"] : 60; // For compatibility 1344 $OuterRadius = isset($Format["OuterRadius"]) ? $Format["OuterRadius"] : $OuterRadius; 1345 $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0; 1346 $InnerRadius = isset($Format["InnerRadius"]) ? $Format["InnerRadius"] : 30; 1347 $Border = isset($Format["Border"]) ? $Format["Border"] : false; 1348 $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255; 1349 $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255; 1350 $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255; 1351 $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : 100; 1352 $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false; 1353 $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false; 1354 $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false; 1355 $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL; 1356 $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0; 1357 $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0; 1358 $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0; 1359 $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100; 1360 $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : null; //PIE_VALUE_PERCENTAGE 1361 $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 5; 1362 $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_OUTSIDE; 1363 $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : ""; 1364 $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255; 1365 $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255; 1366 $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255; 1367 $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100; 1368 $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false; 1369 1370 /* Data Processing */ 1371 $Data = $this->pDataObject->getData(); 1372 $Palette = $this->pDataObject->getPalette(); 1373 1374 /* Do we have an abscissa serie defined? */ 1375 if ($Data["Abscissa"] == "") { 1376 return PIE_NO_ABSCISSA; 1377 } 1378 1379 /* Try to find the data serie */ 1380 $DataSerie = null; 1381 foreach ($Data["Series"] as $SerieName => $SerieData) { 1382 if ($SerieName != $Data["Abscissa"]) { 1383 $DataSerie = $SerieName; 1384 } 1385 } 1386 1387 /* Do we have data to compute? */ 1388 if (!$DataSerie) { 1389 return PIE_NO_DATASERIE; 1390 } 1391 1392 /* Remove unused data */ 1393 list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]); 1394 1395 /* Compute the pie sum */ 1396 $SerieSum = $this->pDataObject->getSum($DataSerie); 1397 1398 /* Do we have data to draw? */ 1399 if ($SerieSum == 0) { 1400 return PIE_SUMISNULL; 1401 } 1402 1403 /* Dump the real number of data to draw */ 1404 $Values = []; 1405 foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) { 1406 if ($Value != 0) { 1407 $Values[] = $Value; 1408 } 1409 } 1410 1411 /* Compute the wasted angular space between series */ 1412 if (count($Values) == 1) { 1413 $WastedAngular = 0; 1414 } else { 1415 $WastedAngular = 0; 1416 } // count($Values) 1417 1418 /* Compute the scale */ 1419 $ScaleFactor = (360 - $WastedAngular) / $SerieSum; 1420 1421 $RestoreShadow = $this->pChartObject->Shadow; 1422 if ($this->pChartObject->Shadow) { 1423 $this->pChartObject->Shadow = false; 1424 1425 $ShadowFormat = $Format; 1426 $ShadowFormat["Shadow"] = true; 1427 $this->draw2DRing( 1428 $X + $this->pChartObject->ShadowX, 1429 $Y + $this->pChartObject->ShadowY, 1430 $ShadowFormat 1431 ); 1432 } 1433 1434 /* Draw the polygon pie elements */ 1435 $Step = 360 / (2 * PI * $OuterRadius); 1436 $Offset = 0; 1437 $ID = 0; 1438 foreach ($Values as $Key => $Value) { 1439 if ($Shadow) { 1440 $Settings = [ 1441 "R" => $this->pChartObject->ShadowR, 1442 "G" => $this->pChartObject->ShadowG, 1443 "B" => $this->pChartObject->ShadowB, 1444 "Alpha" => $this->pChartObject->Shadowa 1445 ]; 1446 $BorderColor = $Settings; 1447 } else { 1448 if (!isset($Palette[$ID]["R"])) { 1449 $Color = $this->pChartObject->getRandomColor(); 1450 $Palette[$ID] = $Color; 1451 $this->pDataObject->savePalette($ID, $Color); 1452 } 1453 $Settings = [ 1454 "R" => $Palette[$ID]["R"], 1455 "G" => $Palette[$ID]["G"], 1456 "B" => $Palette[$ID]["B"], 1457 "Alpha" => $Palette[$ID]["Alpha"] 1458 ]; 1459 1460 if ($Border) { 1461 $BorderColor = [ 1462 "R" => $BorderR, 1463 "G" => $BorderG, 1464 "B" => $BorderB, 1465 "Alpha" => $BorderAlpha 1466 ]; 1467 } else { 1468 $BorderColor = $Settings; 1469 } 1470 } 1471 1472 $Plots = []; 1473 $Boundaries = []; 1474 $AAPixels = []; 1475 $EndAngle = $Offset + ($Value * $ScaleFactor); 1476 if ($EndAngle > 360) { 1477 $EndAngle = 360; 1478 } 1479 for ($i = $Offset; $i <= $EndAngle; $i = $i + $Step) { 1480 $Xc = cos(($i - 90) * PI / 180) * $OuterRadius + $X; 1481 $Yc = sin(($i - 90) * PI / 180) * $OuterRadius + $Y; 1482 1483 if (!isset($Boundaries[0]["X1"])) { 1484 $Boundaries[0]["X1"] = $Xc; 1485 $Boundaries[0]["Y1"] = $Yc; 1486 } 1487 $AAPixels[] = [$Xc, $Yc]; 1488 1489 if ($i < 90) { 1490 $Yc++; 1491 } 1492 if ($i > 180 && $i < 270) { 1493 $Xc++; 1494 } 1495 if ($i >= 270) { 1496 $Xc++; 1497 $Yc++; 1498 } 1499 1500 $Plots[] = $Xc; 1501 $Plots[] = $Yc; 1502 } 1503 $Boundaries[1]["X1"] = $Xc; 1504 $Boundaries[1]["Y1"] = $Yc; 1505 $Lasti = $EndAngle; 1506 1507 for ($i = $EndAngle; $i >= $Offset; $i = $i - $Step) { 1508 $Xc = cos(($i - 90) * PI / 180) * ($InnerRadius - 1) + $X; 1509 $Yc = sin(($i - 90) * PI / 180) * ($InnerRadius - 1) + $Y; 1510 1511 if (!isset($Boundaries[1]["X2"])) { 1512 $Boundaries[1]["X2"] = $Xc; 1513 $Boundaries[1]["Y2"] = $Yc; 1514 } 1515 $AAPixels[] = [$Xc, $Yc]; 1516 1517 $Xc = cos(($i - 90) * PI / 180) * $InnerRadius + $X; 1518 $Yc = sin(($i - 90) * PI / 180) * $InnerRadius + $Y; 1519 1520 if ($i < 90) { 1521 $Yc++; 1522 } 1523 if ($i > 180 && $i < 270) { 1524 $Xc++; 1525 } 1526 if ($i >= 270) { 1527 $Xc++; 1528 $Yc++; 1529 } 1530 1531 $Plots[] = $Xc; 1532 $Plots[] = $Yc; 1533 } 1534 $Boundaries[0]["X2"] = $Xc; 1535 $Boundaries[0]["Y2"] = $Yc; 1536 1537 /* Draw the polygon */ 1538 $this->pChartObject->drawPolygon($Plots, $Settings); 1539 if ($RecordImageMap && !$Shadow) { 1540 $this->pChartObject->addToImageMap( 1541 "POLY", 1542 $this->arraySerialize($Plots), 1543 $this->pChartObject->toHTMLColor( 1544 $Palette[$ID]["R"], 1545 $Palette[$ID]["G"], 1546 $Palette[$ID]["B"] 1547 ), 1548 $Data["Series"][$Data["Abscissa"]]["Data"][$Key], 1549 $Value 1550 ); 1551 } 1552 1553 /* Smooth the edges using AA */ 1554 foreach ($AAPixels as $Pos) { 1555 $this->pChartObject->drawAntialiasPixel($Pos[0], $Pos[1], $BorderColor); 1556 } 1557 $this->pChartObject->drawLine( 1558 $Boundaries[0]["X1"], 1559 $Boundaries[0]["Y1"], 1560 $Boundaries[0]["X2"], 1561 $Boundaries[0]["Y2"], 1562 $BorderColor 1563 ); 1564 $this->pChartObject->drawLine( 1565 $Boundaries[1]["X1"], 1566 $Boundaries[1]["Y1"], 1567 $Boundaries[1]["X2"], 1568 $Boundaries[1]["Y2"], 1569 $BorderColor 1570 ); 1571 1572 if ($DrawLabels && !$Shadow) { 1573 if ($LabelColor == PIE_LABEL_COLOR_AUTO) { 1574 $Settings = [ 1575 "FillR" => $Palette[$ID]["R"], 1576 "FillG" => $Palette[$ID]["G"], 1577 "FillB" => $Palette[$ID]["B"], 1578 "Alpha" => $Palette[$ID]["Alpha"] 1579 ]; 1580 } else { 1581 $Settings = [ 1582 "FillR" => $LabelR, 1583 "FillG" => $LabelG, 1584 "FillB" => $LabelB, 1585 "Alpha" => $LabelAlpha 1586 ]; 1587 } 1588 1589 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 1590 $Xc = cos(($Angle - 90) * PI / 180) * $OuterRadius + $X; 1591 $Yc = sin(($Angle - 90) * PI / 180) * $OuterRadius + $Y; 1592 1593 $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key]; 1594 1595 if ($LabelStacked) { 1596 $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $OuterRadius); 1597 } else { 1598 $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false); 1599 } 1600 } 1601 1602 $Offset = $Lasti; 1603 $ID++; 1604 } 1605 1606 if ($DrawLabels && $LabelStacked) { 1607 $this->writeShiftedLabels(); 1608 } 1609 1610 if ($WriteValues && !$Shadow) { 1611 $Step = 360 / (2 * PI * $OuterRadius); 1612 $Offset = 0; 1613 foreach ($Values as $Key => $Value) { 1614 $EndAngle = $Offset + ($Value * $ScaleFactor); 1615 if ($EndAngle > 360) { 1616 $EndAngle = 360; 1617 } 1618 1619 $Angle = $Offset + ($Value * $ScaleFactor) / 2; 1620 if ($ValuePosition == PIE_VALUE_OUTSIDE) { 1621 $Xc = cos(($Angle - 90) * PI / 180) * ($OuterRadius + $ValuePadding) + $X; 1622 $Yc = sin(($Angle - 90) * PI / 180) * ($OuterRadius + $ValuePadding) + $Y; 1623 if ($Angle >= 0 && $Angle <= 90) { 1624 $Align = TEXT_ALIGN_BOTTOMLEFT; 1625 } 1626 if ($Angle > 90 && $Angle <= 180) { 1627 $Align = TEXT_ALIGN_TOPLEFT; 1628 } 1629 if ($Angle > 180 && $Angle <= 270) { 1630 $Align = TEXT_ALIGN_TOPRIGHT; 1631 } 1632 if ($Angle > 270) { 1633 $Align = TEXT_ALIGN_BOTTOMRIGHT; 1634 } 1635 } else { 1636 $Xc = cos(($Angle - 90) * PI / 180) 1637 * (($OuterRadius - $InnerRadius) / 2 + $InnerRadius) 1638 + $X 1639 ; 1640 $Yc = sin(($Angle - 90) * PI / 180) 1641 * (($OuterRadius - $InnerRadius) / 2 + $InnerRadius) 1642 + $Y 1643 ; 1644 $Align = TEXT_ALIGN_MIDDLEMIDDLE; 1645 } 1646 1647 if ($WriteValues == PIE_VALUE_PERCENTAGE) { 1648 $Display = round((100 / $SerieSum) * $Value, $Precision) . "%"; 1649 } elseif ($WriteValues == PIE_VALUE_NATURAL) { 1650 $Display = $Value . $ValueSuffix; 1651 } else { 1652 $Display = ""; 1653 } 1654 $this->pChartObject->drawText( 1655 $Xc, 1656 $Yc, 1657 $Display, 1658 ["Align" => $Align, "R" => $ValueR, "G" => $ValueG, "B" => $ValueB] 1659 ); 1660 $Offset = $EndAngle; 1661 } 1662 } 1663 1664 $this->pChartObject->Shadow = $RestoreShadow; 1665 1666 return PIE_RENDERED; 1667 } 1668 1669 /** 1670 * Draw a 3D ring chart 1671 * @param int $X 1672 * @param int $Y 1673 * @param array $Format 1674 * @return int 1675 */ 1676 public function draw3DRing($X, $Y, array $Format = []) 1677 { 1678 $OuterRadius = isset($Format["OuterRadius"]) ? $Format["OuterRadius"] : 100; 1679 $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0; 1680 $InnerRadius = isset($Format["InnerRadius"]) ? $Format["InnerRadius"] : 30; 1681 $SkewFactor = isset($Format["SkewFactor"]) ? $Format["SkewFactor"] : .6; 1682 $SliceHeight = isset($Format["SliceHeight"]) ? $Format["SliceHeight"] : 10; 1683 $DataGapAngle = isset($Format["DataGapAngle"]) ? $Format["DataGapAngle"] : 10; 1684 $DataGapRadius = isset($Format["DataGapRadius"]) ? $Format["DataGapRadius"] : 10; 1685 $Border = isset($Format["Border"]) ? $Format["Border"] : false; 1686 $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false; 1687 $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false; 1688 $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false; 1689 $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL; 1690 $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0; 1691 $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0; 1692 $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0; 1693 $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100; 1694 $Cf = isset($Format["Cf"]) ? $Format["Cf"] : 20; 1695 $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : PIE_VALUE_NATURAL; 1696 $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : $SliceHeight + 15; 1697 $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_OUTSIDE; 1698 $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : ""; 1699 $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255; 1700 $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255; 1701 $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255; 1702 $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100; 1703 $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false; 1704 1705 /* Error correction for overlaying rounded corners */ 1706 if ($SkewFactor < .5) { 1707 $SkewFactor = .5; 1708 } 1709 1710 /* Data Processing */ 1711 $Data = $this->pDataObject->getData(); 1712 $Palette = $this->pDataObject->getPalette(); 1713 1714 /* Do we have an abscissa serie defined? */ 1715 if ($Data["Abscissa"] == "") { 1716 return PIE_NO_ABSCISSA; 1717 } 1718 1719 /* Try to find the data serie */ 1720 $DataSerie = null; 1721 foreach ($Data["Series"] as $SerieName => $SerieData) { 1722 if ($SerieName != $Data["Abscissa"]) { 1723 $DataSerie = $SerieName; 1724 } 1725 } 1726 1727 /* Do we have data to compute? */ 1728 if (!$DataSerie) { 1729 return PIE_NO_DATASERIE; 1730 } 1731 1732 /* Remove unused data */ 1733 list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]); 1734 1735 /* Compute the pie sum */ 1736 $SerieSum = $this->pDataObject->getSum($DataSerie); 1737 1738 /* Do we have data to draw? */ 1739 if ($SerieSum == 0) { 1740 return PIE_SUMISNULL; 1741 } 1742 1743 /* Dump the real number of data to draw */ 1744 $Values = []; 1745 foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) { 1746 if ($Value != 0) { 1747 $Values[] = $Value; 1748 } 1749 } 1750 1751 /* Compute the wasted angular space between series */ 1752 if (count($Values) == 1) { 1753 $WastedAngular = 0; 1754 } else { 1755 $WastedAngular = count($Values) * $DataGapAngle; 1756 } 1757 1758 /* Compute the scale */ 1759 $ScaleFactor = (360 - $WastedAngular) / $SerieSum; 1760 1761 $RestoreShadow = $this->pChartObject->Shadow; 1762 if ($this->pChartObject->Shadow) { 1763 $this->pChartObject->Shadow = false; 1764 } 1765 1766 /* Draw the polygon ring elements */ 1767 $Offset = 360; 1768 $ID = count($Values) - 1; 1769 $Values = array_reverse($Values); 1770 $Slice = 0; 1771 $Slices = []; 1772 $SliceColors = []; 1773 $Visible = []; 1774 $SliceAngle = []; 1775 foreach ($Values as $Key => $Value) { 1776 if (!isset($Palette[$ID]["R"])) { 1777 $Color = $this->pChartObject->getRandomColor(); 1778 $Palette[$ID] = $Color; 1779 $this->pDataObject->savePalette($ID, $Color); 1780 } 1781 $Settings = [ 1782 "R" => $Palette[$ID]["R"], 1783 "G" => $Palette[$ID]["G"], 1784 "B" => $Palette[$ID]["B"], 1785 "Alpha" => $Palette[$ID]["Alpha"] 1786 ]; 1787 1788 $SliceColors[$Slice] = $Settings; 1789 1790 $StartAngle = $Offset; 1791 $EndAngle = $Offset - ($Value * $ScaleFactor); 1792 if ($EndAngle < 0) { 1793 $EndAngle = 0; 1794 } 1795 1796 if ($StartAngle > 180) { 1797 $Visible[$Slice]["Start"] = true; 1798 } else { 1799 $Visible[$Slice]["Start"] = true; 1800 } 1801 if ($EndAngle < 180) { 1802 $Visible[$Slice]["End"] = false; 1803 } else { 1804 $Visible[$Slice]["End"] = true; 1805 } 1806 1807 $Step = (360 / (2 * PI * $OuterRadius)) / 2; 1808 $OutX1 = VOID; 1809 $OutY1 = VOID; 1810 for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) { 1811 $Xc = cos(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 2) + $X; 1812 $Yc = sin(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 2) * $SkewFactor + $Y; 1813 $Slices[$Slice]["AA"][] = [$Xc, $Yc]; 1814 1815 $Xc = cos(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 1) + $X; 1816 $Yc = sin(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 1) * $SkewFactor + $Y; 1817 $Slices[$Slice]["AA"][] = [$Xc, $Yc]; 1818 1819 $Xc = cos(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) + $X; 1820 $Yc = sin(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) * $SkewFactor + $Y; 1821 $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings); 1822 1823 if ($OutX1 == VOID) { 1824 $OutX1 = $Xc; 1825 $OutY1 = $Yc; 1826 } 1827 1828 if ($i < 90) { 1829 $Yc++; 1830 } 1831 if ($i > 90 && $i < 180) { 1832 $Xc++; 1833 } 1834 if ($i > 180 && $i < 270) { 1835 $Xc++; 1836 } 1837 if ($i >= 270) { 1838 $Xc++; 1839 $Yc++; 1840 } 1841 1842 $Slices[$Slice]["BottomPoly"][] = floor($Xc); 1843 $Slices[$Slice]["BottomPoly"][] = floor($Yc); 1844 $Slices[$Slice]["TopPoly"][] = floor($Xc); 1845 $Slices[$Slice]["TopPoly"][] = floor($Yc) - $SliceHeight; 1846 $Slices[$Slice]["Angle"][] = $i; 1847 } 1848 $OutX2 = $Xc; 1849 $OutY2 = $Yc; 1850 1851 $Slices[$Slice]["Angle"][] = VOID; 1852 $Lasti = $i; 1853 1854 $Step = (360 / (2 * PI * $InnerRadius)) / 2; 1855 $InX1 = VOID; 1856 $InY1 = VOID; 1857 for ($i = $EndAngle; $i <= $Offset; $i = $i + $Step) { 1858 $Xc = cos(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius - 1) + $X; 1859 $Yc = sin(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius - 1) * $SkewFactor + $Y; 1860 $Slices[$Slice]["AA"][] = [$Xc, $Yc]; 1861 1862 $Xc = cos(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius) + $X; 1863 $Yc = sin(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius) * $SkewFactor + $Y; 1864 $Slices[$Slice]["AA"][] = [$Xc, $Yc]; 1865 1866 if ($InX1 == VOID) { 1867 $InX1 = $Xc; 1868 $InY1 = $Yc; 1869 } 1870 1871 if ($i < 90) { 1872 $Yc++; 1873 } 1874 if ($i > 90 && $i < 180) { 1875 $Xc++; 1876 } 1877 if ($i > 180 && $i < 270) { 1878 $Xc++; 1879 } 1880 if ($i >= 270) { 1881 $Xc++; 1882 $Yc++; 1883 } 1884 1885 $Slices[$Slice]["BottomPoly"][] = floor($Xc); 1886 $Slices[$Slice]["BottomPoly"][] = floor($Yc); 1887 $Slices[$Slice]["TopPoly"][] = floor($Xc); 1888 $Slices[$Slice]["TopPoly"][] = floor($Yc) - $SliceHeight; 1889 $Slices[$Slice]["Angle"][] = $i; 1890 } 1891 $InX2 = $Xc; 1892 $InY2 = $Yc; 1893 1894 $Slices[$Slice]["InX1"] = $InX1; 1895 $Slices[$Slice]["InY1"] = $InY1; 1896 $Slices[$Slice]["InX2"] = $InX2; 1897 $Slices[$Slice]["InY2"] = $InY2; 1898 $Slices[$Slice]["OutX1"] = $OutX1; 1899 $Slices[$Slice]["OutY1"] = $OutY1; 1900 $Slices[$Slice]["OutX2"] = $OutX2; 1901 $Slices[$Slice]["OutY2"] = $OutY2; 1902 1903 $Offset = $Lasti - $DataGapAngle; 1904 $ID--; 1905 $Slice++; 1906 } 1907 1908 /* Draw the bottom pie splice */ 1909 foreach ($Slices as $SliceID => $Plots) { 1910 $Settings = $SliceColors[$SliceID]; 1911 $Settings["NoBorder"] = true; 1912 $this->pChartObject->drawPolygon($Plots["BottomPoly"], $Settings); 1913 1914 foreach ($Plots["AA"] as $Key => $Pos) { 1915 $this->pChartObject->drawAntialiasPixel($Pos[0], $Pos[1], $Settings); 1916 } 1917 $this->pChartObject->drawLine( 1918 $Plots["InX1"], 1919 $Plots["InY1"], 1920 $Plots["OutX2"], 1921 $Plots["OutY2"], 1922 $Settings 1923 ); 1924 $this->pChartObject->drawLine( 1925 $Plots["InX2"], 1926 $Plots["InY2"], 1927 $Plots["OutX1"], 1928 $Plots["OutY1"], 1929 $Settings 1930 ); 1931 } 1932 1933 $Slices = array_reverse($Slices); 1934 $SliceColors = array_reverse($SliceColors); 1935 1936 /* Draw the vertical edges (semi-visible) */ 1937 foreach ($Slices as $SliceID => $Plots) { 1938 $Settings = $SliceColors[$SliceID]; 1939 $Settings["NoBorder"] = true; 1940 $Settings["R"] = $Settings["R"] + $Cf; 1941 $Settings["G"] = $Settings["G"] + $Cf; 1942 $Settings["B"] = $Settings["B"] + $Cf; 1943 1944 $StartAngle = $Plots["Angle"][0]; 1945 foreach ($Plots["Angle"] as $Key => $Angle) { 1946 if ($Angle == VOID) { 1947 $EndAngle = $Plots["Angle"][$Key - 1]; 1948 } 1949 } 1950 1951 if ($StartAngle >= 270 || $StartAngle <= 90) { 1952 $this->pChartObject->drawLine( 1953 $Plots["OutX1"], 1954 $Plots["OutY1"], 1955 $Plots["OutX1"], 1956 $Plots["OutY1"] - $SliceHeight, 1957 $Settings 1958 ); 1959 } 1960 if ($StartAngle >= 270 || $StartAngle <= 90) { 1961 $this->pChartObject->drawLine( 1962 $Plots["OutX2"], 1963 $Plots["OutY2"], 1964 $Plots["OutX2"], 1965 $Plots["OutY2"] - $SliceHeight, 1966 $Settings 1967 ); 1968 } 1969 $this->pChartObject->drawLine( 1970 $Plots["InX1"], 1971 $Plots["InY1"], 1972 $Plots["InX1"], 1973 $Plots["InY1"] - $SliceHeight, 1974 $Settings 1975 ); 1976 $this->pChartObject->drawLine( 1977 $Plots["InX2"], 1978 $Plots["InY2"], 1979 $Plots["InX2"], 1980 $Plots["InY2"] - $SliceHeight, 1981 $Settings 1982 ); 1983 } 1984 1985 /* Draw the inner vertical slices */ 1986 foreach ($Slices as $SliceID => $Plots) { 1987 $Settings = $SliceColors[$SliceID]; 1988 $Settings["NoBorder"] = true; 1989 $Settings["R"] = $Settings["R"] + $Cf; 1990 $Settings["G"] = $Settings["G"] + $Cf; 1991 $Settings["B"] = $Settings["B"] + $Cf; 1992 1993 $Outer = true; 1994 $Inner = false; 1995 $InnerPlotsA = []; 1996 $InnerPlotsB = []; 1997 foreach ($Plots["Angle"] as $ID => $Angle) { 1998 if ($Angle == VOID) { 1999 $Outer = false; 2000 $Inner = true; 2001 } elseif ($Inner && ($Angle < 90 || $Angle > 270) && isset($Plots["BottomPoly"][$ID * 2])) { 2002 $Xo = $Plots["BottomPoly"][$ID * 2]; 2003 $Yo = $Plots["BottomPoly"][$ID * 2 + 1]; 2004 2005 $InnerPlotsA[] = $Xo; 2006 $InnerPlotsA[] = $Yo; 2007 $InnerPlotsB[] = $Xo; 2008 $InnerPlotsB[] = $Yo - $SliceHeight; 2009 } 2010 } 2011 2012 if (count($InnerPlotsA)) { 2013 $InnerPlots = array_merge($InnerPlotsA, $this->arrayReverse($InnerPlotsB)); 2014 $this->pChartObject->drawPolygon($InnerPlots, $Settings); 2015 } 2016 } 2017 2018 /* Draw the splice top and left poly */ 2019 foreach ($Slices as $SliceID => $Plots) { 2020 $Settings = $SliceColors[$SliceID]; 2021 $Settings["NoBorder"] = true; 2022 $Settings["R"] = $Settings["R"] + $Cf * 1.5; 2023 $Settings["G"] = $Settings["G"] + $Cf * 1.5; 2024 $Settings["B"] = $Settings["B"] + $Cf * 1.5; 2025 2026 $StartAngle = $Plots["Angle"][0]; 2027 foreach ($Plots["Angle"] as $Key => $Angle) { 2028 if ($Angle == VOID) { 2029 $EndAngle = $Plots["Angle"][$Key - 1]; 2030 } 2031 } 2032 2033 if ($StartAngle < 180) { 2034 $Points = []; 2035 $Points[] = $Plots["InX2"]; 2036 $Points[] = $Plots["InY2"]; 2037 $Points[] = $Plots["InX2"]; 2038 $Points[] = $Plots["InY2"] - $SliceHeight; 2039 $Points[] = $Plots["OutX1"]; 2040 $Points[] = $Plots["OutY1"] - $SliceHeight; 2041 $Points[] = $Plots["OutX1"]; 2042 $Points[] = $Plots["OutY1"]; 2043 2044 $this->pChartObject->drawPolygon($Points, $Settings); 2045 } 2046 2047 if ($EndAngle > 180) { 2048 $Points = []; 2049 $Points[] = $Plots["InX1"]; 2050 $Points[] = $Plots["InY1"]; 2051 $Points[] = $Plots["InX1"]; 2052 $Points[] = $Plots["InY1"] - $SliceHeight; 2053 $Points[] = $Plots["OutX2"]; 2054 $Points[] = $Plots["OutY2"] - $SliceHeight; 2055 $Points[] = $Plots["OutX2"]; 2056 $Points[] = $Plots["OutY2"]; 2057 2058 $this->pChartObject->drawPolygon($Points, $Settings); 2059 } 2060 } 2061 2062 2063 /* Draw the vertical edges (visible) */ 2064 foreach ($Slices as $SliceID => $Plots) { 2065 $Settings = $SliceColors[$SliceID]; 2066 $Settings["NoBorder"] = true; 2067 $Settings["R"] = $Settings["R"] + $Cf; 2068 $Settings["G"] = $Settings["G"] + $Cf; 2069 $Settings["B"] = $Settings["B"] + $Cf; 2070 2071 $StartAngle = $Plots["Angle"][0]; 2072 foreach ($Plots["Angle"] as $Key => $Angle) { 2073 if ($Angle == VOID) { 2074 $EndAngle = $Plots["Angle"][$Key - 1]; 2075 } 2076 } 2077 2078 if ($StartAngle <= 270 && $StartAngle >= 90) { 2079 $this->pChartObject->drawLine( 2080 $Plots["OutX1"], 2081 $Plots["OutY1"], 2082 $Plots["OutX1"], 2083 $Plots["OutY1"] - $SliceHeight, 2084 $Settings 2085 ); 2086 } 2087 if ($EndAngle <= 270 && $EndAngle >= 90) { 2088 $this->pChartObject->drawLine( 2089 $Plots["OutX2"], 2090 $Plots["OutY2"], 2091 $Plots["OutX2"], 2092 $Plots["OutY2"] - $SliceHeight, 2093 $Settings 2094 ); 2095 } 2096 } 2097 2098 2099 /* Draw the outer vertical slices */ 2100 foreach ($Slices as $SliceID => $Plots) { 2101 $Settings = $SliceColors[$SliceID]; 2102 $Settings["NoBorder"] = true; 2103 $Settings["R"] = $Settings["R"] + $Cf; 2104 $Settings["G"] = $Settings["G"] + $Cf; 2105 $Settings["B"] = $Settings["B"] + $Cf; 2106 2107 $Outer = true; 2108 $Inner = false; 2109 $OuterPlotsA = []; 2110 $OuterPlotsB = []; 2111 $InnerPlotsA = []; 2112 $InnerPlotsB = []; 2113 foreach ($Plots["Angle"] as $ID => $Angle) { 2114 if ($Angle == VOID) { 2115 $Outer = false; 2116 $Inner = true; 2117 } elseif ($Outer && ($Angle > 90 && $Angle < 270) && isset($Plots["BottomPoly"][$ID * 2])) { 2118 $Xo = $Plots["BottomPoly"][$ID * 2]; 2119 $Yo = $Plots["BottomPoly"][$ID * 2 + 1]; 2120 2121 $OuterPlotsA[] = $Xo; 2122 $OuterPlotsA[] = $Yo; 2123 $OuterPlotsB[] = $Xo; 2124 $OuterPlotsB[] = $Yo - $SliceHeight; 2125 } 2126 } 2127 if (count($OuterPlotsA)) { 2128 $OuterPlots = array_merge($OuterPlotsA, $this->arrayReverse($OuterPlotsB)); 2129 $this->pChartObject->drawPolygon($OuterPlots, $Settings); 2130 } 2131 } 2132 2133 $Slices = array_reverse($Slices); 2134 $SliceColors = array_reverse($SliceColors); 2135 2136 /* Draw the top pie splice */ 2137 foreach ($Slices as $SliceID => $Plots) { 2138 $Settings = $SliceColors[$SliceID]; 2139 $Settings["NoBorder"] = true; 2140 $Settings["R"] = $Settings["R"] + $Cf * 2; 2141 $Settings["G"] = $Settings["G"] + $Cf * 2; 2142 $Settings["B"] = $Settings["B"] + $Cf * 2; 2143 2144 $this->pChartObject->drawPolygon($Plots["TopPoly"], $Settings); 2145 2146 if ($RecordImageMap) { 2147 $this->pChartObject->addToImageMap( 2148 "POLY", 2149 $this->arraySerialize($Plots["TopPoly"]), 2150 $this->pChartObject->toHTMLColor( 2151 $Settings["R"], 2152 $Settings["G"], 2153 $Settings["B"] 2154 ), 2155 $Data["Series"][$Data["Abscissa"]]["Data"][$SliceID], 2156 $Data["Series"][$DataSerie]["Data"][count($Slices) - $SliceID - 1] 2157 ); 2158 } 2159 2160 foreach ($Plots["AA"] as $Key => $Pos) { 2161 $this->pChartObject->drawAntialiasPixel( 2162 $Pos[0], 2163 $Pos[1] - $SliceHeight, 2164 $Settings 2165 ); 2166 } 2167 $this->pChartObject->drawLine( 2168 $Plots["InX1"], 2169 $Plots["InY1"] - $SliceHeight, 2170 $Plots["OutX2"], 2171 $Plots["OutY2"] - $SliceHeight, 2172 $Settings 2173 ); 2174 $this->pChartObject->drawLine( 2175 $Plots["InX2"], 2176 $Plots["InY2"] - $SliceHeight, 2177 $Plots["OutX1"], 2178 $Plots["OutY1"] - $SliceHeight, 2179 $Settings 2180 ); 2181 } 2182 2183 if ($DrawLabels) { 2184 $Offset = 360; 2185 foreach ($Values as $Key => $Value) { 2186 $StartAngle = $Offset; 2187 $EndAngle = $Offset - ($Value * $ScaleFactor); 2188 if ($EndAngle < 0) { 2189 $EndAngle = 0; 2190 } 2191 2192 if ($LabelColor == PIE_LABEL_COLOR_AUTO) { 2193 $Settings = [ 2194 "FillR" => $Palette[$ID]["R"], 2195 "FillG" => $Palette[$ID]["G"], 2196 "FillB" => $Palette[$ID]["B"], 2197 "Alpha" => $Palette[$ID]["Alpha"] 2198 ]; 2199 } else { 2200 $Settings = [ 2201 "FillR" => $LabelR, 2202 "FillG" => $LabelG, 2203 "FillB" => $LabelB, 2204 "Alpha" => $LabelAlpha 2205 ]; 2206 } 2207 2208 $Angle = ($EndAngle - $Offset) / 2 + $Offset; 2209 $Xc = cos(($Angle - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) + $X; 2210 $Yc = sin(($Angle - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) * $SkewFactor + $Y; 2211 2212 $Label = ""; 2213 if ($WriteValues == PIE_VALUE_PERCENTAGE) { 2214 $Label = round((100 / $SerieSum) * $Value, $Precision) . "%"; 2215 } elseif ($WriteValues == PIE_VALUE_NATURAL) { 2216 $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key]; 2217 } 2218 2219 if ($LabelStacked) { 2220 $this->writePieLabel( 2221 $Xc, 2222 $Yc - $SliceHeight, 2223 $Label, 2224 $Angle, 2225 $Settings, 2226 true, 2227 $X, 2228 $Y, 2229 $OuterRadius 2230 ); 2231 } else { 2232 $this->writePieLabel($Xc, $Yc - $SliceHeight, $Label, $Angle, $Settings, false); 2233 } 2234 $Offset = $EndAngle - $DataGapAngle; 2235 $ID--; 2236 $Slice++; 2237 } 2238 } 2239 if ($DrawLabels && $LabelStacked) { 2240 $this->writeShiftedLabels(); 2241 } 2242 2243 $this->pChartObject->Shadow = $RestoreShadow; 2244 2245 return PIE_RENDERED; 2246 } 2247 2248 /** 2249 * Serialize an array 2250 * @param array $Data 2251 * @return string 2252 */ 2253 public function arraySerialize(array $Data) 2254 { 2255 $Result = ""; 2256 foreach ($Data as $Value) { 2257 if ($Result == "") { 2258 $Result = floor($Value); 2259 } else { 2260 $Result = $Result . "," . floor($Value); 2261 } 2262 } 2263 2264 return $Result; 2265 } 2266 2267 /** 2268 * Reverse an array 2269 * @param array $Plots 2270 * @return array 2271 */ 2272 public function arrayReverse(array $Plots) 2273 { 2274 $Result = []; 2275 2276 for ($i = count($Plots) - 1; $i >= 0; $i = $i - 2) { 2277 $Result[] = $Plots[$i - 1]; 2278 $Result[] = $Plots[$i]; 2279 } 2280 2281 return $Result; 2282 } 2283 2284 /** 2285 * Remove unused series & values 2286 * @param array $Data 2287 * @param array $Palette 2288 * @param string $DataSerie 2289 * @param string $AbscissaSerie 2290 * @return array 2291 */ 2292 public function clean0Values(array $Data, array $Palette, $DataSerie, $AbscissaSerie) 2293 { 2294 $NewPalette = []; 2295 $NewData = []; 2296 $NewAbscissa = []; 2297 2298 /* Remove unused series */ 2299 foreach (array_keys($Data["Series"]) as $SerieName) { 2300 if ($SerieName != $DataSerie && $SerieName != $AbscissaSerie) { 2301 unset($Data["Series"][$SerieName]); 2302 } 2303 } 2304 2305 /* Remove null values */ 2306 foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) { 2307 if ($Value != 0) { 2308 $NewData[] = $Value; 2309 $NewAbscissa[] = $Data["Series"][$AbscissaSerie]["Data"][$Key]; 2310 if (isset($Palette[$Key])) { 2311 $NewPalette[] = $Palette[$Key]; 2312 } 2313 } 2314 } 2315 $Data["Series"][$DataSerie]["Data"] = $NewData; 2316 $Data["Series"][$AbscissaSerie]["Data"] = $NewAbscissa; 2317 2318 return [$Data, $NewPalette]; 2319 } 2320} 2321