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