mxParallelEdgeLayout.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxParallelEdgeLayout
  7. *
  8. * Extends <mxGraphLayout> for arranging parallel edges. This layout works
  9. * on edges for all pairs of vertices where there is more than one edge
  10. * connecting the latter.
  11. *
  12. * Example:
  13. *
  14. * (code)
  15. * var layout = new mxParallelEdgeLayout(graph);
  16. * layout.execute(graph.getDefaultParent());
  17. * (end)
  18. *
  19. * To run the layout for the parallel edges of a changed edge only, the
  20. * following code can be used.
  21. *
  22. * (code)
  23. * var layout = new mxParallelEdgeLayout(graph);
  24. *
  25. * graph.addListener(mxEvent.CELL_CONNECTED, function(sender, evt)
  26. * {
  27. * var model = graph.getModel();
  28. * var edge = evt.getProperty('edge');
  29. * var src = model.getTerminal(edge, true);
  30. * var trg = model.getTerminal(edge, false);
  31. *
  32. * layout.isEdgeIgnored = function(edge2)
  33. * {
  34. * var src2 = model.getTerminal(edge2, true);
  35. * var trg2 = model.getTerminal(edge2, false);
  36. *
  37. * return !(model.isEdge(edge2) && ((src == src2 && trg == trg2) || (src == trg2 && trg == src2)));
  38. * };
  39. *
  40. * layout.execute(graph.getDefaultParent());
  41. * });
  42. * (end)
  43. *
  44. * Constructor: mxParallelEdgeLayout
  45. *
  46. * Constructs a new parallel edge layout for the specified graph.
  47. */
  48. function mxParallelEdgeLayout(graph)
  49. {
  50. mxGraphLayout.call(this, graph);
  51. };
  52. /**
  53. * Extends mxGraphLayout.
  54. */
  55. mxParallelEdgeLayout.prototype = new mxGraphLayout();
  56. mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;
  57. /**
  58. * Variable: spacing
  59. *
  60. * Defines the spacing between the parallels. Default is 20.
  61. */
  62. mxParallelEdgeLayout.prototype.spacing = 20;
  63. /**
  64. * Variable: checkOverlap
  65. *
  66. * Specifies if only overlapping edges should be considered
  67. * parallel. Default is false.
  68. */
  69. mxParallelEdgeLayout.prototype.checkOverlap = false;
  70. /**
  71. * Function: execute
  72. *
  73. * Implements <mxGraphLayout.execute>.
  74. */
  75. mxParallelEdgeLayout.prototype.execute = function(parent, cells)
  76. {
  77. var lookup = this.findParallels(parent, cells);
  78. this.graph.model.beginUpdate();
  79. try
  80. {
  81. for (var i in lookup)
  82. {
  83. var parallels = lookup[i];
  84. if (parallels.length > 1)
  85. {
  86. this.layout(parallels);
  87. }
  88. }
  89. }
  90. finally
  91. {
  92. this.graph.model.endUpdate();
  93. }
  94. };
  95. /**
  96. * Function: findParallels
  97. *
  98. * Finds the parallel edges in the given parent.
  99. */
  100. mxParallelEdgeLayout.prototype.findParallels = function(parent, cells)
  101. {
  102. var lookup = [];
  103. var addCell = mxUtils.bind(this, function(cell)
  104. {
  105. if (!this.isEdgeIgnored(cell))
  106. {
  107. var id = this.getEdgeId(cell);
  108. if (id != null)
  109. {
  110. if (lookup[id] == null)
  111. {
  112. lookup[id] = [];
  113. }
  114. lookup[id].push(cell);
  115. }
  116. }
  117. });
  118. if (cells != null)
  119. {
  120. for (var i = 0; i < cells.length; i++)
  121. {
  122. addCell(cells[i]);
  123. }
  124. }
  125. else
  126. {
  127. var model = this.graph.getModel();
  128. var childCount = model.getChildCount(parent);
  129. for (var i = 0; i < childCount; i++)
  130. {
  131. addCell(model.getChildAt(parent, i));
  132. }
  133. }
  134. return lookup;
  135. };
  136. /**
  137. * Function: getEdgeId
  138. *
  139. * Returns a unique ID for the given edge. The id is independent of the
  140. * edge direction and is built using the visible terminal of the given
  141. * edge.
  142. */
  143. mxParallelEdgeLayout.prototype.getEdgeId = function(edge)
  144. {
  145. var view = this.graph.getView();
  146. // Cannot used cached visible terminal because this could be triggered in BEFORE_UNDO
  147. var src = view.getVisibleTerminal(edge, true);
  148. var trg = view.getVisibleTerminal(edge, false);
  149. var pts = '';
  150. if (src != null && trg != null)
  151. {
  152. src = mxObjectIdentity.get(src);
  153. trg = mxObjectIdentity.get(trg);
  154. if (this.checkOverlap)
  155. {
  156. var state = this.graph.view.getState(edge);
  157. if (state != null && state.absolutePoints != null)
  158. {
  159. var tmp = [];
  160. for (var i = 0; i < state.absolutePoints.length; i++)
  161. {
  162. var pt = state.absolutePoints[i];
  163. if (pt != null)
  164. {
  165. tmp.push(pt.x, pt.y);
  166. }
  167. }
  168. pts = tmp.join(',');
  169. }
  170. };
  171. return ((src > trg) ? trg + '-' + src : src + '-' + trg) + pts;
  172. }
  173. return null;
  174. };
  175. /**
  176. * Function: layout
  177. *
  178. * Lays out the parallel edges in the given array.
  179. */
  180. mxParallelEdgeLayout.prototype.layout = function(parallels)
  181. {
  182. var edge = parallels[0];
  183. var view = this.graph.getView();
  184. var model = this.graph.getModel();
  185. var src = model.getGeometry(view.getVisibleTerminal(edge, true));
  186. var trg = model.getGeometry(view.getVisibleTerminal(edge, false));
  187. // Routes multiple loops
  188. if (src == trg)
  189. {
  190. var x0 = src.x + src.width + this.spacing;
  191. var y0 = src.y + src.height / 2;
  192. for (var i = 0; i < parallels.length; i++)
  193. {
  194. this.route(parallels[i], x0, y0);
  195. x0 += this.spacing;
  196. }
  197. }
  198. else if (src != null && trg != null)
  199. {
  200. // Routes parallel edges
  201. var scx = src.x + src.width / 2;
  202. var scy = src.y + src.height / 2;
  203. var tcx = trg.x + trg.width / 2;
  204. var tcy = trg.y + trg.height / 2;
  205. var dx = tcx - scx;
  206. var dy = tcy - scy;
  207. var len = Math.sqrt(dx * dx + dy * dy);
  208. if (len > 0)
  209. {
  210. var x0 = scx + dx / 2;
  211. var y0 = scy + dy / 2;
  212. var nx = dy * this.spacing / len;
  213. var ny = dx * this.spacing / len;
  214. x0 += nx * (parallels.length - 1) / 2;
  215. y0 -= ny * (parallels.length - 1) / 2;
  216. for (var i = 0; i < parallels.length; i++)
  217. {
  218. this.route(parallels[i], x0, y0);
  219. x0 -= nx;
  220. y0 += ny;
  221. }
  222. }
  223. }
  224. };
  225. /**
  226. * Function: route
  227. *
  228. * Routes the given edge via the given point.
  229. */
  230. mxParallelEdgeLayout.prototype.route = function(edge, x, y)
  231. {
  232. if (this.graph.isCellMovable(edge))
  233. {
  234. this.setEdgePoints(edge, [new mxPoint(x, y)]);
  235. }
  236. };
  237. __mxOutput.mxParallelEdgeLayout = typeof mxParallelEdgeLayout !== 'undefined' ? mxParallelEdgeLayout : undefined;