1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
|
/**
* $Id: mxGraphHierarchyModel.js,v 1.33 2012-12-18 13:16:43 david Exp $
* Copyright (c) 2006-2012, JGraph Ltd
*/
/**
* Class: mxGraphHierarchyModel
*
* Internal model of a hierarchical graph. This model stores nodes and edges
* equivalent to the real graph nodes and edges, but also stores the rank of the
* cells, the order within the ranks and the new candidate locations of cells.
* The internal model also reverses edge direction were appropriate , ignores
* self-loop and groups parallels together under one edge object.
*
* Constructor: mxGraphHierarchyModel
*
* Creates an internal ordered graph model using the vertices passed in. If
* there are any, leftward edge need to be inverted in the internal model
*
* Arguments:
*
* graph - the facade describing the graph to be operated on
* vertices - the vertices for this hierarchy
* ordered - whether or not the vertices are already ordered
* deterministic - whether or not this layout should be deterministic on each
* tightenToSource - whether or not to tighten vertices towards the sources
* scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
* usage
*/
function mxGraphHierarchyModel(layout, vertices, roots, parent, tightenToSource)
{
var graph = layout.getGraph();
this.tightenToSource = tightenToSource;
this.roots = roots;
this.parent = parent;
// map of cells to internal cell needed for second run through
// to setup the sink of edges correctly
this.vertexMapper = new Object();
this.edgeMapper = new Object();
this.maxRank = 0;
var internalVertices = [];
if (vertices == null)
{
vertices = this.graph.getChildVertices(parent);
}
this.maxRank = this.SOURCESCANSTARTRANK;
// map of cells to internal cell needed for second run through
// to setup the sink of edges correctly. Guess size by number
// of edges is roughly same as number of vertices.
this.createInternalCells(layout, vertices, internalVertices);
// Go through edges set their sink values. Also check the
// ordering if and invert edges if necessary
for (var i = 0; i < vertices.length; i++)
{
var edges = internalVertices[i].connectsAsSource;
for (var j = 0; j < edges.length; j++)
{
var internalEdge = edges[j];
var realEdges = internalEdge.edges;
// Only need to process the first real edge, since
// all the edges connect to the same other vertex
if (realEdges != null && realEdges.length > 0)
{
var realEdge = realEdges[0];
var targetCell = graph.getView().getVisibleTerminal(
realEdge, false);
var targetCellId = mxCellPath.create(targetCell);
var internalTargetCell = this.vertexMapper[targetCellId];
if (internalVertices[i] == internalTargetCell)
{
// The real edge is reversed relative to the internal edge
targetCell = graph.getView().getVisibleTerminal(
realEdge, true);
targetCellId = mxCellPath.create(targetCell);
internalTargetCell = this.vertexMapper[targetCellId];
}
if (internalTargetCell != null
&& internalVertices[i] != internalTargetCell)
{
internalEdge.target = internalTargetCell;
if (internalTargetCell.connectsAsTarget.length == 0)
{
internalTargetCell.connectsAsTarget = [];
}
if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)
{
internalTargetCell.connectsAsTarget.push(internalEdge);
}
}
}
}
// Use the temp variable in the internal nodes to mark this
// internal vertex as having been visited.
internalVertices[i].temp[0] = 1;
}
};
/**
* Variable: maxRank
*
* Stores the largest rank number allocated
*/
mxGraphHierarchyModel.prototype.maxRank = null;
/**
* Variable: vertexMapper
*
* Map from graph vertices to internal model nodes.
*/
mxGraphHierarchyModel.prototype.vertexMapper = null;
/**
* Variable: edgeMapper
*
* Map from graph edges to internal model edges
*/
mxGraphHierarchyModel.prototype.edgeMapper = null;
/**
* Variable: ranks
*
* Mapping from rank number to actual rank
*/
mxGraphHierarchyModel.prototype.ranks = null;
/**
* Variable: roots
*
* Store of roots of this hierarchy model, these are real graph cells, not
* internal cells
*/
mxGraphHierarchyModel.prototype.roots = null;
/**
* Variable: parent
*
* The parent cell whose children are being laid out
*/
mxGraphHierarchyModel.prototype.parent = null;
/**
* Variable: dfsCount
*
* Count of the number of times the ancestor dfs has been used.
*/
mxGraphHierarchyModel.prototype.dfsCount = 0;
/**
* Variable: SOURCESCANSTARTRANK
*
* High value to start source layering scan rank value from.
*/
mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK = 100000000;
/**
* Variable: tightenToSource
*
* Whether or not to tighten the assigned ranks of vertices up towards
* the source cells.
*/
mxGraphHierarchyModel.prototype.tightenToSource = false;
/**
* Function: createInternalCells
*
* Creates all edges in the internal model
*
* Parameters:
*
* layout - Reference to the <mxHierarchicalLayout> algorithm.
* vertices - Array of <mxCells> that represent the vertices whom are to
* have an internal representation created.
* internalVertices - The array of <mxGraphHierarchyNodes> to have their
* information filled in using the real vertices.
*/
mxGraphHierarchyModel.prototype.createInternalCells = function(layout, vertices, internalVertices)
{
var graph = layout.getGraph();
// Create internal edges
for (var i = 0; i < vertices.length; i++)
{
internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
var vertexId = mxCellPath.create(vertices[i]);
this.vertexMapper[vertexId] = internalVertices[i];
// If the layout is deterministic, order the cells
//List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
var conns = layout.getEdges(vertices[i]);
var outgoingCells = graph.getOpposites(conns, vertices[i]);
internalVertices[i].connectsAsSource = [];
// Create internal edges, but don't do any rank assignment yet
// First use the information from the greedy cycle remover to
// invert the leftward edges internally
for (var j = 0; j < outgoingCells.length; j++)
{
var cell = outgoingCells[j];
if (cell != vertices[i] && layout.graph.model.isVertex(cell) &&
!layout.isVertexIgnored(cell))
{
// We process all edge between this source and its targets
// If there are edges going both ways, we need to collect
// them all into one internal edges to avoid looping problems
// later. We assume this direction (source -> target) is the
// natural direction if at least half the edges are going in
// that direction.
// The check below for edges[0] being in the vertex mapper is
// in case we've processed this the other way around
// (target -> source) and the number of edges in each direction
// are the same. All the graph edges will have been assigned to
// an internal edge going the other way, so we don't want to
// process them again
var undirectedEdges = graph.getEdgesBetween(vertices[i],
cell, false);
var directedEdges = graph.getEdgesBetween(vertices[i],
cell, true);
var edgeId = mxCellPath.create(undirectedEdges[0]);
if (undirectedEdges != null &&
undirectedEdges.length > 0 &&
this.edgeMapper[edgeId] == null &&
directedEdges.length * 2 >= undirectedEdges.length)
{
var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);
for (var k = 0; k < undirectedEdges.length; k++)
{
var edge = undirectedEdges[k];
edgeId = mxCellPath.create(edge);
this.edgeMapper[edgeId] = internalEdge;
// Resets all point on the edge and disables the edge style
// without deleting it from the cell style
graph.resetEdge(edge);
if (layout.disableEdgeStyle)
{
layout.setEdgeStyleEnabled(edge, false);
layout.setOrthogonalEdge(edge,true);
}
}
internalEdge.source = internalVertices[i];
if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)
{
internalVertices[i].connectsAsSource.push(internalEdge);
}
}
}
}
// Ensure temp variable is cleared from any previous use
internalVertices[i].temp[0] = 0;
}
};
/**
* Function: initialRank
*
* Basic determination of minimum layer ranking by working from from sources
* or sinks and working through each node in the relevant edge direction.
* Starting at the sinks is basically a longest path layering algorithm.
*/
mxGraphHierarchyModel.prototype.initialRank = function()
{
var startNodes = [];
if (this.roots != null)
{
for (var i = 0; i < this.roots.length; i++)
{
var vertexId = mxCellPath.create(this.roots[i]);
var internalNode = this.vertexMapper[vertexId];
if (internalNode != null)
{
startNodes.push(internalNode);
}
}
}
for (var key in this.vertexMapper)
{
var internalNode = this.vertexMapper[key];
// Mark the node as not having had a layer assigned
internalNode.temp[0] = -1;
}
var startNodesCopy = startNodes.slice();
while (startNodes.length > 0)
{
var internalNode = startNodes[0];
var layerDeterminingEdges;
var edgesToBeMarked;
layerDeterminingEdges = internalNode.connectsAsTarget;
edgesToBeMarked = internalNode.connectsAsSource;
// flag to keep track of whether or not all layer determining
// edges have been scanned
var allEdgesScanned = true;
// Work out the layer of this node from the layer determining
// edges. The minimum layer number of any node connected by one of
// the layer determining edges variable
var minimumLayer = this.SOURCESCANSTARTRANK;
for (var i = 0; i < layerDeterminingEdges.length; i++)
{
var internalEdge = layerDeterminingEdges[i];
if (internalEdge.temp[0] == 5270620)
{
// This edge has been scanned, get the layer of the
// node on the other end
var otherNode = internalEdge.source;
minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
}
else
{
allEdgesScanned = false;
break;
}
}
// If all edge have been scanned, assign the layer, mark all
// edges in the other direction and remove from the nodes list
if (allEdgesScanned)
{
internalNode.temp[0] = minimumLayer;
this.maxRank = Math.min(this.maxRank, minimumLayer);
if (edgesToBeMarked != null)
{
for (var i = 0; i < edgesToBeMarked.length; i++)
{
var internalEdge = edgesToBeMarked[i];
// Assign unique stamp ( y/m/d/h )
internalEdge.temp[0] = 5270620;
// Add node on other end of edge to LinkedList of
// nodes to be analysed
var otherNode = internalEdge.target;
// Only add node if it hasn't been assigned a layer
if (otherNode.temp[0] == -1)
{
startNodes.push(otherNode);
// Mark this other node as neither being
// unassigned nor assigned so it isn't
// added to this list again, but it's
// layer isn't used in any calculation.
otherNode.temp[0] = -2;
}
}
}
startNodes.shift();
}
else
{
// Not all the edges have been scanned, get to the back of
// the class and put the dunces cap on
var removedCell = startNodes.shift();
startNodes.push(internalNode);
if (removedCell == internalNode && startNodes.length == 1)
{
// This is an error condition, we can't get out of
// this loop. It could happen for more than one node
// but that's a lot harder to detect. Log the error
// TODO make log comment
break;
}
}
}
// Normalize the ranks down from their large starting value to place
// at least 1 sink on layer 0
for (var key in this.vertexMapper)
{
var internalNode = this.vertexMapper[key];
// Mark the node as not having had a layer assigned
internalNode.temp[0] -= this.maxRank;
}
// Tighten the rank 0 nodes as far as possible
for ( var i = 0; i < startNodesCopy.length; i++)
{
var internalNode = startNodesCopy[i];
var currentMaxLayer = 0;
var layerDeterminingEdges = internalNode.connectsAsSource;
for ( var j = 0; j < layerDeterminingEdges.length; j++)
{
var internalEdge = layerDeterminingEdges[j];
var otherNode = internalEdge.target;
internalNode.temp[0] = Math.max(currentMaxLayer,
otherNode.temp[0] + 1);
currentMaxLayer = internalNode.temp[0];
}
}
// Reset the maxRank to that which would be expected for a from-sink
// scan
this.maxRank = this.SOURCESCANSTARTRANK - this.maxRank;
};
/**
* Function: fixRanks
*
* Fixes the layer assignments to the values stored in the nodes. Also needs
* to create dummy nodes for edges that cross layers.
*/
mxGraphHierarchyModel.prototype.fixRanks = function()
{
var rankList = [];
this.ranks = [];
for (var i = 0; i < this.maxRank + 1; i++)
{
rankList[i] = [];
this.ranks[i] = rankList[i];
}
// Perform a DFS to obtain an initial ordering for each rank.
// Without doing this you would end up having to process
// crossings for a standard tree.
var rootsArray = null;
if (this.roots != null)
{
var oldRootsArray = this.roots;
rootsArray = [];
for (var i = 0; i < oldRootsArray.length; i++)
{
var cell = oldRootsArray[i];
var cellId = mxCellPath.create(cell);
var internalNode = this.vertexMapper[cellId];
rootsArray[i] = internalNode;
}
}
this.visit(function(parent, node, edge, layer, seen)
{
if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
{
rankList[node.temp[0]].push(node);
node.maxRank = node.temp[0];
node.minRank = node.temp[0];
// Set temp[0] to the nodes position in the rank
node.temp[0] = rankList[node.maxRank].length - 1;
}
if (parent != null && edge != null)
{
var parentToCellRankDifference = parent.maxRank - node.maxRank;
if (parentToCellRankDifference > 1)
{
// There are ranks in between the parent and current cell
edge.maxRank = parent.maxRank;
edge.minRank = node.maxRank;
edge.temp = [];
edge.x = [];
edge.y = [];
for (var i = edge.minRank + 1; i < edge.maxRank; i++)
{
// The connecting edge must be added to the
// appropriate ranks
rankList[i].push(edge);
edge.setGeneralPurposeVariable(i, rankList[i]
.length - 1);
}
}
}
}, rootsArray, false, null);
};
/**
* Function: visit
*
* A depth first search through the internal heirarchy model.
*
* Parameters:
*
* visitor - The visitor function pattern to be called for each node.
* trackAncestors - Whether or not the search is to keep track all nodes
* directly above this one in the search path.
*/
mxGraphHierarchyModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)
{
// Run dfs through on all roots
if (dfsRoots != null)
{
for (var i = 0; i < dfsRoots.length; i++)
{
var internalNode = dfsRoots[i];
if (internalNode != null)
{
if (seenNodes == null)
{
seenNodes = new Object();
}
if (trackAncestors)
{
// Set up hash code for root
internalNode.hashCode = [];
internalNode.hashCode[0] = this.dfsCount;
internalNode.hashCode[1] = i;
this.extendedDfs(null, internalNode, null, visitor, seenNodes,
internalNode.hashCode, i, 0);
}
else
{
this.dfs(null, internalNode, null, visitor, seenNodes, 0);
}
}
}
this.dfsCount++;
}
};
/**
* Function: dfs
*
* Performs a depth first search on the internal hierarchy model
*
* Parameters:
*
* parent - the parent internal node of the current internal node
* root - the current internal node
* connectingEdge - the internal edge connecting the internal node and the parent
* internal node, if any
* visitor - the visitor pattern to be called for each node
* seen - a set of all nodes seen by this dfs a set of all of the
* ancestor node of the current node
* layer - the layer on the dfs tree ( not the same as the model ranks )
*/
mxGraphHierarchyModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)
{
if (root != null)
{
var rootId = mxCellPath.create(root.cell);
if (seen[rootId] == null)
{
seen[rootId] = root;
visitor(parent, root, connectingEdge, layer, 0);
// Copy the connects as source list so that visitors
// can change the original for edge direction inversions
var outgoingEdges = root.connectsAsSource.slice();
for (var i = 0; i< outgoingEdges.length; i++)
{
var internalEdge = outgoingEdges[i];
var targetNode = internalEdge.target;
// Root check is O(|roots|)
this.dfs(root, targetNode, internalEdge, visitor, seen,
layer + 1);
}
}
else
{
// Use the int field to indicate this node has been seen
visitor(parent, root, connectingEdge, layer, 1);
}
}
};
/**
* Function: extendedDfs
*
* Performs a depth first search on the internal hierarchy model. This dfs
* extends the default version by keeping track of cells ancestors, but it
* should be only used when necessary because of it can be computationally
* intensive for deep searches.
*
* Parameters:
*
* parent - the parent internal node of the current internal node
* root - the current internal node
* connectingEdge - the internal edge connecting the internal node and the parent
* internal node, if any
* visitor - the visitor pattern to be called for each node
* seen - a set of all nodes seen by this dfs
* ancestors - the parent hash code
* childHash - the new hash code for this node
* layer - the layer on the dfs tree ( not the same as the model ranks )
*/
mxGraphHierarchyModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)
{
// Explanation of custom hash set. Previously, the ancestors variable
// was passed through the dfs as a HashSet. The ancestors were copied
// into a new HashSet and when the new child was processed it was also
// added to the set. If the current node was in its ancestor list it
// meant there is a cycle in the graph and this information is passed
// to the visitor.visit() in the seen parameter. The HashSet clone was
// very expensive on CPU so a custom hash was developed using primitive
// types. temp[] couldn't be used so hashCode[] was added to each node.
// Each new child adds another int to the array, copying the prefix
// from its parent. Child of the same parent add different ints (the
// limit is therefore 2^32 children per parent...). If a node has a
// child with the hashCode already set then the child code is compared
// to the same portion of the current nodes array. If they match there
// is a loop.
// Note that the basic mechanism would only allow for 1 use of this
// functionality, so the root nodes have two ints. The second int is
// incremented through each node root and the first is incremented
// through each run of the dfs algorithm (therefore the dfs is not
// thread safe). The hash code of each node is set if not already set,
// or if the first int does not match that of the current run.
if (root != null)
{
if (parent != null)
{
// Form this nodes hash code if necessary, that is, if the
// hashCode variable has not been initialized or if the
// start of the parent hash code does not equal the start of
// this nodes hash code, indicating the code was set on a
// previous run of this dfs.
if (root.hashCode == null ||
root.hashCode[0] != parent.hashCode[0])
{
var hashCodeLength = parent.hashCode.length + 1;
root.hashCode = parent.hashCode.slice();
root.hashCode[hashCodeLength - 1] = childHash;
}
}
var rootId = mxCellPath.create(root.cell);
if (seen[rootId] == null)
{
seen[rootId] = root;
visitor(parent, root, connectingEdge, layer, 0);
// Copy the connects as source list so that visitors
// can change the original for edge direction inversions
var outgoingEdges = root.connectsAsSource.slice();
for (var i = 0; i < outgoingEdges.length; i++)
{
var internalEdge = outgoingEdges[i];
var targetNode = internalEdge.target;
// Root check is O(|roots|)
this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
root.hashCode, i, layer + 1);
}
}
else
{
// Use the int field to indicate this node has been seen
visitor(parent, root, connectingEdge, layer, 1);
}
}
};
|