mxSwimlaneModel.js 23 KB

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