PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / vendor / szymach / c-pchart / src / Chart / Spring.php
matomo / app / vendor / szymach / c-pchart / src / Chart Last commit date
Bubble.php 1 year ago Indicator.php 1 year ago Pie.php 1 year ago Radar.php 1 year ago Scatter.php 1 year ago Split.php 1 year ago Spring.php 1 year ago Stock.php 1 year ago Surface.php 1 year ago
Spring.php
994 lines
1 <?php
2
3 namespace CpChart\Chart;
4
5 use CpChart\Image;
6 /**
7 * Spring - class to draw spring graphs
8 *
9 * Version : 2.1.4
10 * Made by : Jean-Damien POGOLOTTI
11 * Last Update : 19/01/2014
12 *
13 * This file can be distributed under the license you can find at :
14 *
15 * http://www.pchart.net/license
16 *
17 * You can find the whole class documentation on the pChart web site.
18 */
19 class Spring
20 {
21 /**
22 * @var array
23 */
24 public $History = [];
25 /**
26 * @var array
27 */
28 public $Data = [];
29 /**
30 * @var array
31 */
32 public $Default = [];
33 /**
34 * @var array
35 */
36 public $Labels = [];
37 /**
38 * @var array
39 */
40 public $Links = [];
41 /**
42 * @var type
43 */
44 public $pChartObject;
45 /**
46 * @var int
47 */
48 public $X1;
49 /**
50 * @var int
51 */
52 public $Y1;
53 /**
54 * @var int
55 */
56 public $X2;
57 /**
58 * @var int
59 */
60 public $Y2;
61 /**
62 * @var boolean
63 */
64 public $AutoComputeFreeZone = \false;
65 /**
66 * @var int|float
67 */
68 private $MagneticForceA;
69 /**
70 * @var int|float
71 */
72 private $MagneticForceR;
73 /**
74 * @var int|float
75 */
76 private $RingSize;
77 public function __construct()
78 {
79 /* Set nodes defaults */
80 $this->Default["R"] = 255;
81 $this->Default["G"] = 255;
82 $this->Default["B"] = 255;
83 $this->Default["Alpha"] = 100;
84 $this->Default["BorderR"] = 0;
85 $this->Default["BorderG"] = 0;
86 $this->Default["BorderB"] = 0;
87 $this->Default["BorderAlpha"] = 100;
88 $this->Default["Surrounding"] = null;
89 $this->Default["BackgroundR"] = 255;
90 $this->Default["BackgroundG"] = 255;
91 $this->Default["BackgroundB"] = 255;
92 $this->Default["BackgroundAlpha"] = 0;
93 $this->Default["Force"] = 1;
94 $this->Default["NodeType"] = NODE_TYPE_FREE;
95 $this->Default["Size"] = 5;
96 $this->Default["Shape"] = NODE_SHAPE_CIRCLE;
97 $this->Default["FreeZone"] = 40;
98 $this->Default["LinkR"] = 0;
99 $this->Default["LinkG"] = 0;
100 $this->Default["LinkB"] = 0;
101 $this->Default["LinkAlpha"] = 0;
102 $this->Labels["Type"] = LABEL_CLASSIC;
103 $this->Labels["R"] = 0;
104 $this->Labels["G"] = 0;
105 $this->Labels["B"] = 0;
106 $this->Labels["Alpha"] = 100;
107 }
108 /**
109 * Set default links options
110 * @param array $Settings
111 */
112 public function setLinkDefaults(array $Settings = [])
113 {
114 if (isset($Settings["R"])) {
115 $this->Default["LinkR"] = $Settings["R"];
116 }
117 if (isset($Settings["G"])) {
118 $this->Default["LinkG"] = $Settings["G"];
119 }
120 if (isset($Settings["B"])) {
121 $this->Default["LinkB"] = $Settings["B"];
122 }
123 if (isset($Settings["Alpha"])) {
124 $this->Default["LinkAlpha"] = $Settings["Alpha"];
125 }
126 }
127 /**
128 * Set default links options
129 * @param array $Settings
130 */
131 public function setLabelsSettings(array $Settings = [])
132 {
133 if (isset($Settings["Type"])) {
134 $this->Labels["Type"] = $Settings["Type"];
135 }
136 if (isset($Settings["R"])) {
137 $this->Labels["R"] = $Settings["R"];
138 }
139 if (isset($Settings["G"])) {
140 $this->Labels["G"] = $Settings["G"];
141 }
142 if (isset($Settings["B"])) {
143 $this->Labels["B"] = $Settings["B"];
144 }
145 if (isset($Settings["Alpha"])) {
146 $this->Labels["Alpha"] = $Settings["Alpha"];
147 }
148 }
149 /**
150 * Auto compute the FreeZone size based on the number of connections
151 */
152 public function autoFreeZone()
153 {
154 /* Check connections reciprocity */
155 foreach ($this->Data as $Key => $Settings) {
156 if (isset($Settings["Connections"])) {
157 $this->Data[$Key]["FreeZone"] = count($Settings["Connections"]) * 10 + 20;
158 } else {
159 $this->Data[$Key]["FreeZone"] = 20;
160 }
161 }
162 }
163 /**
164 * Set link properties
165 * @param int $FromNode
166 * @param int $ToNode
167 * @param array $Settings
168 * @return null|int
169 */
170 public function linkProperties($FromNode, $ToNode, array $Settings)
171 {
172 if (!isset($this->Data[$FromNode])) {
173 return 0;
174 }
175 if (!isset($this->Data[$ToNode])) {
176 return 0;
177 }
178 $R = isset($Settings["R"]) ? $Settings["R"] : 0;
179 $G = isset($Settings["G"]) ? $Settings["G"] : 0;
180 $B = isset($Settings["B"]) ? $Settings["B"] : 0;
181 $Alpha = isset($Settings["Alpha"]) ? $Settings["Alpha"] : 100;
182 $Name = isset($Settings["Name"]) ? $Settings["Name"] : null;
183 $Ticks = isset($Settings["Ticks"]) ? $Settings["Ticks"] : null;
184 $this->Links[$FromNode][$ToNode]["R"] = $R;
185 $this->Links[$ToNode][$FromNode]["R"] = $R;
186 $this->Links[$FromNode][$ToNode]["G"] = $G;
187 $this->Links[$ToNode][$FromNode]["G"] = $G;
188 $this->Links[$FromNode][$ToNode]["B"] = $B;
189 $this->Links[$ToNode][$FromNode]["B"] = $B;
190 $this->Links[$FromNode][$ToNode]["Alpha"] = $Alpha;
191 $this->Links[$ToNode][$FromNode]["Alpha"] = $Alpha;
192 $this->Links[$FromNode][$ToNode]["Name"] = $Name;
193 $this->Links[$ToNode][$FromNode]["Name"] = $Name;
194 $this->Links[$FromNode][$ToNode]["Ticks"] = $Ticks;
195 $this->Links[$ToNode][$FromNode]["Ticks"] = $Ticks;
196 }
197 /**
198 * @param array $Settings
199 */
200 public function setNodeDefaults(array $Settings = [])
201 {
202 if (isset($Settings["R"])) {
203 $this->Default["R"] = $Settings["R"];
204 }
205 if (isset($Settings["G"])) {
206 $this->Default["G"] = $Settings["G"];
207 }
208 if (isset($Settings["B"])) {
209 $this->Default["B"] = $Settings["B"];
210 }
211 if (isset($Settings["Alpha"])) {
212 $this->Default["Alpha"] = $Settings["Alpha"];
213 }
214 if (isset($Settings["BorderR"])) {
215 $this->Default["BorderR"] = $Settings["BorderR"];
216 }
217 if (isset($Settings["BorderG"])) {
218 $this->Default["BorderG"] = $Settings["BorderG"];
219 }
220 if (isset($Settings["BorderB"])) {
221 $this->Default["BorderB"] = $Settings["BorderB"];
222 }
223 if (isset($Settings["BorderAlpha"])) {
224 $this->Default["BorderAlpha"] = $Settings["BorderAlpha"];
225 }
226 if (isset($Settings["Surrounding"])) {
227 $this->Default["Surrounding"] = $Settings["Surrounding"];
228 }
229 if (isset($Settings["BackgroundR"])) {
230 $this->Default["BackgroundR"] = $Settings["BackgroundR"];
231 }
232 if (isset($Settings["BackgroundG"])) {
233 $this->Default["BackgroundG"] = $Settings["BackgroundG"];
234 }
235 if (isset($Settings["BackgroundB"])) {
236 $this->Default["BackgroundB"] = $Settings["BackgroundB"];
237 }
238 if (isset($Settings["BackgroundAlpha"])) {
239 $this->Default["BackgroundAlpha"] = $Settings["BackgroundAlpha"];
240 }
241 if (isset($Settings["NodeType"])) {
242 $this->Default["NodeType"] = $Settings["NodeType"];
243 }
244 if (isset($Settings["Size"])) {
245 $this->Default["Size"] = $Settings["Size"];
246 }
247 if (isset($Settings["Shape"])) {
248 $this->Default["Shape"] = $Settings["Shape"];
249 }
250 if (isset($Settings["FreeZone"])) {
251 $this->Default["FreeZone"] = $Settings["FreeZone"];
252 }
253 }
254 /**
255 * Add a node
256 * @param int $NodeID
257 * @param array $Settings
258 * @return null|int
259 */
260 public function addNode($NodeID, array $Settings = [])
261 {
262 /* if the node already exists, ignore */
263 if (isset($this->Data[$NodeID])) {
264 return 0;
265 }
266 $Name = isset($Settings["Name"]) ? $Settings["Name"] : "Node " . $NodeID;
267 $Connections = isset($Settings["Connections"]) ? $Settings["Connections"] : null;
268 $R = isset($Settings["R"]) ? $Settings["R"] : $this->Default["R"];
269 $G = isset($Settings["G"]) ? $Settings["G"] : $this->Default["G"];
270 $B = isset($Settings["B"]) ? $Settings["B"] : $this->Default["B"];
271 $Alpha = isset($Settings["Alpha"]) ? $Settings["Alpha"] : $this->Default["Alpha"];
272 $BorderR = isset($Settings["BorderR"]) ? $Settings["BorderR"] : $this->Default["BorderR"];
273 $BorderG = isset($Settings["BorderG"]) ? $Settings["BorderG"] : $this->Default["BorderG"];
274 $BorderB = isset($Settings["BorderB"]) ? $Settings["BorderB"] : $this->Default["BorderB"];
275 $BorderAlpha = isset($Settings["BorderAlpha"]) ? $Settings["BorderAlpha"] : $this->Default["BorderAlpha"];
276 $Surrounding = isset($Settings["Surrounding"]) ? $Settings["Surrounding"] : $this->Default["Surrounding"];
277 $BackgroundR = isset($Settings["BackgroundR"]) ? $Settings["BackgroundR"] : $this->Default["BackgroundR"];
278 $BackgroundG = isset($Settings["BackgroundG"]) ? $Settings["BackgroundG"] : $this->Default["BackgroundG"];
279 $BackgroundB = isset($Settings["BackgroundB"]) ? $Settings["BackgroundB"] : $this->Default["BackgroundB"];
280 $BackgroundAlpha = isset($Settings["BackgroundAlpha"]) ? $Settings["BackgroundAlpha"] : $this->Default["BackgroundAlpha"];
281 $Force = isset($Settings["Force"]) ? $Settings["Force"] : $this->Default["Force"];
282 $NodeType = isset($Settings["NodeType"]) ? $Settings["NodeType"] : $this->Default["NodeType"];
283 $Size = isset($Settings["Size"]) ? $Settings["Size"] : $this->Default["Size"];
284 $Shape = isset($Settings["Shape"]) ? $Settings["Shape"] : $this->Default["Shape"];
285 $FreeZone = isset($Settings["FreeZone"]) ? $Settings["FreeZone"] : $this->Default["FreeZone"];
286 if ($Surrounding != null) {
287 $BorderR = $R + $Surrounding;
288 $BorderG = $G + $Surrounding;
289 $BorderB = $B + $Surrounding;
290 }
291 $this->Data[$NodeID]["R"] = $R;
292 $this->Data[$NodeID]["G"] = $G;
293 $this->Data[$NodeID]["B"] = $B;
294 $this->Data[$NodeID]["Alpha"] = $Alpha;
295 $this->Data[$NodeID]["BorderR"] = $BorderR;
296 $this->Data[$NodeID]["BorderG"] = $BorderG;
297 $this->Data[$NodeID]["BorderB"] = $BorderB;
298 $this->Data[$NodeID]["BorderAlpha"] = $BorderAlpha;
299 $this->Data[$NodeID]["BackgroundR"] = $BackgroundR;
300 $this->Data[$NodeID]["BackgroundG"] = $BackgroundG;
301 $this->Data[$NodeID]["BackgroundB"] = $BackgroundB;
302 $this->Data[$NodeID]["BackgroundAlpha"] = $BackgroundAlpha;
303 $this->Data[$NodeID]["Name"] = $Name;
304 $this->Data[$NodeID]["Force"] = $Force;
305 $this->Data[$NodeID]["Type"] = $NodeType;
306 $this->Data[$NodeID]["Size"] = $Size;
307 $this->Data[$NodeID]["Shape"] = $Shape;
308 $this->Data[$NodeID]["FreeZone"] = $FreeZone;
309 if ($Connections != null) {
310 if (is_array($Connections)) {
311 foreach ($Connections as $Key => $Value) {
312 $this->Data[$NodeID]["Connections"][] = $Value;
313 }
314 } else {
315 $this->Data[$NodeID]["Connections"][] = $Connections;
316 }
317 }
318 }
319 /**
320 * Set color attribute for a list of nodes
321 * @param array|string $Nodes
322 * @param array $Settings
323 */
324 public function setNodesColor($Nodes, array $Settings = [])
325 {
326 if (is_array($Nodes)) {
327 foreach ($Nodes as $Key => $NodeID) {
328 if (isset($this->Data[$NodeID])) {
329 if (isset($Settings["R"])) {
330 $this->Data[$NodeID]["R"] = $Settings["R"];
331 }
332 if (isset($Settings["G"])) {
333 $this->Data[$NodeID]["G"] = $Settings["G"];
334 }
335 if (isset($Settings["B"])) {
336 $this->Data[$NodeID]["B"] = $Settings["B"];
337 }
338 if (isset($Settings["Alpha"])) {
339 $this->Data[$NodeID]["Alpha"] = $Settings["Alpha"];
340 }
341 if (isset($Settings["BorderR"])) {
342 $this->Data[$NodeID]["BorderR"] = $Settings["BorderR"];
343 }
344 if (isset($Settings["BorderG"])) {
345 $this->Data[$NodeID]["BorderG"] = $Settings["BorderG"];
346 }
347 if (isset($Settings["BorderB"])) {
348 $this->Data[$NodeID]["BorderB"] = $Settings["BorderB"];
349 }
350 if (isset($Settings["BorderAlpha"])) {
351 $this->Data[$NodeID]["BorderAlpha"] = $Settings["BorderAlpha"];
352 }
353 if (isset($Settings["Surrounding"])) {
354 $this->Data[$NodeID]["BorderR"] = $this->Data[$NodeID]["R"] + $Settings["Surrounding"];
355 $this->Data[$NodeID]["BorderG"] = $this->Data[$NodeID]["G"] + $Settings["Surrounding"];
356 $this->Data[$NodeID]["BorderB"] = $this->Data[$NodeID]["B"] + $Settings["Surrounding"];
357 }
358 }
359 }
360 } else {
361 if (isset($Settings["R"])) {
362 $this->Data[$Nodes]["R"] = $Settings["R"];
363 }
364 if (isset($Settings["G"])) {
365 $this->Data[$Nodes]["G"] = $Settings["G"];
366 }
367 if (isset($Settings["B"])) {
368 $this->Data[$Nodes]["B"] = $Settings["B"];
369 }
370 if (isset($Settings["Alpha"])) {
371 $this->Data[$Nodes]["Alpha"] = $Settings["Alpha"];
372 }
373 if (isset($Settings["BorderR"])) {
374 $this->Data[$Nodes]["BorderR"] = $Settings["BorderR"];
375 }
376 if (isset($Settings["BorderG"])) {
377 $this->Data[$Nodes]["BorderG"] = $Settings["BorderG"];
378 }
379 if (isset($Settings["BorderB"])) {
380 $this->Data[$Nodes]["BorderB"] = $Settings["BorderB"];
381 }
382 if (isset($Settings["BorderAlpha"])) {
383 $this->Data[$Nodes]["BorderAlpha"] = $Settings["BorderAlpha"];
384 }
385 if (isset($Settings["Surrounding"])) {
386 $this->Data[$Nodes]["BorderR"] = $this->Data[$NodeID]["R"] + $Settings["Surrounding"];
387 $this->Data[$NodeID]["BorderG"] = $this->Data[$NodeID]["G"] + $Settings["Surrounding"];
388 $this->Data[$NodeID]["BorderB"] = $this->Data[$NodeID]["B"] + $Settings["Surrounding"];
389 }
390 }
391 }
392 /**
393 * Returns all the nodes details
394 * @return array
395 */
396 public function dumpNodes()
397 {
398 return $this->Data;
399 }
400 /**
401 * Check if a connection exists and create it if required
402 * @param string|int $SourceID
403 * @param string|int $TargetID
404 * @return boolean|null
405 */
406 public function checkConnection($SourceID, $TargetID)
407 {
408 if (isset($this->Data[$SourceID]["Connections"])) {
409 foreach ($this->Data[$SourceID]["Connections"] as $ConnectionID) {
410 if ($TargetID == $ConnectionID) {
411 return \true;
412 }
413 }
414 }
415 $this->Data[$SourceID]["Connections"][] = $TargetID;
416 }
417 /**
418 * Get the median linked nodes position
419 * @param string $Key
420 * @param int $X
421 * @param int $Y
422 * @return array
423 */
424 public function getMedianOffset($Key, $X, $Y)
425 {
426 $Cpt = 1;
427 if (isset($this->Data[$Key]["Connections"])) {
428 foreach ($this->Data[$Key]["Connections"] as $NodeID) {
429 if (isset($this->Data[$NodeID]["X"]) && isset($this->Data[$NodeID]["Y"])) {
430 $X = $X + $this->Data[$NodeID]["X"];
431 $Y = $Y + $this->Data[$NodeID]["Y"];
432 $Cpt++;
433 }
434 }
435 }
436 return ["X" => $X / $Cpt, "Y" => $Y / $Cpt];
437 }
438 /**
439 * Return the ID of the attached partner with the biggest weight
440 * @param string $Key
441 * @return string
442 */
443 public function getBiggestPartner($Key)
444 {
445 if (!isset($this->Data[$Key]["Connections"])) {
446 return "";
447 }
448 $MaxWeight = 0;
449 $Result = "";
450 foreach ($this->Data[$Key]["Connections"] as $Key => $PeerID) {
451 if ($this->Data[$PeerID]["Weight"] > $MaxWeight) {
452 $MaxWeight = $this->Data[$PeerID]["Weight"];
453 $Result = $PeerID;
454 }
455 }
456 return $Result;
457 }
458 /**
459 * Do the initial node positions computing pass
460 * @param int $Algorithm
461 */
462 public function firstPass($Algorithm)
463 {
464 $CenterX = ($this->X2 - $this->X1) / 2 + $this->X1;
465 $CenterY = ($this->Y2 - $this->Y1) / 2 + $this->Y1;
466 /* Check connections reciprocity */
467 foreach ($this->Data as $Key => $Settings) {
468 if (isset($Settings["Connections"])) {
469 foreach ($Settings["Connections"] as $ID => $ConnectionID) {
470 $this->checkConnection($ConnectionID, $Key);
471 }
472 }
473 }
474 if ($this->AutoComputeFreeZone) {
475 $this->autoFreeZone();
476 }
477 /* Get the max number of connections */
478 $MaxConnections = 0;
479 foreach ($this->Data as $Key => $Settings) {
480 if (isset($Settings["Connections"])) {
481 if ($MaxConnections < count($Settings["Connections"])) {
482 $MaxConnections = count($Settings["Connections"]);
483 }
484 }
485 }
486 if ($Algorithm == ALGORITHM_WEIGHTED) {
487 foreach ($this->Data as $Key => $Settings) {
488 if ($Settings["Type"] == NODE_TYPE_CENTRAL) {
489 $this->Data[$Key]["X"] = $CenterX;
490 $this->Data[$Key]["Y"] = $CenterY;
491 }
492 if ($Settings["Type"] == NODE_TYPE_FREE) {
493 if (isset($Settings["Connections"])) {
494 $Connections = count($Settings["Connections"]);
495 } else {
496 $Connections = 0;
497 }
498 $Ring = $MaxConnections - $Connections;
499 $Angle = rand(0, 360);
500 $this->Data[$Key]["X"] = cos(deg2rad($Angle)) * ($Ring * $this->RingSize) + $CenterX;
501 $this->Data[$Key]["Y"] = sin(deg2rad($Angle)) * ($Ring * $this->RingSize) + $CenterY;
502 }
503 }
504 } elseif ($Algorithm == ALGORITHM_CENTRAL) {
505 /* Put a weight on each nodes */
506 foreach ($this->Data as $Key => $Settings) {
507 if (isset($Settings["Connections"])) {
508 $this->Data[$Key]["Weight"] = count($Settings["Connections"]);
509 } else {
510 $this->Data[$Key]["Weight"] = 0;
511 }
512 }
513 $MaxConnections = $MaxConnections + 1;
514 for ($i = $MaxConnections; $i >= 0; $i--) {
515 foreach ($this->Data as $Key => $Settings) {
516 if ($Settings["Type"] == NODE_TYPE_CENTRAL) {
517 $this->Data[$Key]["X"] = $CenterX;
518 $this->Data[$Key]["Y"] = $CenterY;
519 }
520 if ($Settings["Type"] == NODE_TYPE_FREE) {
521 if (isset($Settings["Connections"])) {
522 $Connections = count($Settings["Connections"]);
523 } else {
524 $Connections = 0;
525 }
526 if ($Connections == $i) {
527 $BiggestPartner = $this->getBiggestPartner($Key);
528 if ($BiggestPartner != "") {
529 $Ring = $this->Data[$BiggestPartner]["FreeZone"];
530 $Weight = $this->Data[$BiggestPartner]["Weight"];
531 $AngleDivision = 360 / $this->Data[$BiggestPartner]["Weight"];
532 $Done = \false;
533 $Tries = 0;
534 while (!$Done && $Tries <= $Weight * 2) {
535 $Tries++;
536 $Angle = floor(rand(0, $Weight) * $AngleDivision);
537 if (!isset($this->Data[$BiggestPartner]["Angular"][$Angle]) || !isset($this->Data[$BiggestPartner]["Angular"])) {
538 $this->Data[$BiggestPartner]["Angular"][$Angle] = $Angle;
539 $Done = \true;
540 }
541 }
542 if (!$Done) {
543 $Angle = rand(0, 360);
544 $this->Data[$BiggestPartner]["Angular"][$Angle] = $Angle;
545 }
546 $X = cos(deg2rad($Angle)) * $Ring + $this->Data[$BiggestPartner]["X"];
547 $Y = sin(deg2rad($Angle)) * $Ring + $this->Data[$BiggestPartner]["Y"];
548 $this->Data[$Key]["X"] = $X;
549 $this->Data[$Key]["Y"] = $Y;
550 }
551 }
552 }
553 }
554 }
555 } elseif ($Algorithm == ALGORITHM_CIRCULAR) {
556 $MaxConnections = $MaxConnections + 1;
557 for ($i = $MaxConnections; $i >= 0; $i--) {
558 foreach ($this->Data as $Key => $Settings) {
559 if ($Settings["Type"] == NODE_TYPE_CENTRAL) {
560 $this->Data[$Key]["X"] = $CenterX;
561 $this->Data[$Key]["Y"] = $CenterY;
562 }
563 if ($Settings["Type"] == NODE_TYPE_FREE) {
564 if (isset($Settings["Connections"])) {
565 $Connections = count($Settings["Connections"]);
566 } else {
567 $Connections = 0;
568 }
569 if ($Connections == $i) {
570 $Ring = $MaxConnections - $Connections;
571 $Angle = rand(0, 360);
572 $X = cos(deg2rad($Angle)) * ($Ring * $this->RingSize) + $CenterX;
573 $Y = sin(deg2rad($Angle)) * ($Ring * $this->RingSize) + $CenterY;
574 $MedianOffset = $this->getMedianOffset($Key, $X, $Y);
575 $this->Data[$Key]["X"] = $MedianOffset["X"];
576 $this->Data[$Key]["Y"] = $MedianOffset["Y"];
577 }
578 }
579 }
580 }
581 } elseif ($Algorithm == ALGORITHM_RANDOM) {
582 foreach ($this->Data as $Key => $Settings) {
583 if ($Settings["Type"] == NODE_TYPE_FREE) {
584 $this->Data[$Key]["X"] = $CenterX + rand(-20, 20);
585 $this->Data[$Key]["Y"] = $CenterY + rand(-20, 20);
586 }
587 if ($Settings["Type"] == NODE_TYPE_CENTRAL) {
588 $this->Data[$Key]["X"] = $CenterX;
589 $this->Data[$Key]["Y"] = $CenterY;
590 }
591 }
592 }
593 }
594 /**
595 * Compute one pass
596 */
597 public function doPass()
598 {
599 /* Compute vectors */
600 foreach ($this->Data as $Key => $Settings) {
601 if ($Settings["Type"] != NODE_TYPE_CENTRAL) {
602 unset($this->Data[$Key]["Vectors"]);
603 $X1 = $Settings["X"];
604 $Y1 = $Settings["Y"];
605 /* Repulsion vectors */
606 foreach ($this->Data as $Key2 => $Settings2) {
607 if ($Key != $Key2) {
608 $X2 = $this->Data[$Key2]["X"];
609 $Y2 = $this->Data[$Key2]["Y"];
610 $FreeZone = $this->Data[$Key2]["FreeZone"];
611 $Distance = $this->getDistance($X1, $Y1, $X2, $Y2);
612 $Angle = $this->getAngle($X1, $Y1, $X2, $Y2) + 180;
613 /* Nodes too close, repulsion occurs */
614 if ($Distance < $FreeZone) {
615 $Force = log(pow(2, $FreeZone - $Distance));
616 if ($Force > 1) {
617 $this->Data[$Key]["Vectors"][] = ["Type" => "R", "Angle" => (int) $Angle % 360, "Force" => $Force];
618 }
619 }
620 }
621 }
622 /* Attraction vectors */
623 if (isset($Settings["Connections"])) {
624 foreach ($Settings["Connections"] as $ID => $NodeID) {
625 if (isset($this->Data[$NodeID])) {
626 $X2 = $this->Data[$NodeID]["X"];
627 $Y2 = $this->Data[$NodeID]["Y"];
628 $FreeZone = $this->Data[$Key2]["FreeZone"];
629 $Distance = $this->getDistance($X1, $Y1, $X2, $Y2);
630 $Angle = $this->getAngle($X1, $Y1, $X2, $Y2);
631 if ($Distance > $FreeZone) {
632 $Force = log($Distance - $FreeZone + 1);
633 } else {
634 $Force = log($FreeZone - $Distance + 1);
635 $Angle = $Angle + 180;
636 }
637 if ($Force > 1) {
638 $this->Data[$Key]["Vectors"][] = ["Type" => "A", "Angle" => (int) $Angle % 360, "Force" => $Force];
639 }
640 }
641 }
642 }
643 }
644 }
645 /* Move the nodes accoding to the vectors */
646 foreach ($this->Data as $Key => $Settings) {
647 $X = $Settings["X"];
648 $Y = $Settings["Y"];
649 if (isset($Settings["Vectors"]) && $Settings["Type"] != NODE_TYPE_CENTRAL) {
650 foreach ($Settings["Vectors"] as $ID => $Vector) {
651 $Type = $Vector["Type"];
652 $Force = $Vector["Force"];
653 $Angle = $Vector["Angle"];
654 $Factor = $Type == "A" ? $this->MagneticForceA : $this->MagneticForceR;
655 $X = cos(deg2rad($Angle)) * $Force * $Factor + $X;
656 $Y = sin(deg2rad($Angle)) * $Force * $Factor + $Y;
657 }
658 }
659 $this->Data[$Key]["X"] = $X;
660 $this->Data[$Key]["Y"] = $Y;
661 }
662 }
663 /**
664 * @return int|float
665 */
666 public function lastPass()
667 {
668 /* Put everything inside the graph area */
669 foreach ($this->Data as $Key => $Settings) {
670 $X = $Settings["X"];
671 $Y = $Settings["Y"];
672 if ($X < $this->X1) {
673 $X = $this->X1;
674 }
675 if ($X > $this->X2) {
676 $X = $this->X2;
677 }
678 if ($Y < $this->Y1) {
679 $Y = $this->Y1;
680 }
681 if ($Y > $this->Y2) {
682 $Y = $this->Y2;
683 }
684 $this->Data[$Key]["X"] = $X;
685 $this->Data[$Key]["Y"] = $Y;
686 }
687 /* Dump all links */
688 $Links = [];
689 foreach ($this->Data as $Key => $Settings) {
690 $X1 = $Settings["X"];
691 $Y1 = $Settings["Y"];
692 if (isset($Settings["Connections"])) {
693 foreach ($Settings["Connections"] as $ID => $NodeID) {
694 if (isset($this->Data[$NodeID])) {
695 $X2 = $this->Data[$NodeID]["X"];
696 $Y2 = $this->Data[$NodeID]["Y"];
697 $Links[] = ["X1" => $X1, "Y1" => $Y1, "X2" => $X2, "Y2" => $Y2, "Source" => $Settings["Name"], "Destination" => $this->Data[$NodeID]["Name"]];
698 }
699 }
700 }
701 }
702 /* Check collisions */
703 $Conflicts = 0;
704 foreach ($this->Data as $Key => $Settings) {
705 $X1 = $Settings["X"];
706 $Y1 = $Settings["Y"];
707 if (isset($Settings["Connections"])) {
708 foreach ($Settings["Connections"] as $ID => $NodeID) {
709 if (isset($this->Data[$NodeID])) {
710 $X2 = $this->Data[$NodeID]["X"];
711 $Y2 = $this->Data[$NodeID]["Y"];
712 foreach ($Links as $IDLinks => $Link) {
713 $X3 = $Link["X1"];
714 $Y3 = $Link["Y1"];
715 $X4 = $Link["X2"];
716 $Y4 = $Link["Y2"];
717 if (!($X1 == $X3 && $X2 == $X4 && $Y1 == $Y3 && $Y2 == $Y4)) {
718 if ($this->intersect($X1, $Y1, $X2, $Y2, $X3, $Y3, $X4, $Y4)) {
719 if ($Link["Source"] != $Settings["Name"] && $Link["Source"] != $this->Data[$NodeID]["Name"] && $Link["Destination"] != $Settings["Name"] && $Link["Destination"] != $this->Data[$NodeID]["Name"]) {
720 $Conflicts++;
721 }
722 }
723 }
724 }
725 }
726 }
727 }
728 }
729 return $Conflicts / 2;
730 }
731 /**
732 * Center the graph
733 */
734 public function center()
735 {
736 /* Determine the real center */
737 $TargetCenterX = ($this->X2 - $this->X1) / 2 + $this->X1;
738 $TargetCenterY = ($this->Y2 - $this->Y1) / 2 + $this->Y1;
739 /* Get current boundaries */
740 $XMin = $this->X2;
741 $XMax = $this->X1;
742 $YMin = $this->Y2;
743 $YMax = $this->Y1;
744 foreach ($this->Data as $Key => $Settings) {
745 $X = $Settings["X"];
746 $Y = $Settings["Y"];
747 if ($X < $XMin) {
748 $XMin = $X;
749 }
750 if ($X > $XMax) {
751 $XMax = $X;
752 }
753 if ($Y < $YMin) {
754 $YMin = $Y;
755 }
756 if ($Y > $YMax) {
757 $YMax = $Y;
758 }
759 }
760 $CurrentCenterX = ($XMax - $XMin) / 2 + $XMin;
761 $CurrentCenterY = ($YMax - $YMin) / 2 + $YMin;
762 /* Compute the offset to apply */
763 $XOffset = $TargetCenterX - $CurrentCenterX;
764 $YOffset = $TargetCenterY - $CurrentCenterY;
765 /* Correct the points position */
766 foreach ($this->Data as $Key => $Settings) {
767 $this->Data[$Key]["X"] = $Settings["X"] + $XOffset;
768 $this->Data[$Key]["Y"] = $Settings["Y"] + $YOffset;
769 }
770 }
771 /**
772 * Create the encoded string
773 * @param Image $Object
774 * @param string $Settings
775 * @return array
776 */
777 public function drawSpring(Image $Object, array $Settings = [])
778 {
779 $this->pChartObject = $Object;
780 $Pass = isset($Settings["Pass"]) ? $Settings["Pass"] : 50;
781 $Retries = isset($Settings["Retry"]) ? $Settings["Retry"] : 10;
782 $this->MagneticForceA = isset($Settings["MagneticForceA"]) ? $Settings["MagneticForceA"] : 1.5;
783 $this->MagneticForceR = isset($Settings["MagneticForceR"]) ? $Settings["MagneticForceR"] : 2;
784 $this->RingSize = isset($Settings["RingSize"]) ? $Settings["RingSize"] : 40;
785 $DrawVectors = isset($Settings["DrawVectors"]) ? $Settings["DrawVectors"] : \false;
786 $DrawQuietZone = isset($Settings["DrawQuietZone"]) ? $Settings["DrawQuietZone"] : \false;
787 $CenterGraph = isset($Settings["CenterGraph"]) ? $Settings["CenterGraph"] : \true;
788 $TextPadding = isset($Settings["TextPadding"]) ? $Settings["TextPadding"] : 4;
789 $Algorithm = isset($Settings["Algorithm"]) ? $Settings["Algorithm"] : ALGORITHM_WEIGHTED;
790 $this->X1 = $Object->GraphAreaX1;
791 $this->Y1 = $Object->GraphAreaY1;
792 $this->X2 = $Object->GraphAreaX2;
793 $this->Y2 = $Object->GraphAreaY2;
794 $Conflicts = 1;
795 $Jobs = 0;
796 $this->History["MinimumConflicts"] = -1;
797 while ($Conflicts != 0 && $Jobs < $Retries) {
798 $Jobs++;
799 /* Compute the initial settings */
800 $this->firstPass($Algorithm);
801 /* Apply the vectors */
802 if ($Pass > 0) {
803 for ($i = 0; $i <= $Pass; $i++) {
804 $this->doPass();
805 }
806 }
807 $Conflicts = $this->lastPass();
808 if ($this->History["MinimumConflicts"] == -1 || $Conflicts < $this->History["MinimumConflicts"]) {
809 $this->History["MinimumConflicts"] = $Conflicts;
810 $this->History["Result"] = $this->Data;
811 }
812 }
813 $Conflicts = $this->History["MinimumConflicts"];
814 $this->Data = $this->History["Result"];
815 if ($CenterGraph) {
816 $this->center();
817 }
818 /* Draw the connections */
819 $Drawn = [];
820 foreach ($this->Data as $Key => $Settings) {
821 $X = $Settings["X"];
822 $Y = $Settings["Y"];
823 if (isset($Settings["Connections"])) {
824 foreach ($Settings["Connections"] as $ID => $NodeID) {
825 if (!isset($Drawn[$Key])) {
826 $Drawn[$Key] = "";
827 }
828 if (!isset($Drawn[$NodeID])) {
829 $Drawn[$NodeID] = "";
830 }
831 if (isset($this->Data[$NodeID]) && !isset($Drawn[$Key][$NodeID]) && !isset($Drawn[$NodeID][$Key])) {
832 $Color = ["R" => $this->Default["LinkR"], "G" => $this->Default["LinkG"], "B" => $this->Default["LinkB"], "Alpha" => $this->Default["Alpha"]];
833 if (count($this->Links)) {
834 if (isset($this->Links[$Key][$NodeID]["R"])) {
835 $Color = ["R" => $this->Links[$Key][$NodeID]["R"], "G" => $this->Links[$Key][$NodeID]["G"], "B" => $this->Links[$Key][$NodeID]["B"], "Alpha" => $this->Links[$Key][$NodeID]["Alpha"]];
836 }
837 if (isset($this->Links[$Key][$NodeID]["Ticks"])) {
838 $Color["Ticks"] = $this->Links[$Key][$NodeID]["Ticks"];
839 }
840 }
841 $X2 = $this->Data[$NodeID]["X"];
842 $Y2 = $this->Data[$NodeID]["Y"];
843 $this->pChartObject->drawLine($X, $Y, $X2, $Y2, $Color);
844 $Drawn[$Key][$NodeID] = \true;
845 if (isset($this->Links) && count($this->Links)) {
846 if (isset($this->Links[$Key][$NodeID]["Name"]) || isset($this->Links[$NodeID][$Key]["Name"])) {
847 $Name = isset($this->Links[$Key][$NodeID]["Name"]) ? $this->Links[$Key][$NodeID]["Name"] : $this->Links[$NodeID][$Key]["Name"];
848 $TxtX = ($X2 - $X) / 2 + $X;
849 $TxtY = ($Y2 - $Y) / 2 + $Y;
850 if ($X <= $X2) {
851 $Angle = (360 - $this->getAngle($X, $Y, $X2, $Y2)) % 360;
852 } else {
853 $Angle = (360 - $this->getAngle($X2, $Y2, $X, $Y)) % 360;
854 }
855 $Settings = $Color;
856 $Settings["Angle"] = $Angle;
857 $Settings["Align"] = TEXT_ALIGN_BOTTOMMIDDLE;
858 $this->pChartObject->drawText($TxtX, $TxtY, $Name, $Settings);
859 }
860 }
861 }
862 }
863 }
864 }
865 /* Draw the quiet zones */
866 if ($DrawQuietZone) {
867 foreach ($this->Data as $Key => $Settings) {
868 $X = $Settings["X"];
869 $Y = $Settings["Y"];
870 $FreeZone = $Settings["FreeZone"];
871 $this->pChartObject->drawFilledCircle($X, $Y, $FreeZone, ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 2]);
872 }
873 }
874 /* Draw the nodes */
875 foreach ($this->Data as $Key => $Settings) {
876 $X = $Settings["X"];
877 $Y = $Settings["Y"];
878 $Name = $Settings["Name"];
879 $FreeZone = $Settings["FreeZone"];
880 $Shape = $Settings["Shape"];
881 $Size = $Settings["Size"];
882 $Color = ["R" => $Settings["R"], "G" => $Settings["G"], "B" => $Settings["B"], "Alpha" => $Settings["Alpha"], "BorderR" => $Settings["BorderR"], "BorderG" => $Settings["BorderG"], "BorderB" => $Settings["BorderB"], "BorderApha" => $Settings["BorderAlpha"]];
883 if ($Shape == NODE_SHAPE_CIRCLE) {
884 $this->pChartObject->drawFilledCircle($X, $Y, $Size, $Color);
885 } elseif ($Shape == NODE_SHAPE_TRIANGLE) {
886 $Points = [];
887 $Points[] = cos(deg2rad(270)) * $Size + $X;
888 $Points[] = sin(deg2rad(270)) * $Size + $Y;
889 $Points[] = cos(deg2rad(45)) * $Size + $X;
890 $Points[] = sin(deg2rad(45)) * $Size + $Y;
891 $Points[] = cos(deg2rad(135)) * $Size + $X;
892 $Points[] = sin(deg2rad(135)) * $Size + $Y;
893 $this->pChartObject->drawPolygon($Points, $Color);
894 } elseif ($Shape == NODE_SHAPE_SQUARE) {
895 $Offset = $Size / 2;
896 $Size = $Size / 2;
897 $this->pChartObject->drawFilledRectangle($X - $Offset, $Y - $Offset, $X + $Offset, $Y + $Offset, $Color);
898 }
899 if ($Name != "") {
900 $LabelOptions = ["R" => $this->Labels["R"], "G" => $this->Labels["G"], "B" => $this->Labels["B"], "Alpha" => $this->Labels["Alpha"]];
901 if ($this->Labels["Type"] == LABEL_LIGHT) {
902 $LabelOptions["Align"] = TEXT_ALIGN_BOTTOMLEFT;
903 $this->pChartObject->drawText($X, $Y, $Name, $LabelOptions);
904 } elseif ($this->Labels["Type"] == LABEL_CLASSIC) {
905 $LabelOptions["Align"] = TEXT_ALIGN_TOPMIDDLE;
906 $LabelOptions["DrawBox"] = \true;
907 $LabelOptions["BoxAlpha"] = 50;
908 $LabelOptions["BorderOffset"] = 4;
909 $LabelOptions["RoundedRadius"] = 3;
910 $LabelOptions["BoxRounded"] = \true;
911 $LabelOptions["NoShadow"] = \true;
912 $this->pChartObject->drawText($X, $Y + $Size + $TextPadding, $Name, $LabelOptions);
913 }
914 }
915 }
916 /* Draw the vectors */
917 if ($DrawVectors) {
918 foreach ($this->Data as $Key => $Settings) {
919 $X1 = $Settings["X"];
920 $Y1 = $Settings["Y"];
921 if (isset($Settings["Vectors"]) && $Settings["Type"] != NODE_TYPE_CENTRAL) {
922 foreach ($Settings["Vectors"] as $ID => $Vector) {
923 $Type = $Vector["Type"];
924 $Force = $Vector["Force"];
925 $Angle = $Vector["Angle"];
926 $Factor = $Type == "A" ? $this->MagneticForceA : $this->MagneticForceR;
927 $Color = $Type == "A" ? ["FillR" => 255, "FillG" => 0, "FillB" => 0] : ["FillR" => 0, "FillG" => 255, "FillB" => 0];
928 $X2 = cos(deg2rad($Angle)) * $Force * $Factor + $X1;
929 $Y2 = sin(deg2rad($Angle)) * $Force * $Factor + $Y1;
930 $this->pChartObject->drawArrow($X1, $Y1, $X2, $Y2, $Color);
931 }
932 }
933 }
934 }
935 return ["Pass" => $Jobs, "Conflicts" => $Conflicts];
936 }
937 /**
938 * Return the distance between two points
939 * @param int $X1
940 * @param int $Y1
941 * @param int $X2
942 * @param int $Y2
943 * @return int|float
944 */
945 public function getDistance($X1, $Y1, $X2, $Y2)
946 {
947 return sqrt(($X2 - $X1) * ($X2 - $X1) + ($Y2 - $Y1) * ($Y2 - $Y1));
948 }
949 /**
950 * Return the angle made by a line and the X axis
951 * @param int $X1
952 * @param int $Y1
953 * @param int $X2
954 * @param int $Y2
955 * @return int|float
956 */
957 public function getAngle($X1, $Y1, $X2, $Y2)
958 {
959 $Opposite = $Y2 - $Y1;
960 $Adjacent = $X2 - $X1;
961 $Angle = rad2deg(atan2($Opposite, $Adjacent));
962 return $Angle > 0 ? $Angle : 360 - abs($Angle);
963 }
964 /**
965 * @param int $X1
966 * @param int $Y1
967 * @param int $X2
968 * @param int $Y2
969 * @param int $X3
970 * @param int $Y3
971 * @param int $X4
972 * @param int $Y4
973 * @return boolean
974 */
975 public function intersect($X1, $Y1, $X2, $Y2, $X3, $Y3, $X4, $Y4)
976 {
977 $A = ($X3 * $Y4 - $X4 * $Y3) * ($X1 - $X2) - ($X1 * $Y2 - $X2 * $Y1) * ($X3 - $X4);
978 $B = ($Y1 - $Y2) * ($X3 - $X4) - ($Y3 - $Y4) * ($X1 - $X2);
979 if ($B == 0) {
980 return \false;
981 }
982 $Xi = $A / $B;
983 $C = $X1 - $X2;
984 if ($C == 0) {
985 return \false;
986 }
987 $Yi = $Xi * (($Y1 - $Y2) / $C) + ($X1 * $Y2 - $X2 * $Y1) / $C;
988 if ($Xi >= min($X1, $X2) && $Xi >= min($X3, $X4) && $Xi <= max($X1, $X2) && $Xi <= max($X3, $X4) && $Yi >= min($Y1, $Y2) && $Yi >= min($Y3, $Y4) && $Yi <= max($Y1, $Y2) && $Yi <= max($Y3, $Y4)) {
989 return \true;
990 }
991 return \false;
992 }
993 }
994