graph.js ➔ ... ➔ Graph.buildAxisConnectionInfo   F
last analyzed

Complexity

Conditions 13
Paths 324

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
nc 324
dl 0
loc 43
rs 3.7737
c 0
b 0
f 0
nop 0

How to fix   Complexity   

Complexity

Complex classes like graph.js ➔ ... ➔ Graph.buildAxisConnectionInfo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
define(['./settings', './directions', './vector2d', './constraint/simple/empty-constraint',
2
    './constraint/simple/left-simple-constraint', './constraint/simple/right-simple-constraint',
3
    './location-directive/stick-left-location-directive', './location-directive/stick-right-location-directive',
4
    './location-directive/center-location-directive', './rectangle', './base-axis', './point2d', './path',
5
    './interval2d', './line2d', './node-point'
6
], function(settings, directions, Vector2d, EmptyConstraint,
7
    LeftSimpleConstraint, RightSimpleConstraint,
8
    StickLeftLocationDirective, StickRightLocationDirective,
9
    CenterLocationDirective, Rectangle, BaseAxis, Point2d, Path,
10
    Interval2d, Line2d, NodePoint) {
11
    'use strict';
12
13
    /**
14
     * Graph embeds logic about it's build and update procedures
15
     *
16
     * @constructor
17
     */
18
    function Graph() {
19
        this.rectangles = [];
20
        this.baseAxises = [];
21
        this.horizontalAxises = [];
22
        this.verticalAxises = [];
23
        this.nodes = {};
24
        this.mergeAxisesQueue = [];
25
        this.axisesConnectedAtLeft = [];
26
        this.axisesConnectedAtRight = [];
27
        this.centerLineMinimalRequiredWidth = 32;
28
    }
29
30
    /**
31
     * Main build function
32
     */
33
    Graph.prototype.build = function() {
34
        this.outerRect = this.rectangles.slice(1).reduce(function(prev, current) {
35
            return current.union(prev);
36
        }, this.rectangles[0].clone());
37
        this.baseAxises.push(
38
            BaseAxis.createFromInterval(
39
                this.outerRect.topSide,
40
                this,
41
                new EmptyConstraint(),
42
                new RightSimpleConstraint(this.outerRect.top),
43
                new StickRightLocationDirective()),
44
            BaseAxis.createFromInterval(
45
                this.outerRect.bottomSide,
46
                this,
47
                new LeftSimpleConstraint(this.outerRect.bottom),
48
                new EmptyConstraint(),
49
                new StickLeftLocationDirective()),
50
            BaseAxis.createFromInterval(
51
                this.outerRect.leftSide,
52
                this,
53
                new EmptyConstraint(),
54
                new RightSimpleConstraint(this.outerRect.left),
55
                new StickRightLocationDirective()),
56
            BaseAxis.createFromInterval(
57
                this.outerRect.rightSide,
58
                this,
59
                new LeftSimpleConstraint(this.outerRect.right),
60
                new EmptyConstraint(),
61
                new StickLeftLocationDirective())
62
        );
63
        this.buildCornerAxises();
64
        this.buildCenterAxises();
65
        this.buildCenterLinesBetweenNodes();
66
        this.createAxises();
67
        this.buildMergeRequests();
68
        this.mergeAxises();
69
        this.buildNodes();
70
        this.finalizeAxises();
71
    };
72
73
    /**
74
     * Returns outgoing path from initial rectangle found by cid
75
     *
76
     * @param {string} cid content id of initial rectangles passed into graph
77
     * @param {Point2d} direction of outgoing connection
78
     * @returns {Path}
79
     */
80
    Graph.prototype.getPathFromCid = function(cid, direction) {
81
        return this.getPathFrom(this.getRectByCid(cid), direction);
82
    };
83
84
    /**
85
     * Returns outgoing path from rectangle
86
     *
87
     * @param {Rectangle} rect
88
     * @param {Point2d} direction  direction of outgoing connection
89
     * @returns {Path}
90
     */
91
    Graph.prototype.getPathFrom = function(rect, direction) {
92
        var center = rect.center;
93
        var node;
94
        switch (direction.id) {
95
            case directions.BOTTOM_TO_TOP.id:
0 ignored issues
show
Bug introduced by
The variable directions seems to be never declared. If this is a global, consider adding a /** global: directions */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
96
                node = this.getNodeAt(new Point2d(center.x, center.y - 1));
97
                break;
98
            case directions.TOP_TO_BOTTOM.id:
99
                node = this.getNodeAt(new Point2d(center.x, center.y + 1));
100
                break;
101
            case directions.LEFT_TO_RIGHT.id:
102
                node = this.getNodeAt(new Point2d(center.x + 1, center.y));
103
                break;
104
            case directions.RIGHT_TO_LEFT.id:
105
                node = this.getNodeAt(new Point2d(center.x - 1, center.y));
106
                break;
107
            default:
108
                throw new Error('Not supported direction');
109
        }
110
        if (node.connections[direction.id] === void 0 || node.connections[direction.id] === null) {
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
111
            throw new Error('Path not found. Node at (' + node.x + ', ' + node.y +
112
                    '), direction(' + direction.x + ', ' + direction.y + ')');
113
        }
114
        return new Path(node.connections[direction.id], node, null);
115
    };
116
117
    /**
118
     * Finds and recturns one of initial rectangle by cid
119
     *
120
     * @param {string} cid
121
     * @returns {Rectangle}
122
     */
123
    Graph.prototype.getRectByCid = function(cid) {
124
        for (var i = 0; i < this.rectangles.length; i++) {
125
            var rect = this.rectangles[i];
126
            if (rect.cid === cid) {
127
                return rect;
128
            }
129
        }
130
        return null;
131
    };
132
133
    /**
134
     * Draws graph
135
     */
136
    Graph.prototype.draw = function() {
137
        var i;
138
        this.outerRect.draw('red');
139
        function drawFn(item) {
140
            return item.draw('cyan');
141
        }
142
        for (i = this.horizontalAxises.length - 1; i >= 0; i--) {
143
            this.horizontalAxises[i].allClones.forEach(drawFn);
144
        }
145
        for (i = this.verticalAxises.length - 1; i >= 0; i--) {
146
            this.verticalAxises[i].allClones.forEach(drawFn);
147
        }
148
        for (i = this.rectangles.length - 1; i >= 0; i--) {
149
            this.rectangles[i].draw('black');
150
        }
151
        for (var key in this.nodes) {
152
            if (this.nodes.hasOwnProperty(key)) {
153
                this.nodes[key].draw('black');
154
            }
155
        }
156
    };
157
158
    /**
159
     * Divides all axises into horizontal and vertical ones
160
     */
161
    Graph.prototype.createAxises = function() {
162
        for (var i = 0; i < this.baseAxises.length; i++) {
163
            var axis = this.baseAxises[i];
164
            if (axis.isVertical) {
165
                this.verticalAxises.push(axis);
166
            } else if (axis.a.y === axis.b.y) {
167
                this.horizontalAxises.push(axis);
168
            }
169
        }
170
    };
171
172
    /**
173
     * Removes axis
174
     *
175
     * @param {Axis} axis
176
     */
177
    Graph.prototype.removeAxis = function(axis) {
178
        var index;
179
        if ((index = this.horizontalAxises.indexOf(axis)) !== -1) {
180
            this.horizontalAxises.splice(index, 1);
181
            return;
182
        }
183
        if ((index = this.verticalAxises.indexOf(axis)) !== -1) {
184
            this.verticalAxises.splice(index, 1);
185
        }
186
    };
187
188
    /**
189
     * Processes merge axises queue
190
     */
191
    Graph.prototype.mergeAxises = function() {
192
        var i;
193
        var j;
194
        for (i = 0; i < this.mergeAxisesQueue.length; i++) {
195
            var queue = this.mergeAxisesQueue[i];
196
            for (j = queue.length - 1; j >= 1; j--) {
197
                queue[j - 1].merge(queue[j]);
198
                this.removeAxis(queue[j]);
199
            }
200
        }
201
    };
202
203
    /**
204
     * Performs final operations on graph for future usage.
205
     * - connects and sorts nodes
206
     * - sort axises
207
     */
208
    Graph.prototype.finalizeAxises = function() {
209
        var i;
210
        for (i = this.verticalAxises.length - 1; i >= 0; i--) {
211
            this.verticalAxises[i].sortNodes();
212
            this.verticalAxises[i].finalize();
213
        }
214
        for (i = this.horizontalAxises.length - 1; i >= 0; i--) {
215
            this.horizontalAxises[i].sortNodes();
216
            this.horizontalAxises[i].finalize();
217
        }
218
        this.verticalAxises.sort(function(a, b) {
219
            return a.a.x - b.a.x;
220
        });
221
        this.horizontalAxises.sort(function(a, b) {
222
            return a.a.y - b.a.y;
223
        });
224
    };
225
226
    /**
227
     * Prepares information about axis connection between each other
228
     */
229
    Graph.prototype.buildAxisConnectionInfo = function() {
230
        var i;
231
        var j;
232
        var node;
233
        var connectionToLeft;
234
        var nodeAtLeft;
235
        var connectionToRight;
236
        var nodeAtRight;
237
        var axis;
238
239
        for (i = 0; i < this.horizontalAxises.length; i++) {
240
            axis = this.horizontalAxises[i];
241
            for (j = axis.nodes.length - 1; j >= 0; j--) {
242
                node = axis.nodes[j];
243
                connectionToLeft = node.connections[directions.TOP_TO_BOTTOM.id];
244
                nodeAtLeft = connectionToLeft ? connectionToLeft.second(node) : null;
245
                connectionToRight = node.connections[directions.BOTTOM_TO_TOP.id];
246
                nodeAtRight = connectionToRight ? connectionToRight.second(node) : null;
247
                if (nodeAtLeft) {
248
                    this.addAxisConnectionInfo(this.axisesConnectedAtLeft, axis, nodeAtLeft.hAxis);
249
                }
250
                if (nodeAtRight) {
251
                    this.addAxisConnectionInfo(this.axisesConnectedAtRight, axis, nodeAtRight.hAxis);
252
                }
253
            }
254
        }
255
        for (i = 0; i < this.verticalAxises.length; i++) {
256
            axis = this.verticalAxises[i];
257
            for (j = axis.nodes.length - 1; j >= 0; j--) {
258
                node = axis.nodes[j];
259
                connectionToLeft = node.connections[directions.RIGHT_TO_LEFT.id];
260
                nodeAtLeft = connectionToLeft ? connectionToLeft.second(node) : null;
261
                connectionToRight = node.connections[directions.LEFT_TO_RIGHT.id];
262
                nodeAtRight = connectionToRight ? connectionToRight.second(node) : null;
263
                if (nodeAtLeft) {
264
                    this.addAxisConnectionInfo(this.axisesConnectedAtLeft, axis, nodeAtLeft.vAxis);
265
                }
266
                if (nodeAtRight) {
267
                    this.addAxisConnectionInfo(this.axisesConnectedAtRight, axis, nodeAtRight.vAxis);
268
                }
269
            }
270
        }
271
    };
272
273
    /**
274
     * Adds into keeper information about axis connections
275
     *
276
     * @private
277
     * @param {Object} keeper
278
     * @param {Axis} main
279
     * @param {Axis} secondary
280
     */
281
    Graph.prototype.addAxisConnectionInfo = function(keeper, main, secondary) {
282
        if (!keeper[main.uid]) {
283
            keeper[main.uid] = [];
284
        }
285
        if (keeper[main.uid].indexOf(secondary) === -1) {
286
            keeper[main.uid].push(secondary);
287
        }
288
    };
289
290
    /**
291
     * Adds axises around initial rectangles
292
     */
293
    Graph.prototype.buildCornerAxises = function() {
294
        for (var i = this.rectangles.length - 1; i >= 0; i--) {
295
            var rect = this.rectangles[i];
296
            var defs = [
297
                {
298
                    vectorA: new Vector2d(rect.left, rect.top, directions.TOP_TO_BOTTOM),
299
                    vectorB: new Vector2d(rect.left, rect.bottom, directions.BOTTOM_TO_TOP),
300
                    leftConstraint: new EmptyConstraint(),
301
                    rightConstraint: new RightSimpleConstraint(rect.left),
302
                    locationDirective: new StickRightLocationDirective()
303
                },
304
                {
305
                    vectorA: new Vector2d(rect.right, rect.top, directions.TOP_TO_BOTTOM),
306
                    vectorB: new Vector2d(rect.right, rect.bottom, directions.BOTTOM_TO_TOP),
307
                    leftConstraint: new LeftSimpleConstraint(rect.right),
308
                    rightConstraint: new EmptyConstraint(),
309
                    locationDirective: new StickLeftLocationDirective()
310
                },
311
                {
312
                    vectorA: new Vector2d(rect.left, rect.top, directions.RIGHT_TO_LEFT),
313
                    vectorB: new Vector2d(rect.right, rect.top, directions.LEFT_TO_RIGHT),
314
                    leftConstraint: new EmptyConstraint(),
315
                    rightConstraint: new RightSimpleConstraint(rect.top),
316
                    locationDirective: new StickRightLocationDirective()
317
                },
318
                {
319
                    vectorA: new Vector2d(rect.left, rect.bottom, directions.RIGHT_TO_LEFT),
320
                    vectorB: new Vector2d(rect.right, rect.bottom, directions.LEFT_TO_RIGHT),
321
                    leftConstraint: new LeftSimpleConstraint(rect.bottom),
322
                    rightConstraint: new EmptyConstraint(),
323
                    locationDirective: new StickLeftLocationDirective()
324
                }
325
            ];
326
            for (var j = defs.length - 1; j >= 0; j--) {
327
                var def = defs[j];
328
                var closestRectCrossPoint1 = this.findClosestRectCross(def.vectorA, rect);
329
                var closestRectCrossPoint2 = this.findClosestRectCross(def.vectorB, rect);
330
                this.baseAxises.push(
331
                    BaseAxis.createFromInterval(
332
                        new Interval2d(closestRectCrossPoint1, closestRectCrossPoint2),
333
                        this,
334
                        def.leftConstraint,
335
                        def.rightConstraint,
336
                        def.locationDirective)
337
                );
338
            }
339
        }
340
    };
341
342
    /**
343
     * Adds axises which go through center of initial rectangle
344
     */
345
    Graph.prototype.buildCenterAxises = function() {
346
        for (var i = this.rectangles.length - 1; i >= 0; i--) {
347
            var rect = this.rectangles[i];
348
            var center = rect.center;
349
            var defs = [
350
                {
351
                    vector: new Vector2d(center.x, center.y + 1, directions.TOP_TO_BOTTOM),
352
                    leftConstraint: new LeftSimpleConstraint(rect.left),
353
                    rightConstraint: new RightSimpleConstraint(rect.right),
354
                    locationDirective: new CenterLocationDirective()
355
                },
356
                {
357
                    vector: new Vector2d(center.x, center.y - 1, directions.BOTTOM_TO_TOP),
358
                    leftConstraint: new LeftSimpleConstraint(rect.left),
359
                    rightConstraint: new RightSimpleConstraint(rect.right),
360
                    locationDirective: new CenterLocationDirective()
361
                },
362
                {
363
                    vector: new Vector2d(center.x + 1, center.y, directions.LEFT_TO_RIGHT),
364
                    leftConstraint: new LeftSimpleConstraint(rect.top),
365
                    rightConstraint: new RightSimpleConstraint(rect.bottom),
366
                    locationDirective: new CenterLocationDirective()
367
                },
368
                {
369
                    vector: new Vector2d(center.x - 1, center.y, directions.RIGHT_TO_LEFT),
370
                    leftConstraint: new LeftSimpleConstraint(rect.top),
371
                    rightConstraint: new RightSimpleConstraint(rect.bottom),
372
                    locationDirective: new CenterLocationDirective()
373
                }
374
            ];
375
            for (var j = defs.length - 1; j >= 0; j--) {
376
                var def = defs[j];
377
                var closestRectCrossPoint = this.findClosestRectCross(def.vector, rect);
378
                var axis = new BaseAxis(def.vector.start, closestRectCrossPoint, this, 1, def.leftConstraint,
379
                    def.rightConstraint, def.locationDirective);
380
                var secondaryAxis = new BaseAxis(def.vector.start, def.vector.start, this, 1, new EmptyConstraint(),
381
                    new EmptyConstraint(), new CenterLocationDirective());
382
                secondaryAxis.isVertical = !axis.isVertical;
383
                this.baseAxises.push(axis, secondaryAxis);
384
            }
385
        }
386
    };
387
388
    /**
389
     * Iterator for all rectangle pairs
390
     * @param {Function} fn
391
     */
392
    Graph.prototype.eachRectanglePair = function(fn) {
393
        for (var i = this.rectangles.length - 1; i >= 0; i--) {
394
            var rect1 = this.rectangles[i];
395
            for (var j = i - 1; j >= 0; j--) {
396
                fn(rect1, this.rectangles[j]);
397
            }
398
        }
399
    };
400
401
    /**
402
     * Adds axises between rectangles
403
     */
404
    Graph.prototype.buildCenterLinesBetweenNodes = function() {
405
        this.eachRectanglePair(function(a, b) {
406
            if (a.top > b.bottom && a.top - b.bottom > this.centerLineMinimalRequiredWidth) {
407
                this.buildSingleCenterLine(a, b, (a.top + b.bottom) / 2, a.topSide, b.bottomSide, a.top, b.bottom);
408
            }
409
            if (b.top > a.bottom && b.top - a.bottom > this.centerLineMinimalRequiredWidth) {
410
                this.buildSingleCenterLine(a, b, (b.top + a.bottom) / 2, b.topSide, a.bottomSide, b.top, a.bottom);
411
            }
412
            if (a.left > b.right && a.left - b.right > this.centerLineMinimalRequiredWidth) {
413
                this.buildSingleCenterLine(a, b, (a.left + b.right) / 2, a.leftSide, b.rightSide, a.left, b.right);
414
            }
415
            if (b.left > a.right && b.left - a.right > this.centerLineMinimalRequiredWidth) {
416
                this.buildSingleCenterLine(a, b, (b.left + a.right) / 2, b.leftSide, a.rightSide, b.left, a.right);
417
            }
418
        }.bind(this));
419
    };
420
421
    /**
422
     * Adds single axis between rectangles
423
     */
424
    Graph.prototype.buildSingleCenterLine = function(aRect, bRect, coordinate, a, b, min, max) {
425
        var aVector = new Vector2d(a.center.x, a.center.y, a.a.sub(a.b).rot270().unitVector);
426
        var bVector = new Vector2d(b.center.x, b.center.y, b.a.sub(b.b).rot90().unitVector);
427
        var crossRect = new Rectangle(Math.min(a.center.x, b.center.x), Math.min(a.center.y, b.center.y), 1, 1);
428
        var crossLine;
429
        crossRect.right = Math.max(a.center.x, b.center.x);
430
        crossRect.bottom = Math.max(a.center.y, b.center.y);
431
        if (this.rectangleIntersectsAnyRectangle(crossRect)) {
432
            return;
433
        }
434
        if (aVector.direction.x === 0) {
435
            crossLine = new Line2d(0, coordinate);
436
        } else {
437
            crossLine = new Line2d(Infinity, coordinate);
438
        }
439
        var intersectionA = crossLine.intersection(aVector.line);
440
        var intersectionB = crossLine.intersection(bVector.line);
441
        var vector1 = new Vector2d(intersectionA.x, intersectionA.y, aVector.direction.rot90());
442
        var vector2 = new Vector2d(intersectionB.x, intersectionB.y, bVector.direction.rot90());
443
        var closestRectCrossPoint1 = this.findClosestRectCross(vector1, null);
444
        var closestRectCrossPoint2 = this.findClosestRectCross(vector2, null);
445
        this.baseAxises.push(new BaseAxis(closestRectCrossPoint1, closestRectCrossPoint2, this,
446
            settings.centerAxisCostMultiplier, new LeftSimpleConstraint(min),
447
            new RightSimpleConstraint(max), new CenterLocationDirective()));
448
    };
449
    Graph.prototype.buildNodes = function() {
450
        /*
451
         * add all nodes at axises cross points
452
         */
453
        var node;
454
        var i;
455
        var j;
456
        var hAxis;
457
        var vAxis;
458
        var crossPoint;
459
        for (i = this.horizontalAxises.length - 1; i >= 0; i--) {
460
            hAxis = this.horizontalAxises[i];
461
            for (j = this.verticalAxises.length - 1; j >= 0; j--) {
462
                vAxis = this.verticalAxises[j];
463
                crossPoint = hAxis.getCrossPoint(vAxis);
464
                if (crossPoint) {
465
                    node = this.getNodeAt(crossPoint);
466
                    hAxis.addNode(node);
467
                    vAxis.addNode(node);
468
                    node.hAxis = hAxis;
469
                    node.vAxis = vAxis;
470
                    node.stale = true;
471
                }
472
            }
473
        }
474
        this.buildNodesAtEndPoints();
475
    };
476
477
    /**
478
     * Build nodes at endpoints
479
     */
480
    Graph.prototype.buildNodesAtEndPoints = function() {
481
        var newVerticalAxises = this.buildNodesAtEndPointsVertical();
482
        var newHorizontalAxises = this.buildNodesAtEndPointsHorizontal();
483
        this.verticalAxises.push.apply(this.verticalAxises, newVerticalAxises);
484
        this.horizontalAxises.push.apply(this.horizontalAxises, newHorizontalAxises);
485
    };
486
487
    /**
488
     * Build nodes at endpoints on horizontal axises
489
     */
490 View Code Duplication
    Graph.prototype.buildNodesAtEndPointsHorizontal = function() {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
491
        var node;
492
        var newAxis;
493
        var i;
494
        var hAxis;
495
        var newVerticalAxises = [];
496
        for (i = this.horizontalAxises.length - 1; i >= 0; i--) {
497
            hAxis = this.horizontalAxises[i];
498
            node = this.getNodeAt(hAxis.a);
499
            if (!node.stale) {
500
                newAxis = new BaseAxis(hAxis.a, hAxis.a, this, 0, new EmptyConstraint(), new EmptyConstraint(),
501
                    new CenterLocationDirective());
502
                newAxis.isVertical = true;
503
                newVerticalAxises.push(newAxis);
504
                hAxis.addNode(node);
505
                newAxis.addNode(node);
506
                node.hAxis = hAxis;
507
                node.vAxis = newAxis;
508
            }
509
            node = this.getNodeAt(hAxis.b);
510
            if (!node.stale) {
511
                newAxis = new BaseAxis(hAxis.b, hAxis.b, this, 0, new EmptyConstraint(), new EmptyConstraint(),
512
                    new CenterLocationDirective());
513
                newAxis.isVertical = true;
514
                newVerticalAxises.push(newAxis);
515
                hAxis.addNode(node);
516
                newAxis.addNode(node);
517
                node.hAxis = hAxis;
518
                node.vAxis = newAxis;
519
            }
520
        }
521
        return newVerticalAxises;
522
    };
523
524
    /**
525
     * Build nodes at endpoints on vertical axises
526
     */
527 View Code Duplication
    Graph.prototype.buildNodesAtEndPointsVertical = function() {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
528
        var node;
529
        var newAxis;
530
        var i;
531
        var vAxis;
532
        var newHorizontalAxises = [];
533
        for (i = this.verticalAxises.length - 1; i >= 0; i--) {
534
            vAxis = this.verticalAxises[i];
535
            node = this.getNodeAt(vAxis.a);
536
            if (!node.stale) {
537
                newAxis = new BaseAxis(vAxis.a, vAxis.a, this, 0, new EmptyConstraint(), new EmptyConstraint(),
538
                    new CenterLocationDirective());
539
                newAxis.isVertical = false;
540
                newHorizontalAxises.push(newAxis);
541
                vAxis.addNode(node);
542
                newAxis.addNode(node);
543
                node.hAxis = newAxis;
544
                node.vAxis = vAxis;
545
            }
546
            node = this.getNodeAt(vAxis.b);
547
            if (!node.stale) {
548
                newAxis = new BaseAxis(vAxis.b, vAxis.b, this, 0, new EmptyConstraint(), new EmptyConstraint(),
549
                    new CenterLocationDirective());
550
                newAxis.isVertical = false;
551
                newHorizontalAxises.push(newAxis);
552
                vAxis.addNode(node);
553
                newAxis.addNode(node);
554
                node.hAxis = newAxis;
555
                node.vAxis = vAxis;
556
            }
557
        }
558
        return newHorizontalAxises;
559
    };
560
561
    /**
562
     * Prepares merge axises requests
563
     */
564
    Graph.prototype.buildMergeRequests = function() {
565
        for (var i = this.horizontalAxises.length - 1; i >= 0; i--) {
566
            var hAxis = this.horizontalAxises[i];
567
            for (var j = this.verticalAxises.length - 1; j >= 0; j--) {
568
                var vAxis = this.verticalAxises[j];
569
                var crossPoint = hAxis.getCrossPoint(vAxis);
570
                if (crossPoint) {
571
                    var node = this.getNodeAt(crossPoint);
572
                    if (node.stale) {
573
                        if (node.hAxis !== hAxis) {
574
                            this.addMergeRequest(node.hAxis, hAxis);
575
                        }
576
                        if (node.vAxis !== vAxis) {
577
                            this.addMergeRequest(node.vAxis, vAxis);
578
                        }
579
                    }
580
                    node.hAxis = hAxis;
581
                    node.vAxis = vAxis;
582
                    node.stale = true;
583
                }
584
            }
585
        }
586
    };
587
588
    /**
589
     * Adds single merge axis request. Takes care about queue structure
590
     */
591
    Graph.prototype.addMergeRequest = function(a, b) {
592
        var foundAQueue;
593
        var foundBQueue;
594
        var i;
595
        var queue;
596
        for (i = this.mergeAxisesQueue.length - 1; i >= 0; i--) {
597
            queue = this.mergeAxisesQueue[i];
598
            if (queue.indexOf(a) !== -1) {
599
                foundAQueue = queue;
600
                break;
601
            }
602
        }
603
        for (i = this.mergeAxisesQueue.length - 1; i >= 0; i--) {
604
            queue = this.mergeAxisesQueue[i];
605
            if (queue.indexOf(b) !== -1) {
606
                foundBQueue = queue;
607
                break;
608
            }
609
        }
610
        if (foundAQueue !== undefined && foundAQueue === foundBQueue) {
0 ignored issues
show
Bug introduced by
The variable foundBQueue seems to not be initialized for all possible execution paths.
Loading history...
Bug introduced by
The variable foundAQueue seems to not be initialized for all possible execution paths.
Loading history...
611
            return;
612
        }
613
        if (!foundAQueue) {
614
            if (foundBQueue) {
615
                foundBQueue.push(a);
616
            } else {
617
                this.mergeAxisesQueue.push([a, b]);
618
            }
619
        } else {
620
            if (foundBQueue) {
621
                // must merge
622
                foundAQueue.push.apply(foundAQueue, foundBQueue);
623
                this.mergeAxisesQueue.splice(this.mergeAxisesQueue.indexOf(foundBQueue), 1);
624
            } else {
625
                foundAQueue.push(b);
626
            }
627
        }
628
    };
629
630
    /**
631
     * Returns node at certain point at graph
632
     * @param {Point2d} point
633
     * @returns {NodePoint}
634
     */
635
    Graph.prototype.getNodeAt = function(point) {
636
        var node = this.nodes[point.id];
637
        if (!node) {
638
            node = new NodePoint(point.x, point.y);
639
            this.nodes[point.id] = node;
640
        }
641
        return node;
642
    };
643
644
    /**
645
     * Finds closes initial rectangle (or outer rectangle) cross point by specified vector
646
     *
647
     * @param {Vector2d} vector
648
     * @param {Rectangle} ignoreRect
649
     * @returns {*}
650
     */
651
    Graph.prototype.findClosestRectCross = function(vector, ignoreRect) {
652
        var closestDistance = Infinity;
0 ignored issues
show
Comprehensibility Best Practice introduced by
You seem to be aliasing the built-in name Infinity as closestDistance. This makes your code very difficult to follow, consider using the built-in name directly.
Loading history...
653
        var closestPoint = null;
654
        for (var i = this.rectangles.length - 1; i >= 0; i--) {
655
            var rect = this.rectangles[i];
656
            if (rect === ignoreRect) {
657
                continue;
658
            }
659
            var crossPoint = vector.getCrossPointWithRect(rect);
660
            if (crossPoint && closestDistance > crossPoint.distanceTo(vector.start)) {
661
                closestPoint = crossPoint;
662
                closestDistance = crossPoint.distanceTo(vector.start);
663
            }
664
        }
665
        if (closestDistance === Infinity) {
666
            this.outerRect.eachSide(function(side) {
667
                var crossPoint;
668
                if ((crossPoint = vector.getCrossPointWithInterval(side))) {
669
                    if (vector.start.distanceTo(crossPoint) < closestDistance) {
670
                        closestPoint = crossPoint;
671
                        closestDistance = vector.start.distanceTo(crossPoint);
672
                    }
673
                }
674
            });
675
        }
676
        if (closestDistance === Infinity) {
677
            return vector.start;
678
        }
679
        return closestPoint;
680
    };
681
682
    /**
683
     * Returns true if specified rectangle intersects any initial rectangle on graph
684
     *
685
     * @param {Rectangle} rectangle
686
     * @param {Rectangle} ignoreRect
687
     * @returns {boolean}
688
     */
689
    Graph.prototype.rectangleIntersectsAnyRectangle = function(rectangle, ignoreRect) {
690
        for (var i = this.rectangles.length - 1; i >= 0; i--) {
691
            if (this.rectangles[i] === ignoreRect) {
692
                continue;
693
            }
694
            var intersection = rectangle.intersection(this.rectangles[i]);
695
            // non-inclusive
696
            if (intersection !== null && intersection.width !== 0 && intersection.height !== 0) {
697
                return true;
698
            }
699
        }
700
        return false;
701
    };
702
703
    /**
704
     * Updates graph with path. Ensures its traversability.
705
     *
706
     * @param {Path} path
707
     */
708
    Graph.prototype.updateWithPath = function(path) {
709
        var connections = path.allConnections;
710
        var axises = [];
711
        var i;
712
        var conn;
713
        var prev;
714
        for (i = 0; i < connections.length; i++) {
715
            conn = connections[i];
716
            if (axises.indexOf(conn.axis) === -1) {
717
                axises.push(conn.axis);
718
                conn.axis.ensureTraversableSiblings();
719
                conn.axis.used = true;
720
                conn.axis.costMultiplier *= settings.usedAxisCostMultiplier;
721
            }
722
        }
723
        connections = path.allConnections;
724
        for (i = 0; i < connections.length; i++) {
725
            conn = connections[i];
726
            conn.traversable = false;
727
            conn.a.used = true;
728
            conn.b.used = true;
729
730
            if (prev && conn.vector.id !== prev.vector.id) {
731
                // corner
732
                // all connections are used on corner
733
                // this will avoid double corner use
734
                var midNode = conn.a === prev.a || conn.a === prev.b ? conn.a : conn.b;
735
                midNode.eachConnection(function(conn) {
736
                    conn.traversable = false;
737
                });
738
            }
739
            prev = conn;
740
        }
741
    };
742
743
    /**
744
     * Locates axises to be able get valid path points
745
     */
746
    Graph.prototype.locateAxises = function() {
747
        var i;
748
        var j;
749
        var axis;
750
        var clones;
751
        var current;
752
        var clone;
753
        for (i = this.verticalAxises.length - 1; i >= 0; i--) {
754
            axis = this.verticalAxises[i];
755
            clones = axis.allClones;
756
            axis.linesIncluded = Math.floor(clones.length / 2);
757
            if (axis.linesIncluded > 0) {
758
                current = 0;
759
                for (j = 0; j < clones.length; j++) {
760
                    clone = clones[j];
761
                    clone.recommendedPosition = axis.locationDirective.getRecommendedPosition(current);
762
                    current += 0.5;
763
                }
764
            }
765
        }
766
        for (i = this.horizontalAxises.length - 1; i >= 0; i--) {
767
            axis = this.horizontalAxises[i];
768
            clones = axis.allClones;
769
            axis.linesIncluded = Math.floor(clones.length / 2);
770
            if (axis.linesIncluded > 0) {
771
                current = 0;
772
                for (j = 0; j < clones.length; j++) {
773
                    clone = clones[j];
774
                    clone.recommendedPosition = axis.locationDirective.getRecommendedPosition(current);
775
                    current += 0.5;
776
                }
777
            }
778
        }
779
    };
780
781
    /**
782
     * Returns if this connection is under any rectangle
783
     *
784
     * @TODO optimize this!
785
     *
786
     * @param interval
787
     * @returns {boolean}
788
     */
789
    Graph.prototype.isConnectionUnderRect = function(interval) {
790
        for (var i = this.rectangles.length - 1; i >= 0; i--) {
791
            var rect = this.rectangles[i];
792
            if (rect.containsPoint(interval.a) || rect.containsPoint(interval.b)) {
793
                return true;
794
            }
795
        }
796
        return false;
797
    };
798
    return Graph;
799
});
800