mxGraphHierarchyModel.js 20 KB

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