mxCompactTreeLayout.js 23 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  1. /**
  2. * Copyright (c) 2006-2018, JGraph Ltd
  3. * Copyright (c) 2006-2018, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxCompactTreeLayout
  7. *
  8. * Extends <mxGraphLayout> to implement a compact tree (Moen) algorithm. This
  9. * layout is suitable for graphs that have no cycles (trees). Vertices that are
  10. * not connected to the tree will be ignored by this layout.
  11. *
  12. * Example:
  13. *
  14. * (code)
  15. * var layout = new mxCompactTreeLayout(graph);
  16. * layout.execute(graph.getDefaultParent());
  17. * (end)
  18. *
  19. * Constructor: mxCompactTreeLayout
  20. *
  21. * Constructs a new compact tree layout for the specified graph
  22. * and orientation.
  23. */
  24. function mxCompactTreeLayout(graph, horizontal, invert)
  25. {
  26. mxGraphLayout.call(this, graph);
  27. this.horizontal = (horizontal != null) ? horizontal : true;
  28. this.invert = (invert != null) ? invert : false;
  29. };
  30. /**
  31. * Extends mxGraphLayout.
  32. */
  33. mxCompactTreeLayout.prototype = new mxGraphLayout();
  34. mxCompactTreeLayout.prototype.constructor = mxCompactTreeLayout;
  35. /**
  36. * Variable: horizontal
  37. *
  38. * Specifies the orientation of the layout. Default is true.
  39. */
  40. mxCompactTreeLayout.prototype.horizontal = null;
  41. /**
  42. * Variable: invert
  43. *
  44. * Specifies if edge directions should be inverted. Default is false.
  45. */
  46. mxCompactTreeLayout.prototype.invert = null;
  47. /**
  48. * Variable: resizeParent
  49. *
  50. * If the parents should be resized to match the width/height of the
  51. * children. Default is true.
  52. */
  53. mxCompactTreeLayout.prototype.resizeParent = true;
  54. /**
  55. * Variable: maintainParentLocation
  56. *
  57. * Specifies if the parent location should be maintained, so that the
  58. * top, left corner stays the same before and after execution of
  59. * the layout. Default is false for backwards compatibility.
  60. */
  61. mxCompactTreeLayout.prototype.maintainParentLocation = false;
  62. /**
  63. * Variable: groupPadding
  64. *
  65. * Padding added to resized parents. Default is 10.
  66. */
  67. mxCompactTreeLayout.prototype.groupPadding = 10;
  68. /**
  69. * Variable: groupPaddingTop
  70. *
  71. * Top padding added to resized parents. Default is 0.
  72. */
  73. mxCompactTreeLayout.prototype.groupPaddingTop = 0;
  74. /**
  75. * Variable: groupPaddingRight
  76. *
  77. * Right padding added to resized parents. Default is 0.
  78. */
  79. mxCompactTreeLayout.prototype.groupPaddingRight = 0;
  80. /**
  81. * Variable: groupPaddingBottom
  82. *
  83. * Bottom padding added to resized parents. Default is 0.
  84. */
  85. mxCompactTreeLayout.prototype.groupPaddingBottom = 0;
  86. /**
  87. * Variable: groupPaddingLeft
  88. *
  89. * Left padding added to resized parents. Default is 0.
  90. */
  91. mxCompactTreeLayout.prototype.groupPaddingLeft = 0;
  92. /**
  93. * Variable: parentsChanged
  94. *
  95. * A set of the parents that need updating based on children
  96. * process as part of the layout.
  97. */
  98. mxCompactTreeLayout.prototype.parentsChanged = null;
  99. /**
  100. * Variable: moveTree
  101. *
  102. * Specifies if the tree should be moved to the top, left corner
  103. * if it is inside a top-level layer. Default is false.
  104. */
  105. mxCompactTreeLayout.prototype.moveTree = false;
  106. /**
  107. * Variable: visited
  108. *
  109. * Specifies if the tree should be moved to the top, left corner
  110. * if it is inside a top-level layer. Default is false.
  111. */
  112. mxCompactTreeLayout.prototype.visited = null;
  113. /**
  114. * Variable: levelDistance
  115. *
  116. * Holds the levelDistance. Default is 10.
  117. */
  118. mxCompactTreeLayout.prototype.levelDistance = 10;
  119. /**
  120. * Variable: nodeDistance
  121. *
  122. * Holds the nodeDistance. Default is 20.
  123. */
  124. mxCompactTreeLayout.prototype.nodeDistance = 20;
  125. /**
  126. * Variable: resetEdges
  127. *
  128. * Specifies if all edge points of traversed edges should be removed.
  129. * Default is true.
  130. */
  131. mxCompactTreeLayout.prototype.resetEdges = true;
  132. /**
  133. * Variable: prefHozEdgeSep
  134. *
  135. * The preferred horizontal distance between edges exiting a vertex.
  136. */
  137. mxCompactTreeLayout.prototype.prefHozEdgeSep = 5;
  138. /**
  139. * Variable: prefVertEdgeOff
  140. *
  141. * The preferred vertical offset between edges exiting a vertex.
  142. */
  143. mxCompactTreeLayout.prototype.prefVertEdgeOff = 4;
  144. /**
  145. * Variable: minEdgeJetty
  146. *
  147. * The minimum distance for an edge jetty from a vertex.
  148. */
  149. mxCompactTreeLayout.prototype.minEdgeJetty = 8;
  150. /**
  151. * Variable: channelBuffer
  152. *
  153. * The size of the vertical buffer in the center of inter-rank channels
  154. * where edge control points should not be placed.
  155. */
  156. mxCompactTreeLayout.prototype.channelBuffer = 4;
  157. /**
  158. * Variable: edgeRouting
  159. *
  160. * Whether or not to apply the internal tree edge routing.
  161. */
  162. mxCompactTreeLayout.prototype.edgeRouting = true;
  163. /**
  164. * Variable: sortEdges
  165. *
  166. * Specifies if edges should be sorted according to the order of their
  167. * opposite terminal cell in the model.
  168. */
  169. mxCompactTreeLayout.prototype.sortEdges = false;
  170. /**
  171. * Variable: alignRanks
  172. *
  173. * Whether or not the tops of cells in each rank should be aligned
  174. * across the rank
  175. */
  176. mxCompactTreeLayout.prototype.alignRanks = false;
  177. /**
  178. * Variable: maxRankHeight
  179. *
  180. * An array of the maximum height of cells (relative to the layout direction)
  181. * per rank
  182. */
  183. mxCompactTreeLayout.prototype.maxRankHeight = null;
  184. /**
  185. * Variable: root
  186. *
  187. * The cell to use as the root of the tree
  188. */
  189. mxCompactTreeLayout.prototype.root = null;
  190. /**
  191. * Variable: node
  192. *
  193. * The internal node representation of the root cell. Do not set directly
  194. * , this value is only exposed to assist with post-processing functionality
  195. */
  196. mxCompactTreeLayout.prototype.node = null;
  197. /**
  198. * Function: isVertexIgnored
  199. *
  200. * Returns a boolean indicating if the given <mxCell> should be ignored as a
  201. * vertex. This returns true if the cell has no connections.
  202. *
  203. * Parameters:
  204. *
  205. * vertex - <mxCell> whose ignored state should be returned.
  206. */
  207. mxCompactTreeLayout.prototype.isVertexIgnored = function(vertex)
  208. {
  209. return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
  210. this.graph.getConnections(vertex).length == 0;
  211. };
  212. /**
  213. * Function: isHorizontal
  214. *
  215. * Returns <horizontal>.
  216. */
  217. mxCompactTreeLayout.prototype.isHorizontal = function()
  218. {
  219. return this.horizontal;
  220. };
  221. /**
  222. * Function: execute
  223. *
  224. * Implements <mxGraphLayout.execute>.
  225. *
  226. * If the parent has any connected edges, then it is used as the root of
  227. * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
  228. * root node within the set of children of the given parent.
  229. *
  230. * Parameters:
  231. *
  232. * parent - <mxCell> whose children should be laid out.
  233. * root - Optional <mxCell> that will be used as the root of the tree.
  234. * Overrides <root> if specified.
  235. */
  236. mxCompactTreeLayout.prototype.execute = function(parent, root)
  237. {
  238. this.parent = parent;
  239. var model = this.graph.getModel();
  240. if (root == null)
  241. {
  242. // Takes the parent as the root if it has outgoing edges
  243. if (this.graph.getEdges(parent, model.getParent(parent),
  244. this.invert, !this.invert, false).length > 0)
  245. {
  246. this.root = parent;
  247. }
  248. // Tries to find a suitable root in the parent's
  249. // children
  250. else
  251. {
  252. var roots = this.graph.findTreeRoots(parent, true, this.invert);
  253. if (roots.length > 0)
  254. {
  255. for (var i = 0; i < roots.length; i++)
  256. {
  257. if (!this.isVertexIgnored(roots[i]) &&
  258. this.graph.getEdges(roots[i], null,
  259. this.invert, !this.invert, false).length > 0)
  260. {
  261. this.root = roots[i];
  262. break;
  263. }
  264. }
  265. }
  266. }
  267. }
  268. else
  269. {
  270. this.root = root;
  271. }
  272. if (this.root != null)
  273. {
  274. if (this.resizeParent)
  275. {
  276. this.parentsChanged = new Object();
  277. }
  278. else
  279. {
  280. this.parentsChanged = null;
  281. }
  282. // Maintaining parent location
  283. this.parentX = null;
  284. this.parentY = null;
  285. if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
  286. {
  287. var geo = this.graph.getCellGeometry(parent);
  288. if (geo != null)
  289. {
  290. this.parentX = geo.x;
  291. this.parentY = geo.y;
  292. }
  293. }
  294. model.beginUpdate();
  295. try
  296. {
  297. this.visited = new Object();
  298. this.node = this.dfs(this.root, parent);
  299. if (this.alignRanks)
  300. {
  301. this.maxRankHeight = [];
  302. this.findRankHeights(this.node, 0);
  303. this.setCellHeights(this.node, 0);
  304. }
  305. if (this.node != null)
  306. {
  307. this.layout(this.node);
  308. var x0 = this.graph.gridSize;
  309. var y0 = x0;
  310. if (!this.moveTree)
  311. {
  312. var g = this.getVertexBounds(this.root);
  313. if (g != null)
  314. {
  315. x0 = g.x;
  316. y0 = g.y;
  317. }
  318. }
  319. var bounds = null;
  320. if (this.isHorizontal())
  321. {
  322. bounds = this.horizontalLayout(this.node, x0, y0);
  323. }
  324. else
  325. {
  326. bounds = this.verticalLayout(this.node, null, x0, y0);
  327. }
  328. if (bounds != null)
  329. {
  330. var dx = 0;
  331. var dy = 0;
  332. if (bounds.x < 0)
  333. {
  334. dx = Math.abs(x0 - bounds.x);
  335. }
  336. if (bounds.y < 0)
  337. {
  338. dy = Math.abs(y0 - bounds.y);
  339. }
  340. if (dx != 0 || dy != 0)
  341. {
  342. this.moveNode(this.node, dx, dy);
  343. }
  344. if (this.resizeParent)
  345. {
  346. this.adjustParents();
  347. }
  348. if (this.edgeRouting)
  349. {
  350. // Iterate through all edges setting their positions
  351. this.localEdgeProcessing(this.node);
  352. }
  353. }
  354. // Maintaining parent location
  355. if (this.parentX != null && this.parentY != null)
  356. {
  357. var geo = this.graph.getCellGeometry(parent);
  358. if (geo != null)
  359. {
  360. geo = geo.clone();
  361. geo.x = this.parentX;
  362. geo.y = this.parentY;
  363. model.setGeometry(parent, geo);
  364. }
  365. }
  366. }
  367. }
  368. finally
  369. {
  370. model.endUpdate();
  371. }
  372. }
  373. };
  374. /**
  375. * Function: moveNode
  376. *
  377. * Moves the specified node and all of its children by the given amount.
  378. */
  379. mxCompactTreeLayout.prototype.moveNode = function(node, dx, dy)
  380. {
  381. node.x += dx;
  382. node.y += dy;
  383. this.apply(node);
  384. var child = node.child;
  385. while (child != null)
  386. {
  387. this.moveNode(child, dx, dy);
  388. child = child.next;
  389. }
  390. };
  391. /**
  392. * Function: sortOutgoingEdges
  393. *
  394. * Called if <sortEdges> is true to sort the array of outgoing edges in place.
  395. */
  396. mxCompactTreeLayout.prototype.sortOutgoingEdges = function(source, edges)
  397. {
  398. var lookup = new mxDictionary();
  399. edges.sort(function(e1, e2)
  400. {
  401. var end1 = e1.getTerminal(e1.getTerminal(false) == source);
  402. var p1 = lookup.get(end1);
  403. if (p1 == null)
  404. {
  405. p1 = mxCellPath.create(end1).split(mxCellPath.PATH_SEPARATOR);
  406. lookup.put(end1, p1);
  407. }
  408. var end2 = e2.getTerminal(e2.getTerminal(false) == source);
  409. var p2 = lookup.get(end2);
  410. if (p2 == null)
  411. {
  412. p2 = mxCellPath.create(end2).split(mxCellPath.PATH_SEPARATOR);
  413. lookup.put(end2, p2);
  414. }
  415. return mxCellPath.compare(p1, p2);
  416. });
  417. };
  418. /**
  419. * Function: findRankHeights
  420. *
  421. * Stores the maximum height (relative to the layout
  422. * direction) of cells in each rank
  423. */
  424. mxCompactTreeLayout.prototype.findRankHeights = function(node, rank)
  425. {
  426. if (this.maxRankHeight[rank] == null || this.maxRankHeight[rank] < node.height)
  427. {
  428. this.maxRankHeight[rank] = node.height;
  429. }
  430. var child = node.child;
  431. while (child != null)
  432. {
  433. this.findRankHeights(child, rank + 1);
  434. child = child.next;
  435. }
  436. };
  437. /**
  438. * Function: setCellHeights
  439. *
  440. * Set the cells heights (relative to the layout
  441. * direction) when the tops of each rank are to be aligned
  442. */
  443. mxCompactTreeLayout.prototype.setCellHeights = function(node, rank)
  444. {
  445. if (this.maxRankHeight[rank] != null && this.maxRankHeight[rank] > node.height)
  446. {
  447. node.height = this.maxRankHeight[rank];
  448. }
  449. var child = node.child;
  450. while (child != null)
  451. {
  452. this.setCellHeights(child, rank + 1);
  453. child = child.next;
  454. }
  455. };
  456. /**
  457. * Function: dfs
  458. *
  459. * Does a depth first search starting at the specified cell.
  460. * Makes sure the specified parent is never left by the
  461. * algorithm.
  462. */
  463. mxCompactTreeLayout.prototype.dfs = function(cell, parent)
  464. {
  465. var id = mxCellPath.create(cell);
  466. var node = null;
  467. if (cell != null && this.visited[id] == null && !this.isVertexIgnored(cell))
  468. {
  469. this.visited[id] = cell;
  470. node = this.createNode(cell);
  471. var model = this.graph.getModel();
  472. var prev = null;
  473. var out = this.graph.getEdges(cell, parent, this.invert, !this.invert, false, true);
  474. var view = this.graph.getView();
  475. if (this.sortEdges)
  476. {
  477. this.sortOutgoingEdges(cell, out);
  478. }
  479. for (var i = 0; i < out.length; i++)
  480. {
  481. var edge = out[i];
  482. if (!this.isEdgeIgnored(edge))
  483. {
  484. // Resets the points on the traversed edge
  485. if (this.resetEdges)
  486. {
  487. this.setEdgePoints(edge, null);
  488. }
  489. if (this.edgeRouting)
  490. {
  491. this.setEdgeStyleEnabled(edge, false);
  492. this.setEdgePoints(edge, null);
  493. }
  494. // Checks if terminal in same swimlane
  495. var state = view.getState(edge);
  496. var target = (state != null) ? state.getVisibleTerminal(this.invert) : view.getVisibleTerminal(edge, this.invert);
  497. var tmp = this.dfs(target, parent);
  498. if (tmp != null && model.getGeometry(target) != null)
  499. {
  500. if (prev == null)
  501. {
  502. node.child = tmp;
  503. }
  504. else
  505. {
  506. prev.next = tmp;
  507. }
  508. prev = tmp;
  509. }
  510. }
  511. }
  512. }
  513. return node;
  514. };
  515. /**
  516. * Function: layout
  517. *
  518. * Starts the actual compact tree layout algorithm
  519. * at the given node.
  520. */
  521. mxCompactTreeLayout.prototype.layout = function(node)
  522. {
  523. if (node != null)
  524. {
  525. var child = node.child;
  526. while (child != null)
  527. {
  528. this.layout(child);
  529. child = child.next;
  530. }
  531. if (node.child != null)
  532. {
  533. this.attachParent(node, this.join(node));
  534. }
  535. else
  536. {
  537. this.layoutLeaf(node);
  538. }
  539. }
  540. };
  541. /**
  542. * Function: horizontalLayout
  543. */
  544. mxCompactTreeLayout.prototype.horizontalLayout = function(node, x0, y0, bounds)
  545. {
  546. node.x += x0 + node.offsetX;
  547. node.y += y0 + node.offsetY;
  548. bounds = this.apply(node, bounds);
  549. var child = node.child;
  550. if (child != null)
  551. {
  552. bounds = this.horizontalLayout(child, node.x, node.y, bounds);
  553. var siblingOffset = node.y + child.offsetY;
  554. var s = child.next;
  555. while (s != null)
  556. {
  557. bounds = this.horizontalLayout(s, node.x + child.offsetX, siblingOffset, bounds);
  558. siblingOffset += s.offsetY;
  559. s = s.next;
  560. }
  561. }
  562. return bounds;
  563. };
  564. /**
  565. * Function: verticalLayout
  566. */
  567. mxCompactTreeLayout.prototype.verticalLayout = function(node, parent, x0, y0, bounds)
  568. {
  569. node.x += x0 + node.offsetY;
  570. node.y += y0 + node.offsetX;
  571. bounds = this.apply(node, bounds);
  572. var child = node.child;
  573. if (child != null)
  574. {
  575. bounds = this.verticalLayout(child, node, node.x, node.y, bounds);
  576. var siblingOffset = node.x + child.offsetY;
  577. var s = child.next;
  578. while (s != null)
  579. {
  580. bounds = this.verticalLayout(s, node, siblingOffset, node.y + child.offsetX, bounds);
  581. siblingOffset += s.offsetY;
  582. s = s.next;
  583. }
  584. }
  585. return bounds;
  586. };
  587. /**
  588. * Function: attachParent
  589. */
  590. mxCompactTreeLayout.prototype.attachParent = function(node, height)
  591. {
  592. var x = this.nodeDistance + this.levelDistance;
  593. var y2 = (height - node.width) / 2 - this.nodeDistance;
  594. var y1 = y2 + node.width + 2 * this.nodeDistance - height;
  595. node.child.offsetX = x + node.height;
  596. node.child.offsetY = y1;
  597. node.contour.upperHead = this.createLine(node.height, 0,
  598. this.createLine(x, y1, node.contour.upperHead));
  599. node.contour.lowerHead = this.createLine(node.height, 0,
  600. this.createLine(x, y2, node.contour.lowerHead));
  601. };
  602. /**
  603. * Function: layoutLeaf
  604. */
  605. mxCompactTreeLayout.prototype.layoutLeaf = function(node)
  606. {
  607. var dist = 2 * this.nodeDistance;
  608. node.contour.upperTail = this.createLine(
  609. node.height + dist, 0);
  610. node.contour.upperHead = node.contour.upperTail;
  611. node.contour.lowerTail = this.createLine(
  612. 0, -node.width - dist);
  613. node.contour.lowerHead = this.createLine(
  614. node.height + dist, 0, node.contour.lowerTail);
  615. };
  616. /**
  617. * Function: join
  618. */
  619. mxCompactTreeLayout.prototype.join = function(node)
  620. {
  621. var dist = 2 * this.nodeDistance;
  622. var child = node.child;
  623. node.contour = child.contour;
  624. var h = child.width + dist;
  625. var sum = h;
  626. child = child.next;
  627. while (child != null)
  628. {
  629. var d = this.merge(node.contour, child.contour);
  630. child.offsetY = d + h;
  631. child.offsetX = 0;
  632. h = child.width + dist;
  633. sum += d + h;
  634. child = child.next;
  635. }
  636. return sum;
  637. };
  638. /**
  639. * Function: merge
  640. */
  641. mxCompactTreeLayout.prototype.merge = function(p1, p2)
  642. {
  643. var x = 0;
  644. var y = 0;
  645. var total = 0;
  646. var upper = p1.lowerHead;
  647. var lower = p2.upperHead;
  648. while (lower != null && upper != null)
  649. {
  650. var d = this.offset(x, y, lower.dx, lower.dy,
  651. upper.dx, upper.dy);
  652. y += d;
  653. total += d;
  654. if (x + lower.dx <= upper.dx)
  655. {
  656. x += lower.dx;
  657. y += lower.dy;
  658. lower = lower.next;
  659. }
  660. else
  661. {
  662. x -= upper.dx;
  663. y -= upper.dy;
  664. upper = upper.next;
  665. }
  666. }
  667. if (lower != null)
  668. {
  669. var b = this.bridge(p1.upperTail, 0, 0, lower, x, y);
  670. p1.upperTail = (b.next != null) ? p2.upperTail : b;
  671. p1.lowerTail = p2.lowerTail;
  672. }
  673. else
  674. {
  675. var b = this.bridge(p2.lowerTail, x, y, upper, 0, 0);
  676. if (b.next == null)
  677. {
  678. p1.lowerTail = b;
  679. }
  680. }
  681. p1.lowerHead = p2.lowerHead;
  682. return total;
  683. };
  684. /**
  685. * Function: offset
  686. */
  687. mxCompactTreeLayout.prototype.offset = function(p1, p2, a1, a2, b1, b2)
  688. {
  689. var d = 0;
  690. if (b1 <= p1 || p1 + a1 <= 0)
  691. {
  692. return 0;
  693. }
  694. var t = b1 * a2 - a1 * b2;
  695. if (t > 0)
  696. {
  697. if (p1 < 0)
  698. {
  699. var s = p1 * a2;
  700. d = s / a1 - p2;
  701. }
  702. else if (p1 > 0)
  703. {
  704. var s = p1 * b2;
  705. d = s / b1 - p2;
  706. }
  707. else
  708. {
  709. d = -p2;
  710. }
  711. }
  712. else if (b1 < p1 + a1)
  713. {
  714. var s = (b1 - p1) * a2;
  715. d = b2 - (p2 + s / a1);
  716. }
  717. else if (b1 > p1 + a1)
  718. {
  719. var s = (a1 + p1) * b2;
  720. d = s / b1 - (p2 + a2);
  721. }
  722. else
  723. {
  724. d = b2 - (p2 + a2);
  725. }
  726. if (d > 0)
  727. {
  728. return d;
  729. }
  730. else
  731. {
  732. return 0;
  733. }
  734. };
  735. /**
  736. * Function: bridge
  737. */
  738. mxCompactTreeLayout.prototype.bridge = function(line1, x1, y1, line2, x2, y2)
  739. {
  740. var dx = x2 + line2.dx - x1;
  741. var dy = 0;
  742. var s = 0;
  743. if (line2.dx == 0)
  744. {
  745. dy = line2.dy;
  746. }
  747. else
  748. {
  749. s = dx * line2.dy;
  750. dy = s / line2.dx;
  751. }
  752. var r = this.createLine(dx, dy, line2.next);
  753. line1.next = this.createLine(0, y2 + line2.dy - dy - y1, r);
  754. return r;
  755. };
  756. /**
  757. * Function: createNode
  758. */
  759. mxCompactTreeLayout.prototype.createNode = function(cell)
  760. {
  761. var node = new Object();
  762. node.cell = cell;
  763. node.x = 0;
  764. node.y = 0;
  765. node.width = 0;
  766. node.height = 0;
  767. var geo = this.getVertexBounds(cell);
  768. if (geo != null)
  769. {
  770. if (this.isHorizontal())
  771. {
  772. node.width = geo.height;
  773. node.height = geo.width;
  774. }
  775. else
  776. {
  777. node.width = geo.width;
  778. node.height = geo.height;
  779. }
  780. }
  781. node.offsetX = 0;
  782. node.offsetY = 0;
  783. node.contour = new Object();
  784. return node;
  785. };
  786. /**
  787. * Function: apply
  788. */
  789. mxCompactTreeLayout.prototype.apply = function(node, bounds)
  790. {
  791. var model = this.graph.getModel();
  792. var cell = node.cell;
  793. var g = model.getGeometry(cell);
  794. if (cell != null && g != null)
  795. {
  796. if (this.isVertexMovable(cell))
  797. {
  798. g = this.setVertexLocation(cell, node.x, node.y);
  799. if (this.resizeParent)
  800. {
  801. var parent = model.getParent(cell);
  802. var id = mxCellPath.create(parent);
  803. // Implements set semantic
  804. if (this.parentsChanged[id] == null)
  805. {
  806. this.parentsChanged[id] = parent;
  807. }
  808. }
  809. }
  810. if (bounds == null)
  811. {
  812. bounds = new mxRectangle(g.x, g.y, g.width, g.height);
  813. }
  814. else
  815. {
  816. bounds = new mxRectangle(Math.min(bounds.x, g.x),
  817. Math.min(bounds.y, g.y),
  818. Math.max(bounds.x + bounds.width, g.x + g.width),
  819. Math.max(bounds.y + bounds.height, g.y + g.height));
  820. }
  821. }
  822. return bounds;
  823. };
  824. /**
  825. * Function: createLine
  826. */
  827. mxCompactTreeLayout.prototype.createLine = function(dx, dy, next)
  828. {
  829. var line = new Object();
  830. line.dx = dx;
  831. line.dy = dy;
  832. line.next = next;
  833. return line;
  834. };
  835. /**
  836. * Function: adjustParents
  837. *
  838. * Adjust parent cells whose child geometries have changed. The default
  839. * implementation adjusts the group to just fit around the children with
  840. * a padding.
  841. */
  842. mxCompactTreeLayout.prototype.adjustParents = function()
  843. {
  844. var tmp = [];
  845. for (var id in this.parentsChanged)
  846. {
  847. tmp.push(this.parentsChanged[id]);
  848. }
  849. this.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding, this.groupPaddingTop,
  850. this.groupPaddingRight, this.groupPaddingBottom, this.groupPaddingLeft);
  851. };
  852. /**
  853. * Function: localEdgeProcessing
  854. *
  855. * Moves the specified node and all of its children by the given amount.
  856. */
  857. mxCompactTreeLayout.prototype.localEdgeProcessing = function(node)
  858. {
  859. this.processNodeOutgoing(node);
  860. var child = node.child;
  861. while (child != null)
  862. {
  863. this.localEdgeProcessing(child);
  864. child = child.next;
  865. }
  866. };
  867. /**
  868. * Function: processNodeOutgoing
  869. *
  870. * Separates the x position of edges as they connect to vertices
  871. */
  872. mxCompactTreeLayout.prototype.processNodeOutgoing = function(node)
  873. {
  874. var child = node.child;
  875. var parentCell = node.cell;
  876. var childCount = 0;
  877. var sortedCells = [];
  878. while (child != null)
  879. {
  880. childCount++;
  881. var sortingCriterion = child.x;
  882. if (this.horizontal)
  883. {
  884. sortingCriterion = child.y;
  885. }
  886. sortedCells.push(new WeightedCellSorter(child, sortingCriterion));
  887. child = child.next;
  888. }
  889. sortedCells.sort(WeightedCellSorter.prototype.compare);
  890. var availableWidth = node.width;
  891. var requiredWidth = (childCount + 1) * this.prefHozEdgeSep;
  892. // Add a buffer on the edges of the vertex if the edge count allows
  893. if (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))
  894. {
  895. availableWidth -= 2 * this.prefHozEdgeSep;
  896. }
  897. var edgeSpacing = availableWidth / childCount;
  898. var currentXOffset = edgeSpacing / 2.0;
  899. if (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))
  900. {
  901. currentXOffset += this.prefHozEdgeSep;
  902. }
  903. var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
  904. var maxYOffset = 0;
  905. var parentBounds = this.getVertexBounds(parentCell);
  906. child = node.child;
  907. for (var j = 0; j < sortedCells.length; j++)
  908. {
  909. var childCell = sortedCells[j].cell.cell;
  910. var childBounds = this.getVertexBounds(childCell);
  911. var edges = this.graph.getEdgesBetween(parentCell,
  912. childCell, false);
  913. var newPoints = [];
  914. var x = 0;
  915. var y = 0;
  916. for (var i = 0; i < edges.length; i++)
  917. {
  918. if (this.horizontal)
  919. {
  920. // Use opposite co-ords, calculation was done for
  921. //
  922. x = parentBounds.x + parentBounds.width;
  923. y = parentBounds.y + currentXOffset;
  924. newPoints.push(new mxPoint(x, y));
  925. x = parentBounds.x + parentBounds.width
  926. + currentYOffset;
  927. newPoints.push(new mxPoint(x, y));
  928. y = childBounds.y + childBounds.height / 2.0;
  929. newPoints.push(new mxPoint(x, y));
  930. this.setEdgePoints(edges[i], newPoints);
  931. }
  932. else
  933. {
  934. x = parentBounds.x + currentXOffset;
  935. y = parentBounds.y + parentBounds.height;
  936. newPoints.push(new mxPoint(x, y));
  937. y = parentBounds.y + parentBounds.height
  938. + currentYOffset;
  939. newPoints.push(new mxPoint(x, y));
  940. x = childBounds.x + childBounds.width / 2.0;
  941. newPoints.push(new mxPoint(x, y));
  942. this.setEdgePoints(edges[i], newPoints);
  943. }
  944. }
  945. if (j < childCount / 2)
  946. {
  947. currentYOffset += this.prefVertEdgeOff;
  948. }
  949. else if (j > childCount / 2)
  950. {
  951. currentYOffset -= this.prefVertEdgeOff;
  952. }
  953. // Ignore the case if equals, this means the second of 2
  954. // jettys with the same y (even number of edges)
  955. // pos[k * 2] = currentX;
  956. currentXOffset += edgeSpacing;
  957. // pos[k * 2 + 1] = currentYOffset;
  958. maxYOffset = Math.max(maxYOffset, currentYOffset);
  959. }
  960. };
  961. __mxOutput.mxCompactTreeLayout = typeof mxCompactTreeLayout !== 'undefined' ? mxCompactTreeLayout : undefined;