mxEdgeBridge.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. /**
  2. * Copyright (c) 2017, CTI LOGIC
  3. * Copyright (c) 2006-2017, JGraph Ltd
  4. * Copyright (c) 2006-2017, Gaudenz Alder
  5. *
  6. * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  9. *
  10. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  11. *
  12. * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  15. * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  16. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  17. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  18. */
  19. (function ()
  20. {
  21. mxConnector.prototype.defaultJumpSize = 6;
  22. mxConnector.prototype.checkLineIntersection = function (line1Start, line1End, line2Start, line2End)
  23. {
  24. // if the lines intersect, the result contains the the
  25. // intersection point if both line segment 1 and line segment 2 contain the point
  26. // null otherwise
  27. var denominator = ((line2End.y - line2Start.y) * (line1End.x - line1Start.x)) -
  28. ((line2End.x - line2Start.x) * (line1End.y - line1Start.y));
  29. if (denominator == 0)
  30. {
  31. return null;
  32. }
  33. else
  34. {
  35. var a = line1Start.y - line2Start.y;
  36. var b = line1Start.x - line2Start.x;
  37. var numerator1 = ((line2End.x - line2Start.x) * a) - ((line2End.y - line2Start.y) * b);
  38. var numerator2 = ((line1End.x - line1Start.x) * a) - ((line1End.y - line1Start.y) * b);
  39. a = numerator1 / denominator;
  40. b = numerator2 / denominator;
  41. // if we cast these lines infinitely in both directions, they intersect here:
  42. var x = line1Start.x + (a * (line1End.x - line1Start.x));
  43. var y = line1Start.y + (a * (line1End.y - line1Start.y));
  44. // on both lines?
  45. if (a > 0 && a < 1 && b > 0 && b < 1)
  46. {
  47. return new mxPoint(x, y);
  48. }
  49. else
  50. {
  51. return null;
  52. }
  53. }
  54. };
  55. // Code from https://stackoverflow.com/questions/27664298/calculating-intersection-point-of-quadratic-bezier-curve
  56. mxConnector.prototype.calcQLintersects = function (p1, p2, p3, a1, a2)
  57. {
  58. //linear interpolation utility
  59. var lerp = function (a, b, x)
  60. {
  61. return (a + x * (b - a));
  62. };
  63. var intersections = [];
  64. // inverse line normal
  65. var normal = {
  66. x: a1.y - a2.y,
  67. y: a2.x - a1.x,
  68. };
  69. // Q-coefficients
  70. var c2 = {
  71. x: p1.x + p2.x * -2 + p3.x,
  72. y: p1.y + p2.y * -2 + p3.y
  73. };
  74. var c1 = {
  75. x: p1.x * -2 + p2.x * 2,
  76. y: p1.y * -2 + p2.y * 2,
  77. };
  78. var c0 = {
  79. x: p1.x,
  80. y: p1.y
  81. };
  82. // Transform to line
  83. var coefficient = a1.x * a2.y - a2.x * a1.y;
  84. var a = normal.x * c2.x + normal.y * c2.y;
  85. var b = (normal.x * c1.x + normal.y * c1.y) / a;
  86. var c = (normal.x * c0.x + normal.y * c0.y + coefficient) / a;
  87. // solve the roots
  88. var roots = [];
  89. d = b * b - 4 * c;
  90. if (d > 0)
  91. {
  92. var e = Math.sqrt(d);
  93. roots.push((-b + Math.sqrt(d)) / 2);
  94. roots.push((-b - Math.sqrt(d)) / 2);
  95. }
  96. else if (d == 0)
  97. {
  98. roots.push(-b / 2);
  99. }
  100. // calc the solution points
  101. for (var i = 0; i < roots.length; i++)
  102. {
  103. var minX = Math.min(a1.x, a2.x);
  104. var minY = Math.min(a1.y, a2.y);
  105. var maxX = Math.max(a1.x, a2.x);
  106. var maxY = Math.max(a1.y, a2.y);
  107. var t = roots[i];
  108. if (t >= 0 && t <= 1)
  109. {
  110. // possible point -- pending bounds check
  111. var point = {
  112. x: lerp(lerp(p1.x, p2.x, t), lerp(p2.x, p3.x, t), t),
  113. y: lerp(lerp(p1.y, p2.y, t), lerp(p2.y, p3.y, t), t)
  114. }
  115. var x = point.x;
  116. var y = point.y;
  117. // bounds checks
  118. if (a1.x == a2.x && y >= minY && y <= maxY)
  119. {
  120. // vertical line
  121. intersections.push(point);
  122. }
  123. else if (a1.y == a2.y && x >= minX && x <= maxX)
  124. {
  125. // horizontal line
  126. intersections.push(point);
  127. }
  128. else if (x >= minX && y >= minY && x <= maxX && y <= maxY)
  129. {
  130. // line passed bounds check
  131. intersections.push(point);
  132. }
  133. }
  134. }
  135. return intersections;
  136. };
  137. mxConnector.prototype.getOnLinePointAtDist = function (x1, x2, y1, y2)
  138. {
  139. var dx = x1 - x2;
  140. var dy = y1 - y2;
  141. var d = Math.sqrt(dy * dy + dx * dx);
  142. var t = this.jumpSize / d;
  143. return new mxPoint((1 - t) * x1 + t * x2, (1 - t) * y1 + t * y2);
  144. };
  145. mxConnector.prototype.addBridgePoints = function (intersectPt, result, l2p1, l2p2, otherId, eState)
  146. {
  147. // instead of reordering, get the order and the top edge has the bridge
  148. var dx = result.x - l2p2.x;
  149. var dy = result.y - l2p2.y;
  150. var dist = Math.sqrt(dx * dx + dy * dy);
  151. dx /= dist;
  152. dy /= dist;
  153. intersectPt.push(this.getOnLinePointAtDist(result.x, l2p1.x, result.y, l2p1.y));
  154. intersectPt.push(new mxPoint(result.x + this.jumpSize * dy, result.y - this.jumpSize * dx));
  155. intersectPt.push(this.getOnLinePointAtDist(result.x, l2p2.x, result.y, l2p2.y));
  156. // add others id here since here is the actual bridge!
  157. if (eState.oldIntersectIds != null)
  158. {
  159. eState.oldIntersectIds[otherId] = true;
  160. }
  161. else
  162. {
  163. eState.oldIntersectIds = {otherId: true};
  164. }
  165. };
  166. mxConnector.prototype.scalePoint = function (point, scale)
  167. {
  168. return {
  169. x: point.x / scale,
  170. y: point.y / scale
  171. };
  172. };
  173. //TODO Add curves support for curve edges (paintCurvedLine) for curve intersecting another curve
  174. var mxConnectorPaintLine = mxConnector.prototype.paintLine;
  175. mxConnector.prototype.paintLine = function (canvas, pts, rounded)
  176. {
  177. this.jumpSize = parseInt(mxUtils.getValue(this.style, 'jumpSize', this.defaultJumpSize));
  178. var jumpStyle = mxUtils.getValue(this.style, 'jumpStyle', 'none');
  179. if (this.outline || jumpStyle === 'none')
  180. {
  181. mxConnectorPaintLine.apply(this, arguments);
  182. }
  183. else
  184. {
  185. var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
  186. canvas.begin();
  187. var ptsCln = pts;
  188. var state = this.state;
  189. var graph = state.view.graph;
  190. // during DnD we can save the extra calculations
  191. var model = graph.getModel();
  192. var noRedraw = state.redrawEdge;
  193. ptsCln = [];
  194. if (state.oldIntersectIds)
  195. {
  196. for (var id in state.oldIntersectIds)
  197. {
  198. var c = model.getCell(id);
  199. if (c != null)
  200. {
  201. var eState = graph.view.getState(c);
  202. if (eState != null && eState.style != null)
  203. {
  204. eState.redrawEdge = true;
  205. eState.shape.redraw();
  206. }
  207. }
  208. }
  209. delete state.oldIntersectIds;
  210. }
  211. // for each pair find the intersection
  212. for (var i = 0; i < pts.length - 1; i++)
  213. {
  214. var l2p1 = pts[i];
  215. var l2p2 = pts[i + 1];
  216. ptsCln.push(l2p1);
  217. var intersectPt = [];
  218. for (var id in model.cells)
  219. {
  220. var edge = model.cells[id];
  221. if (edge.isEdge() && edge != state.cell)
  222. {
  223. var eState = graph.view.getState(edge);
  224. if (eState != null && eState.absolutePoints != null)
  225. {
  226. var eScale = eState.view.scale;
  227. if (eState.style == null || eState.style[mxConstants.STYLE_CURVED] != 1) //line edge
  228. {
  229. for (var j = 0; j < eState.absolutePoints.length - 1; j++)
  230. {
  231. var l1p1 = eState.absolutePoints[j];
  232. var l1p2 = eState.absolutePoints[j + 1];
  233. //the absolute points of current edge is scaled before calling this function, so we have to scale others to get correct intersections
  234. var result = this.checkLineIntersection(this.scalePoint(l1p1, eScale), this.scalePoint(l1p2, eScale), l2p1, l2p2);
  235. if (result != null)
  236. {
  237. var getEdgeOrder = function (edge1, edge2)
  238. {
  239. var p1 = edge1.getParent();
  240. var p2 = edge2.getParent();
  241. if (p1 == p2)
  242. {
  243. return p1.getIndex(edge1) - p1.getIndex(edge2); //+ve edge1 on top, -ve edge2 on top
  244. }
  245. else
  246. {
  247. return 0; //not the same parent, so we cannot determine
  248. }
  249. };
  250. if (getEdgeOrder(state.cell, eState.cell) > 0) //instead of reordering, get the order and the top edge has the bridge
  251. {
  252. this.addBridgePoints(intersectPt, result, l2p1, l2p2, state.cell.id, eState);
  253. }
  254. else if (!noRedraw)
  255. {
  256. eState.redrawEdge = true;
  257. eState.shape.redraw();
  258. }
  259. }
  260. }
  261. }
  262. else // curve edge (guad Bezier)
  263. {
  264. // We'll not check edge order here as always line will have bridges as it is better than curve bridges
  265. var ePts = eState.absolutePoints;
  266. var pt = this.scalePoint(ePts[0], eScale);
  267. var n = ePts.length;
  268. var allPts = [];
  269. for (var k = 1; k < n - 2; k++)
  270. {
  271. var p0 = this.scalePoint(ePts[k], eScale);
  272. var p1 = this.scalePoint(ePts[k + 1], eScale);
  273. var ip = {
  274. x: (p0.x + p1.x) / 2,
  275. y: (p0.y + p1.y) / 2
  276. };
  277. allPts.push.apply(allPts, this.calcQLintersects(pt, p0, ip, l2p1, l2p2));
  278. pt = ip;
  279. }
  280. var p0 = this.scalePoint(ePts[n - 2], eScale);
  281. var p1 = this.scalePoint(ePts[n - 1], eScale);
  282. allPts.push.apply(allPts, this.calcQLintersects(pt, p0, p1, l2p1, l2p2));
  283. if (allPts.length > 0)
  284. {
  285. for (var o = 0; o < allPts.length; o++)
  286. {
  287. this.addBridgePoints(intersectPt, allPts[o], l2p1, l2p2, state.cell.id, eState);
  288. }
  289. }
  290. }
  291. }
  292. }
  293. }
  294. if (intersectPt.length > 0)
  295. {
  296. // sort points and then add them
  297. intersectPt.sort(function (p1, p2)
  298. {
  299. var dx1 = p1.x - l2p1.x;
  300. var dy1 = p1.y - l2p1.y;
  301. var d1 = dx1 * dx1 + dy1 * dy1;
  302. var dx2 = p2.x - l2p1.x;
  303. var dy2 = p2.y - l2p1.y;
  304. var d2 = dx2 * dx2 + dy2 * dy2;
  305. return d1 - d2;
  306. });
  307. ptsCln.push(intersectPt[0]);
  308. this.addPoints(canvas, ptsCln, rounded, arcSize, false);
  309. ptsCln = [intersectPt[intersectPt.length - 1]];
  310. if (jumpStyle !== 'gap')
  311. {
  312. this.addPoints(canvas, intersectPt, jumpStyle !== 'square', arcSize, false);
  313. }
  314. }
  315. ptsCln.push(l2p2);
  316. }
  317. if (noRedraw)
  318. {
  319. state.redrawEdge = false;
  320. }
  321. this.addPoints(canvas, ptsCln, rounded, arcSize, false);
  322. canvas.stroke();
  323. }
  324. };
  325. })();