TreeGraph::next()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
rs 9.7998
cc 3
nc 4
nop 0
1
<?php
2
3
namespace Alpha\Util\Graph;
4
5
use Alpha\Util\Logging\Logger;
6
7
/**
8
 * Maintains the geometry for a tree graph.
9
 *
10
 * @since 1.0
11
 *
12
 * @author John Collins <[email protected]>
13
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
14
 * @copyright Copyright (c) 2018, John Collins (founder of Alpha Framework).
15
 * All rights reserved.
16
 *
17
 * <pre>
18
 * Redistribution and use in source and binary forms, with or
19
 * without modification, are permitted provided that the
20
 * following conditions are met:
21
 *
22
 * * Redistributions of source code must retain the above
23
 *   copyright notice, this list of conditions and the
24
 *   following disclaimer.
25
 * * Redistributions in binary form must reproduce the above
26
 *   copyright notice, this list of conditions and the
27
 *   following disclaimer in the documentation and/or other
28
 *   materials provided with the distribution.
29
 * * Neither the name of the Alpha Framework nor the names
30
 *   of its contributors may be used to endorse or promote
31
 *   products derived from this software without specific
32
 *   prior written permission.
33
 *
34
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
35
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
36
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
37
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
39
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
44
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
45
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
46
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47
 * </pre>
48
 */
49
class TreeGraph
50
{
51
    /**
52
     * An array of nodes on the previous level.
53
     *
54
     * @var array
55
     *
56
     * @since 1.0
57
     */
58
    private $previousLevelNodes = array();
59
60
    /**
61
     * An array of nodes in this graph.
62
     *
63
     * @var array
64
     *
65
     * @since 1.0
66
     */
67
    private $nodes = array();
68
69
    /**
70
     * The root node of the graph.
71
     *
72
     * @var \Alpha\Util\Graph\GraphNode
73
     *
74
     * @since 1.0
75
     */
76
    private $root;
77
78
    /**
79
     * The amount of space between graph rows.
80
     *
81
     * @var int
82
     *
83
     * @since 1.0
84
     */
85
    private $rowSpace;
86
87
    /**
88
     * The amount of space between graph columns.
89
     *
90
     * @var int
91
     *
92
     * @since 1.0
93
     */
94
    private $colSpace;
95
96
    /**
97
     * The amount of space between graph branches.
98
     *
99
     * @var int
100
     *
101
     * @since 1.0
102
     */
103
    private $branchSpace;
104
105
    /**
106
     * Flag to track whether the chart is rendered or not.
107
     *
108
     * @var bool
109
     *
110
     * @since 1.0
111
     */
112
    private $isRendered = false;
113
114
    /**
115
     * The index of the current node in the graph we are inspecting.
116
     *
117
     * @var int
118
     *
119
     * @since 1.0
120
     */
121
    private $position = 0;
122
123
    /**
124
     * The height of the graph.
125
     *
126
     * @var int
127
     *
128
     * @since 1.0
129
     */
130
    private $height = 0;
131
132
    /**
133
     * The width of the graph.
134
     *
135
     * @var int
136
     *
137
     * @since 1.0
138
     */
139
    private $width = 0;
140
141
    /**
142
     * Trace logger.
143
     *
144
     * @var \Alpha\Util\Logging\Logger
145
     *
146
     * @since 1.0
147
     */
148
    private static $logger = null;
149
150
    /**
151
     * Constructor.
152
     *
153
     * @param int $rowSpace
154
     * @param int $colSpace
155
     * @param int $branchSpace
156
     *
157
     * @since 1.0
158
     */
159
    public function __construct($rowSpace = 40, $colSpace = 40, $branchSpace = 80)
160
    {
161
        self::$logger = new Logger('TreeGraph');
162
163
        $this->root = new GraphNode(0, 0, 0);
164
        $this->nodes[0] = $this->root;
165
        $this->rowSpace = $rowSpace;
166
        $this->colSpace = $colSpace;
167
        $this->branchSpace = $branchSpace;
168
    }
169
170
    /**
171
     * Add a new node to the graph.
172
     *
173
     * @param int    $id
174
     * @param int    $pid
175
     * @param string $message
176
     * @param int    $w
177
     * @param int    $h
178
     * @param array  $nodeColour
179
     * @param string $URL
180
     *
181
     * @since 1.0
182
     */
183
    public function add($id, $pid, $message = '', $w = 0, $h = 0, $nodeColour, $URL)
184
    {
185
        $node = new GraphNode($id, $w, $h, $message, $nodeColour, $URL);
186
187
        if (isset($this->nodes[$pid])) {
188
            $pnode = $this->nodes[$pid];
189
            $node->setParentNode($pnode);
190
            $pnode->addChild($node);
191
        } else {
192
            $pnode = $this->root;
193
            $node->setParentNode($pnode);
0 ignored issues
show
Documentation introduced by
$pnode is of type object<Alpha\Util\Graph\GraphNode>, but the function expects a object<Alpha\Util\Graph\...a\Util\Graph\GraphNode>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
194
            $this->root->addChild($node);
195
        }
196
197
        $this->nodes[$id] = $node;
198
    }
199
200
    /**
201
     * Get the specified node from the graph.
202
     *
203
     * @param int    $id
204
     *
205
     * @return \Alpha\Util\Graph\GraphNode
206
     *
207
     * @since 2.0.1
208
     */
209
    public function get($id)
210
    {
211
        if (isset($this->nodes[$id])) {
212
            return $this->nodes[$id];
213
        } else {
214
            return null;
215
        }
216
    }
217
218
    /**
219
     * The first pass of the graph.
220
     *
221
     * @param \Alpha\Util\Graph\GraphNode $node
222
     * @param int                        $level
223
     *
224
     * @since 1.0
225
     */
226
    private function firstPass($node, $level)
227
    {
228
        $this->setNeighbours($node, $level);
229
230
        if ($node->childCount() == 0) {
231
            $leftSibling = $node->getLeftSibling();
232
233
            if (isset($leftSibling)) {
234
                $node->setOffset($leftSibling->getOffset()+$leftSibling->getWidth()+$this->colSpace);
235
            } else {
236
                $node->setOffset(0);
237
            }
238
        } else {
239
            $childCount = $node->childCount();
240
241
            for ($i = 0; $i < $childCount; ++$i) {
242
                $this->firstPass($node->getChildAt($i), $level+1);
243
            }
244
245
            $midPoint = $node->getChildrenCenter();
246
            $midPoint -= $node->getWidth()/2;
247
            $leftSibling = $node->getLeftSibling();
248
249
            if (isset($leftSibling)) {
250
                $node->setOffset($leftSibling->getOffset()+$leftSibling->getWidth()+$this->colSpace);
251
                $node->setModifier($node->getOffset()-$midPoint);
252
253
                $this->layout($node, $level);
254
            } else {
255
                $node->setOffset($midPoint);
256
            }
257
        }
258
259
        self::$logger->debug('Memory usage at first scan ['.((memory_get_usage(true)/1024)/1024).' MB]');
260
    }
261
262
    /**
263
     * The second pass of the graph.
264
     *
265
     * @param \Alpha\Util\Graph\GraphNode $node
266
     * @param int                        $level
267
     * @param int                        $x
268
     * @param int                        $y
269
     *
270
     * @since 1.0
271
     */
272
    private function secondPass($node, $level, $x = 0, $y = 0)
273
    {
274
        $nodeX = $node->getOffset()+$x;
275
        $nodeY = $y;
276
277
        $node->setX($nodeX);
278
        $node->setY($nodeY);
279
280
        $this->height = ($this->height > $node->getY()+$node->getWidth()) ? $this->height : $node->getY()+$node->getWidth();
281
        $this->width = ($this->width > $nodeX+$node->getWidth()) ? $this->width : $nodeX+$node->getWidth()+10;
282
283
        if ($node->childCount() > 0) {
284
            $this->secondPass($node->getChildAt(0), $level+1, $x+$node->getModifier(), $y+$node->getHeight()+$this->rowSpace);
285
        }
286
287
        $rightSibling = $node->getRightSibling();
288
289
        if (isset($rightSibling)) {
290
            $this->secondPass($rightSibling, $level, $x, $y);
291
        }
292
293
        self::$logger->debug('Memory usage at second scan ['.((memory_get_usage(true)/1024)/1024).' MB]');
294
    }
295
296
    /**
297
     * Handles the laying out of multi-branch trees.
298
     *
299
     * @param \Alpha\Util\Graph\GraphNode $node
300
     * @param int                        $level
301
     *
302
     * @since 1.0
303
     */
304
    private function layout($node, $level)
305
    {
306
        $firstChild = $node->getChildAt(0);
307
        $firstChildLeftNeighbour = $firstChild->getLeftSibling();
308
309
        for ($j = 1; $j <= $level; ++$j) {
310
            $modifierSumRight = 0;
311
            $modifierSumLeft = 0;
312
            $rightAncestor = $firstChild;
313
            $leftAncestor = $firstChildLeftNeighbour;
314
315
            for ($l = 0; $l < $j; ++$l) {
316
                $rightAncestor = $rightAncestor->getParentNode();
317
                $leftAncestor = $leftAncestor->getParentNode();
318
                $modifierSumRight += $rightAncestor->getModifier();
319
                $modifierSumLeft += $leftAncestor->getModifier();
320
            }
321
322
            $totalGap = ($firstChildLeftNeighbour->getOffset()+$modifierSumLeft+$firstChildLeftNeighbour->getWidth()+$this->branchSpace)-($firstChild->getOffset()+$modifierSumRight);
323
324
            if ($totalGap > 0) {
325
                $subTree = $node;
326
                $subTreesCount = 0;
327
328
                while (isset($subTree) && $subTree !== $leftAncestor) {
329
                    $subTree = $subTree->getLeftSibling();
330
                    ++$subTreesCount;
331
                }
332
333
                $subTreeMove = $node;
334
                $singleGap = $totalGap/$subTreesCount;
335
336
                while (isset($subTreeMove) && $subTreeMove !== $leftAncestor) {
337
                    $subTreeMove = $subTreeMove->getLeftSibling();
338
339
                    if (isset($subTreeMove)) {
340
                        $subTreeMove->setOffset($subTreeMove->getOffset()+$totalGap);
341
                        $subTreeMove->setModifier($subTreeMove->getModifier()+$totalGap);
342
                        $totalGap -= $singleGap;
343
                    }
344
                }
345
            }
346
347
            if ($firstChild->childCount() == 0) {
348
                $firstChild = $this->getLeftmost($node, 0, $j);
349
            } else {
350
                $firstChild = $firstChild->getChildAt(0);
351
            }
352
353
            if (isset($firstChild)) {
354
                $firstChildLeftNeighbour = $firstChild->getLeftSibling();
355
            }
356
        }
357
    }
358
359
    /**
360
     * Setup neighbour nodes.
361
     *
362
     * @param \Alpha\Util\Graph\GraphNode $node
363
     * @param int                        $level
364
     *
365
     * @since 1.0
366
     */
367
    private function setNeighbours($node, $level)
368
    {
369
        if (isset($this->previousLevelNodes[$level])) {
370
            $node->setLeftSibling($this->previousLevelNodes[$level]);
371
        }
372
373
        if ($node->getLeftSibling()) {
374
            $node->getLeftSibling()->setRightSibling($node);
375
        }
376
        $this->previousLevelNodes[$level] = $node;
377
    }
378
379
    /**
380
     * Get left most node in the branch.
381
     *
382
     * @param \Alpha\Util\Graph\GraphNode $node
383
     * @param int                        $level
384
     * @param int                        $maxlevel
385
     *
386
     * @return \Alpha\Util\Graph\GraphNode
387
     *
388
     * @since 1.0
389
     */
390
    private function getLeftmost($node, $level, $maxlevel)
391
    {
392
        if ($level >= $maxlevel) {
393
            return $node;
394
        }
395
396
        $childCount = $node->childCount();
397
398
        if ($childCount == 0) {
399
            return;
400
        }
401
402
        for ($i = 0; $i < $childCount; ++$i) {
403
            $child = $node->getChildAt($i);
404
405
            $leftmostDescendant = $this->getLeftmost($child, $level+1, $maxlevel);
406
407
            if (isset($leftmostDescendant)) {
408
                return $leftmostDescendant;
409
            }
410
        }
411
412
        return;
413
    }
414
415
    /**
416
     * Render the chart in memory.
417
     *
418
     * @since 1.0
419
     */
420
    protected function render()
421
    {
422
        $this->firstPass($this->root, 0);
423
        $this->secondPass($this->root, 0);
424
425
        foreach ($this->nodes as $node) {
426
            $node->setUpLinks();
427
        }
428
429
        $this->isRendered = true;
430
    }
431
432
    /**
433
     * Get the width of the graph, will invoke render() if not already rendered.
434
     *
435
     * @since 1.0
436
     */
437
    public function getWidth()
438
    {
439
        if (!$this->isRendered) {
440
            $this->render();
441
        }
442
443
        return $this->width;
444
    }
445
446
    /**
447
     * Get the heith of the graph, will invoke render() if not already rendered.
448
     *
449
     * @since 1.0
450
     */
451
    public function getHeight()
452
    {
453
        if (!$this->isRendered) {
454
            $this->render();
455
        }
456
457
        return $this->height;
458
    }
459
460
    /**
461
     * Get the next GraphNode instance in the graph, will invoke render() if not already rendered.
462
     *
463
     * @return \Alpha\Util\Graph\GraphNode
464
     *
465
     * @since 1.0
466
     */
467
    public function next()
468
    {
469
        if (!$this->isRendered) {
470
            $this->render();
471
        }
472
473
        if (isset($this->nodes[$this->position+1])) {
474
            ++$this->position;
475
476
            return $this->nodes[$this->position];
477
        } else {
478
            return;
479
        }
480
    }
481
482
    /**
483
     * Check to see if another GraphNode instance in the graph is available.
484
     *
485
     * @return bool
486
     *
487
     * @since 1.0
488
     */
489
    public function hasNext()
490
    {
491
        if (isset($this->nodes[$this->position+1])) {
492
            return true;
493
        } else {
494
            return false;
495
        }
496
    }
497
}
498