mxCompactTreeLayout.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116
  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. };