mxSwimlaneLayout.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxSwimlaneLayout
  7. *
  8. * A hierarchical layout algorithm.
  9. *
  10. * Constructor: mxSwimlaneLayout
  11. *
  12. * Constructs a new hierarchical layout algorithm.
  13. *
  14. * Arguments:
  15. *
  16. * graph - Reference to the enclosing <mxGraph>.
  17. * orientation - Optional constant that defines the orientation of this
  18. * layout.
  19. * deterministic - Optional boolean that specifies if this layout should be
  20. * deterministic. Default is true.
  21. */
  22. function mxSwimlaneLayout(graph, orientation, deterministic)
  23. {
  24. mxGraphLayout.call(this, graph);
  25. this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
  26. this.deterministic = (deterministic != null) ? deterministic : true;
  27. };
  28. /**
  29. * Extends mxGraphLayout.
  30. */
  31. mxSwimlaneLayout.prototype = new mxGraphLayout();
  32. mxSwimlaneLayout.prototype.constructor = mxSwimlaneLayout;
  33. /**
  34. * Variable: roots
  35. *
  36. * Holds the array of <mxCell> that this layout contains.
  37. */
  38. mxSwimlaneLayout.prototype.roots = null;
  39. /**
  40. * Variable: swimlanes
  41. *
  42. * Holds the array of <mxCell> of the ordered swimlanes to lay out
  43. */
  44. mxSwimlaneLayout.prototype.swimlanes = null;
  45. /**
  46. * Variable: dummyVertexWidth
  47. *
  48. * The cell width of any dummy vertices inserted
  49. */
  50. mxSwimlaneLayout.prototype.dummyVertexWidth = 50;
  51. /**
  52. * Variable: resizeParent
  53. *
  54. * Specifies if the parent should be resized after the layout so that it
  55. * contains all the child cells. Default is false. See also <parentBorder>.
  56. */
  57. mxSwimlaneLayout.prototype.resizeParent = false;
  58. /**
  59. * Variable: maintainParentLocation
  60. *
  61. * Specifies if the parent location should be maintained, so that the
  62. * top, left corner stays the same before and after execution of
  63. * the layout. Default is false for backwards compatibility.
  64. */
  65. mxSwimlaneLayout.prototype.maintainParentLocation = false;
  66. /**
  67. * Variable: moveParent
  68. *
  69. * Specifies if the parent should be moved if <resizeParent> is enabled.
  70. * Default is false.
  71. */
  72. mxSwimlaneLayout.prototype.moveParent = false;
  73. /**
  74. * Variable: parentBorder
  75. *
  76. * The border to be added around the children if the parent is to be
  77. * resized using <resizeParent>. Default is 30.
  78. */
  79. mxSwimlaneLayout.prototype.parentBorder = 30;
  80. /**
  81. * Variable: intraCellSpacing
  82. *
  83. * The spacing buffer added between cells on the same layer. Default is 30.
  84. */
  85. mxSwimlaneLayout.prototype.intraCellSpacing = 30;
  86. /**
  87. * Variable: interRankCellSpacing
  88. *
  89. * The spacing buffer added between cell on adjacent layers. Default is 100.
  90. */
  91. mxSwimlaneLayout.prototype.interRankCellSpacing = 100;
  92. /**
  93. * Variable: interHierarchySpacing
  94. *
  95. * The spacing buffer between unconnected hierarchies. Default is 60.
  96. */
  97. mxSwimlaneLayout.prototype.interHierarchySpacing = 60;
  98. /**
  99. * Variable: parallelEdgeSpacing
  100. *
  101. * The distance between each parallel edge on each ranks for long edges.
  102. * Default is 10.
  103. */
  104. mxSwimlaneLayout.prototype.parallelEdgeSpacing = 10;
  105. /**
  106. * Variable: orientation
  107. *
  108. * The position of the root node(s) relative to the laid out graph in.
  109. * Default is <mxConstants.DIRECTION_NORTH>.
  110. */
  111. mxSwimlaneLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
  112. /**
  113. * Variable: fineTuning
  114. *
  115. * Whether or not to perform local optimisations and iterate multiple times
  116. * through the algorithm. Default is true.
  117. */
  118. mxSwimlaneLayout.prototype.fineTuning = true;
  119. /**
  120. * Variable: tightenToSource
  121. *
  122. * Whether or not to tighten the assigned ranks of vertices up towards
  123. * the source cells. Default is true.
  124. */
  125. mxSwimlaneLayout.prototype.tightenToSource = true;
  126. /**
  127. * Variable: disableEdgeStyle
  128. *
  129. * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
  130. * modified by the result. Default is true.
  131. */
  132. mxSwimlaneLayout.prototype.disableEdgeStyle = true;
  133. /**
  134. * Variable: traverseAncestors
  135. *
  136. * Whether or not to drill into child cells and layout in reverse
  137. * group order. This also cause the layout to navigate edges whose
  138. * terminal vertices have different parents but are in the same
  139. * ancestry chain. Default is true.
  140. */
  141. mxSwimlaneLayout.prototype.traverseAncestors = true;
  142. /**
  143. * Variable: model
  144. *
  145. * The internal <mxSwimlaneModel> formed of the layout.
  146. */
  147. mxSwimlaneLayout.prototype.model = null;
  148. /**
  149. * Variable: edgesSet
  150. *
  151. * A cache of edges whose source terminal is the key
  152. */
  153. mxSwimlaneLayout.prototype.edgesCache = null;
  154. /**
  155. * Variable: edgesSet
  156. *
  157. * A cache of edges whose source terminal is the key
  158. */
  159. mxHierarchicalLayout.prototype.edgeSourceTermCache = null;
  160. /**
  161. * Variable: edgesSet
  162. *
  163. * A cache of edges whose source terminal is the key
  164. */
  165. mxHierarchicalLayout.prototype.edgesTargetTermCache = null;
  166. /**
  167. * Variable: edgeStyle
  168. *
  169. * The style to apply between cell layers to edge segments.
  170. * Default is <mxHierarchicalEdgeStyle.POLYLINE>.
  171. */
  172. mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
  173. /**
  174. * Function: getModel
  175. *
  176. * Returns the internal <mxSwimlaneModel> for this layout algorithm.
  177. */
  178. mxSwimlaneLayout.prototype.getModel = function()
  179. {
  180. return this.model;
  181. };
  182. /**
  183. * Function: execute
  184. *
  185. * Executes the layout for the children of the specified parent.
  186. *
  187. * Parameters:
  188. *
  189. * parent - Parent <mxCell> that contains the children to be laid out.
  190. * swimlanes - Ordered array of swimlanes to be laid out
  191. */
  192. mxSwimlaneLayout.prototype.execute = function(parent, swimlanes)
  193. {
  194. this.parent = parent;
  195. var model = this.graph.model;
  196. this.edgesCache = new mxDictionary();
  197. this.edgeSourceTermCache = new mxDictionary();
  198. this.edgesTargetTermCache = new mxDictionary();
  199. // If the roots are set and the parent is set, only
  200. // use the roots that are some dependent of the that
  201. // parent.
  202. // If just the root are set, use them as-is
  203. // If just the parent is set use it's immediate
  204. // children as the initial set
  205. if (swimlanes == null || swimlanes.length < 1)
  206. {
  207. // TODO indicate the problem
  208. return;
  209. }
  210. if (parent == null)
  211. {
  212. parent = model.getParent(swimlanes[0]);
  213. }
  214. // Maintaining parent location
  215. this.parentX = null;
  216. this.parentY = null;
  217. if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
  218. {
  219. var geo = this.graph.getCellGeometry(parent);
  220. if (geo != null)
  221. {
  222. this.parentX = geo.x;
  223. this.parentY = geo.y;
  224. }
  225. }
  226. this.swimlanes = swimlanes;
  227. var dummyVertices = [];
  228. // Check the swimlanes all have vertices
  229. // in them
  230. for (var i = 0; i < swimlanes.length; i++)
  231. {
  232. var children = this.graph.getChildCells(swimlanes[i]);
  233. if (children == null || children.length == 0)
  234. {
  235. var vertex = this.graph.insertVertex(swimlanes[i], null, null, 0, 0, this.dummyVertexWidth, 0);
  236. dummyVertices.push(vertex);
  237. }
  238. }
  239. model.beginUpdate();
  240. try
  241. {
  242. this.run(parent);
  243. if (this.resizeParent && !this.graph.isCellCollapsed(parent))
  244. {
  245. this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
  246. }
  247. // Maintaining parent location
  248. if (this.parentX != null && this.parentY != null)
  249. {
  250. var geo = this.graph.getCellGeometry(parent);
  251. if (geo != null)
  252. {
  253. geo = geo.clone();
  254. geo.x = this.parentX;
  255. geo.y = this.parentY;
  256. model.setGeometry(parent, geo);
  257. }
  258. }
  259. this.graph.removeCells(dummyVertices);
  260. }
  261. finally
  262. {
  263. model.endUpdate();
  264. }
  265. };
  266. /**
  267. * Function: updateGroupBounds
  268. *
  269. * Updates the bounds of the given array of groups so that it includes
  270. * all child vertices.
  271. *
  272. */
  273. mxSwimlaneLayout.prototype.updateGroupBounds = function()
  274. {
  275. // Get all vertices and edge in the layout
  276. var cells = [];
  277. var model = this.model;
  278. for (var key in model.edgeMapper)
  279. {
  280. var edge = model.edgeMapper[key];
  281. for (var i = 0; i < edge.edges.length; i++)
  282. {
  283. cells.push(edge.edges[i]);
  284. }
  285. }
  286. var layoutBounds = this.graph.getBoundingBoxFromGeometry(cells, true);
  287. var childBounds = [];
  288. for (var i = 0; i < this.swimlanes.length; i++)
  289. {
  290. var lane = this.swimlanes[i];
  291. var geo = this.graph.getCellGeometry(lane);
  292. if (geo != null)
  293. {
  294. var children = this.graph.getChildCells(lane);
  295. var size = (this.graph.isSwimlane(lane)) ?
  296. this.graph.getStartSize(lane) : new mxRectangle();
  297. var bounds = this.graph.getBoundingBoxFromGeometry(children);
  298. childBounds[i] = bounds;
  299. var childrenY = bounds.y + geo.y - size.height - this.parentBorder;
  300. var maxChildrenY = bounds.y + geo.y + bounds.height;
  301. if (layoutBounds == null)
  302. {
  303. layoutBounds = new mxRectangle(0, childrenY, 0, maxChildrenY - childrenY);
  304. }
  305. else
  306. {
  307. layoutBounds.y = Math.min(layoutBounds.y, childrenY);
  308. var maxY = Math.max(layoutBounds.y + layoutBounds.height, maxChildrenY);
  309. layoutBounds.height = maxY - layoutBounds.y;
  310. }
  311. }
  312. }
  313. for (var i = 0; i < this.swimlanes.length; i++)
  314. {
  315. var lane = this.swimlanes[i];
  316. var geo = this.graph.getCellGeometry(lane);
  317. if (geo != null)
  318. {
  319. var children = this.graph.getChildCells(lane);
  320. var size = (this.graph.isSwimlane(lane)) ?
  321. this.graph.getStartSize(lane) : new mxRectangle();
  322. var newGeo = geo.clone();
  323. var leftGroupBorder = (i == 0) ? this.parentBorder : this.interRankCellSpacing/2;
  324. var w = size.width + leftGroupBorder;
  325. var x = childBounds[i].x - w;
  326. var y = layoutBounds.y - this.parentBorder;
  327. newGeo.x += x;
  328. newGeo.y = y;
  329. newGeo.width = childBounds[i].width + w + this.interRankCellSpacing/2;
  330. newGeo.height = layoutBounds.height + size.height + 2 * this.parentBorder;
  331. this.graph.model.setGeometry(lane, newGeo);
  332. this.graph.moveCells(children, -x, geo.y - y);
  333. }
  334. }
  335. };
  336. /**
  337. * Function: findRoots
  338. *
  339. * Returns all visible children in the given parent which do not have
  340. * incoming edges. If the result is empty then the children with the
  341. * maximum difference between incoming and outgoing edges are returned.
  342. * This takes into account edges that are being promoted to the given
  343. * root due to invisible children or collapsed cells.
  344. *
  345. * Parameters:
  346. *
  347. * parent - <mxCell> whose children should be checked.
  348. * vertices - array of vertices to limit search to
  349. */
  350. mxSwimlaneLayout.prototype.findRoots = function(parent, vertices)
  351. {
  352. var roots = [];
  353. if (parent != null && vertices != null)
  354. {
  355. var model = this.graph.model;
  356. var best = null;
  357. var maxDiff = -100000;
  358. for (var i in vertices)
  359. {
  360. var cell = vertices[i];
  361. if (cell != null && model.isVertex(cell) && this.graph.isCellVisible(cell) && model.isAncestor(parent, cell))
  362. {
  363. var conns = this.getEdges(cell);
  364. var fanOut = 0;
  365. var fanIn = 0;
  366. for (var k = 0; k < conns.length; k++)
  367. {
  368. var src = this.getVisibleTerminal(conns[k], true);
  369. if (src == cell)
  370. {
  371. // Only count connection within this swimlane
  372. var other = this.getVisibleTerminal(conns[k], false);
  373. if (model.isAncestor(parent, other))
  374. {
  375. fanOut++;
  376. }
  377. }
  378. else if (model.isAncestor(parent, src))
  379. {
  380. fanIn++;
  381. }
  382. }
  383. if (fanIn == 0 && fanOut > 0)
  384. {
  385. roots.push(cell);
  386. }
  387. var diff = fanOut - fanIn;
  388. if (diff > maxDiff)
  389. {
  390. maxDiff = diff;
  391. best = cell;
  392. }
  393. }
  394. }
  395. if (roots.length == 0 && best != null)
  396. {
  397. roots.push(best);
  398. }
  399. }
  400. return roots;
  401. };
  402. /**
  403. * Function: getEdges
  404. *
  405. * Returns the connected edges for the given cell.
  406. *
  407. * Parameters:
  408. *
  409. * cell - <mxCell> whose edges should be returned.
  410. */
  411. mxSwimlaneLayout.prototype.getEdges = function(cell)
  412. {
  413. var cachedEdges = this.edgesCache.get(cell);
  414. if (cachedEdges != null)
  415. {
  416. return cachedEdges;
  417. }
  418. var model = this.graph.model;
  419. var edges = [];
  420. var isCollapsed = this.graph.isCellCollapsed(cell);
  421. var childCount = model.getChildCount(cell);
  422. for (var i = 0; i < childCount; i++)
  423. {
  424. var child = model.getChildAt(cell, i);
  425. if (this.isPort(child))
  426. {
  427. edges = edges.concat(model.getEdges(child, true, true));
  428. }
  429. else if (isCollapsed || !this.graph.isCellVisible(child))
  430. {
  431. edges = edges.concat(model.getEdges(child, true, true));
  432. }
  433. }
  434. edges = edges.concat(model.getEdges(cell, true, true));
  435. var result = [];
  436. for (var i = 0; i < edges.length; i++)
  437. {
  438. var source = this.getVisibleTerminal(edges[i], true);
  439. var target = this.getVisibleTerminal(edges[i], false);
  440. if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
  441. (source == cell && (this.parent == null ||
  442. this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
  443. {
  444. result.push(edges[i]);
  445. }
  446. }
  447. this.edgesCache.put(cell, result);
  448. return result;
  449. };
  450. /**
  451. * Function: getVisibleTerminal
  452. *
  453. * Helper function to return visible terminal for edge allowing for ports
  454. *
  455. * Parameters:
  456. *
  457. * edge - <mxCell> whose edges should be returned.
  458. * source - Boolean that specifies whether the source or target terminal is to be returned
  459. */
  460. mxSwimlaneLayout.prototype.getVisibleTerminal = function(edge, source)
  461. {
  462. var terminalCache = this.edgesTargetTermCache;
  463. if (source)
  464. {
  465. terminalCache = this.edgeSourceTermCache;
  466. }
  467. var term = terminalCache.get(edge);
  468. if (term != null)
  469. {
  470. return term;
  471. }
  472. var state = this.graph.view.getState(edge);
  473. var terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
  474. if (terminal == null)
  475. {
  476. terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
  477. }
  478. if (terminal != null)
  479. {
  480. if (this.isPort(terminal))
  481. {
  482. terminal = this.graph.model.getParent(terminal);
  483. }
  484. terminalCache.put(edge, terminal);
  485. }
  486. return terminal;
  487. };
  488. /**
  489. * Function: run
  490. *
  491. * The API method used to exercise the layout upon the graph description
  492. * and produce a separate description of the vertex position and edge
  493. * routing changes made. It runs each stage of the layout that has been
  494. * created.
  495. */
  496. mxSwimlaneLayout.prototype.run = function(parent)
  497. {
  498. // Separate out unconnected hierarchies
  499. var hierarchyVertices = [];
  500. var allVertexSet = Object();
  501. if (this.swimlanes != null && this.swimlanes.length > 0 && parent != null)
  502. {
  503. var filledVertexSet = Object();
  504. for (var i = 0; i < this.swimlanes.length; i++)
  505. {
  506. this.filterDescendants(this.swimlanes[i], filledVertexSet);
  507. }
  508. this.roots = [];
  509. var filledVertexSetEmpty = true;
  510. // Poor man's isSetEmpty
  511. for (var key in filledVertexSet)
  512. {
  513. if (filledVertexSet[key] != null)
  514. {
  515. filledVertexSetEmpty = false;
  516. break;
  517. }
  518. }
  519. // Only test for candidates in each swimlane in order
  520. var laneCounter = 0;
  521. while (!filledVertexSetEmpty && laneCounter < this.swimlanes.length)
  522. {
  523. var candidateRoots = this.findRoots(this.swimlanes[laneCounter], filledVertexSet);
  524. if (candidateRoots.length == 0)
  525. {
  526. laneCounter++;
  527. continue;
  528. }
  529. // If the candidate root is an unconnected group cell, remove it from
  530. // the layout. We may need a custom set that holds such groups and forces
  531. // them to be processed for resizing and/or moving.
  532. for (var i = 0; i < candidateRoots.length; i++)
  533. {
  534. var vertexSet = Object();
  535. hierarchyVertices.push(vertexSet);
  536. this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
  537. hierarchyVertices, filledVertexSet, laneCounter);
  538. }
  539. for (var i = 0; i < candidateRoots.length; i++)
  540. {
  541. this.roots.push(candidateRoots[i]);
  542. }
  543. filledVertexSetEmpty = true;
  544. // Poor man's isSetEmpty
  545. for (var key in filledVertexSet)
  546. {
  547. if (filledVertexSet[key] != null)
  548. {
  549. filledVertexSetEmpty = false;
  550. break;
  551. }
  552. }
  553. }
  554. }
  555. else
  556. {
  557. // Find vertex set as directed traversal from roots
  558. for (var i = 0; i < this.roots.length; i++)
  559. {
  560. var vertexSet = Object();
  561. hierarchyVertices.push(vertexSet);
  562. this.traverse(this.roots[i], true, null, allVertexSet, vertexSet,
  563. hierarchyVertices, null);
  564. }
  565. }
  566. var tmp = [];
  567. for (var key in allVertexSet)
  568. {
  569. tmp.push(allVertexSet[key]);
  570. }
  571. this.model = new mxSwimlaneModel(this, tmp, this.roots,
  572. parent, this.tightenToSource);
  573. this.cycleStage(parent);
  574. this.layeringStage();
  575. this.crossingStage(parent);
  576. this.placementStage(0, parent);
  577. };
  578. /**
  579. * Function: filterDescendants
  580. *
  581. * Creates an array of descendant cells
  582. */
  583. mxSwimlaneLayout.prototype.filterDescendants = function(cell, result)
  584. {
  585. var model = this.graph.model;
  586. if (model.isVertex(cell) && cell != this.parent && model.getParent(cell) != this.parent && this.graph.isCellVisible(cell))
  587. {
  588. result[mxObjectIdentity.get(cell)] = cell;
  589. }
  590. if (this.traverseAncestors || cell == this.parent
  591. && this.graph.isCellVisible(cell))
  592. {
  593. var childCount = model.getChildCount(cell);
  594. for (var i = 0; i < childCount; i++)
  595. {
  596. var child = model.getChildAt(cell, i);
  597. // Ignore ports in the layout vertex list, they are dealt with
  598. // in the traversal mechanisms
  599. if (!this.isPort(child))
  600. {
  601. this.filterDescendants(child, result);
  602. }
  603. }
  604. }
  605. };
  606. /**
  607. * Function: isPort
  608. *
  609. * Returns true if the given cell is a "port", that is, when connecting to
  610. * it, its parent is the connecting vertex in terms of graph traversal
  611. *
  612. * Parameters:
  613. *
  614. * cell - <mxCell> that represents the port.
  615. */
  616. mxSwimlaneLayout.prototype.isPort = function(cell)
  617. {
  618. if (cell.geometry.relative)
  619. {
  620. return true;
  621. }
  622. return false;
  623. };
  624. /**
  625. * Function: getEdgesBetween
  626. *
  627. * Returns the edges between the given source and target. This takes into
  628. * account collapsed and invisible cells and ports.
  629. *
  630. * Parameters:
  631. *
  632. * source -
  633. * target -
  634. * directed -
  635. */
  636. mxSwimlaneLayout.prototype.getEdgesBetween = function(source, target, directed)
  637. {
  638. directed = (directed != null) ? directed : false;
  639. var edges = this.getEdges(source);
  640. var result = [];
  641. // Checks if the edge is connected to the correct
  642. // cell and returns the first match
  643. for (var i = 0; i < edges.length; i++)
  644. {
  645. var src = this.getVisibleTerminal(edges[i], true);
  646. var trg = this.getVisibleTerminal(edges[i], false);
  647. if ((src == source && trg == target) || (!directed && src == target && trg == source))
  648. {
  649. result.push(edges[i]);
  650. }
  651. }
  652. return result;
  653. };
  654. /**
  655. * Traverses the (directed) graph invoking the given function for each
  656. * visited vertex and edge. The function is invoked with the current vertex
  657. * and the incoming edge as a parameter. This implementation makes sure
  658. * each vertex is only visited once. The function may return false if the
  659. * traversal should stop at the given vertex.
  660. *
  661. * Parameters:
  662. *
  663. * vertex - <mxCell> that represents the vertex where the traversal starts.
  664. * directed - boolean indicating if edges should only be traversed
  665. * from source to target. Default is true.
  666. * edge - Optional <mxCell> that represents the incoming edge. This is
  667. * null for the first step of the traversal.
  668. * allVertices - Array of cell paths for the visited cells.
  669. * swimlaneIndex - the laid out order index of the swimlane vertex is contained in
  670. */
  671. mxSwimlaneLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
  672. hierarchyVertices, filledVertexSet, swimlaneIndex)
  673. {
  674. if (vertex != null && allVertices != null)
  675. {
  676. // Has this vertex been seen before in any traversal
  677. // And if the filled vertex set is populated, only
  678. // process vertices in that it contains
  679. var vertexID = mxObjectIdentity.get(vertex);
  680. if ((allVertices[vertexID] == null)
  681. && (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
  682. {
  683. if (currentComp[vertexID] == null)
  684. {
  685. currentComp[vertexID] = vertex;
  686. }
  687. if (allVertices[vertexID] == null)
  688. {
  689. allVertices[vertexID] = vertex;
  690. }
  691. if (filledVertexSet !== null)
  692. {
  693. delete filledVertexSet[vertexID];
  694. }
  695. var edges = this.getEdges(vertex);
  696. var model = this.graph.model;
  697. for (var i = 0; i < edges.length; i++)
  698. {
  699. var otherVertex = this.getVisibleTerminal(edges[i], true);
  700. var isSource = otherVertex == vertex;
  701. if (isSource)
  702. {
  703. otherVertex = this.getVisibleTerminal(edges[i], false);
  704. }
  705. var otherIndex = 0;
  706. // Get the swimlane index of the other terminal
  707. for (otherIndex = 0; otherIndex < this.swimlanes.length; otherIndex++)
  708. {
  709. if (model.isAncestor(this.swimlanes[otherIndex], otherVertex))
  710. {
  711. break;
  712. }
  713. }
  714. if (otherIndex >= this.swimlanes.length)
  715. {
  716. continue;
  717. }
  718. // Traverse if the other vertex is within the same swimlane as
  719. // as the current vertex, or if the swimlane index of the other
  720. // vertex is greater than that of this vertex
  721. if ((otherIndex > swimlaneIndex) ||
  722. ((!directed || isSource) && otherIndex == swimlaneIndex))
  723. {
  724. currentComp = this.traverse(otherVertex, directed, edges[i], allVertices,
  725. currentComp, hierarchyVertices,
  726. filledVertexSet, otherIndex);
  727. }
  728. }
  729. }
  730. else
  731. {
  732. if (currentComp[vertexID] == null)
  733. {
  734. // We've seen this vertex before, but not in the current component
  735. // This component and the one it's in need to be merged
  736. for (var i = 0; i < hierarchyVertices.length; i++)
  737. {
  738. var comp = hierarchyVertices[i];
  739. if (comp[vertexID] != null)
  740. {
  741. for (var key in comp)
  742. {
  743. currentComp[key] = comp[key];
  744. }
  745. // Remove the current component from the hierarchy set
  746. hierarchyVertices.splice(i, 1);
  747. return currentComp;
  748. }
  749. }
  750. }
  751. }
  752. }
  753. return currentComp;
  754. };
  755. /**
  756. * Function: cycleStage
  757. *
  758. * Executes the cycle stage using mxMinimumCycleRemover.
  759. */
  760. mxSwimlaneLayout.prototype.cycleStage = function(parent)
  761. {
  762. var cycleStage = new mxSwimlaneOrdering(this);
  763. cycleStage.execute(parent);
  764. };
  765. /**
  766. * Function: layeringStage
  767. *
  768. * Implements first stage of a Sugiyama layout.
  769. */
  770. mxSwimlaneLayout.prototype.layeringStage = function()
  771. {
  772. this.model.initialRank();
  773. this.model.fixRanks();
  774. };
  775. /**
  776. * Function: crossingStage
  777. *
  778. * Executes the crossing stage using mxMedianHybridCrossingReduction.
  779. */
  780. mxSwimlaneLayout.prototype.crossingStage = function(parent)
  781. {
  782. var crossingStage = new mxMedianHybridCrossingReduction(this);
  783. crossingStage.execute(parent);
  784. };
  785. /**
  786. * Function: placementStage
  787. *
  788. * Executes the placement stage using mxCoordinateAssignment.
  789. */
  790. mxSwimlaneLayout.prototype.placementStage = function(initialX, parent)
  791. {
  792. var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
  793. this.interRankCellSpacing, this.orientation, initialX,
  794. this.parallelEdgeSpacing);
  795. placementStage.fineTuning = this.fineTuning;
  796. placementStage.execute(parent);
  797. return placementStage.limitX + this.interHierarchySpacing;
  798. };