mxArrowConnector.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxArrowConnector
  7. *
  8. * Extends <mxShape> to implement an new rounded arrow shape with support for
  9. * waypoints and double arrows. (The shape is used to represent edges, not
  10. * vertices.) This shape is registered under <mxConstants.SHAPE_ARROW_CONNECTOR>
  11. * in <mxCellRenderer>.
  12. *
  13. * Constructor: mxArrowConnector
  14. *
  15. * Constructs a new arrow shape.
  16. *
  17. * Parameters:
  18. *
  19. * points - Array of <mxPoints> that define the points. This is stored in
  20. * <mxShape.points>.
  21. * fill - String that defines the fill color. This is stored in <fill>.
  22. * stroke - String that defines the stroke color. This is stored in <stroke>.
  23. * strokewidth - Optional integer that defines the stroke width. Default is
  24. * 1. This is stored in <strokewidth>.
  25. * arrowWidth - Optional integer that defines the arrow width. Default is
  26. * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
  27. * spacing - Optional integer that defines the spacing between the arrow shape
  28. * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
  29. * <spacing>.
  30. * endSize - Optional integer that defines the size of the arrowhead. Default
  31. * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
  32. */
  33. function mxArrowConnector(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
  34. {
  35. mxShape.call(this);
  36. this.points = points;
  37. this.fill = fill;
  38. this.stroke = stroke;
  39. this.strokewidth = (strokewidth != null) ? strokewidth : 1;
  40. this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
  41. this.arrowSpacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
  42. this.startSize = mxConstants.ARROW_SIZE / 5;
  43. this.endSize = mxConstants.ARROW_SIZE / 5;
  44. };
  45. /**
  46. * Extends mxShape.
  47. */
  48. mxUtils.extend(mxArrowConnector, mxShape);
  49. /**
  50. * Variable: useSvgBoundingBox
  51. *
  52. * Allows to use the SVG bounding box in SVG. Default is false for performance
  53. * reasons.
  54. */
  55. mxArrowConnector.prototype.useSvgBoundingBox = true;
  56. /**
  57. * Function: isRoundable
  58. *
  59. * Hook for subclassers.
  60. */
  61. mxArrowConnector.prototype.isRoundable = function()
  62. {
  63. return true;
  64. };
  65. /**
  66. * Variable: resetStyles
  67. *
  68. * Overrides mxShape to reset spacing.
  69. */
  70. mxArrowConnector.prototype.resetStyles = function()
  71. {
  72. mxShape.prototype.resetStyles.apply(this, arguments);
  73. this.arrowSpacing = mxConstants.ARROW_SPACING;
  74. };
  75. /**
  76. * Overrides apply to get smooth transition from default start- and endsize.
  77. */
  78. mxArrowConnector.prototype.apply = function(state)
  79. {
  80. mxShape.prototype.apply.apply(this, arguments);
  81. if (this.style != null)
  82. {
  83. this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) * 3;
  84. this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) * 3;
  85. }
  86. };
  87. /**
  88. * Function: augmentBoundingBox
  89. *
  90. * Augments the bounding box with the edge width and markers.
  91. */
  92. mxArrowConnector.prototype.augmentBoundingBox = function(bbox)
  93. {
  94. mxShape.prototype.augmentBoundingBox.apply(this, arguments);
  95. var w = this.getEdgeWidth();
  96. if (this.isMarkerStart())
  97. {
  98. w = Math.max(w, this.getStartArrowWidth());
  99. }
  100. if (this.isMarkerEnd())
  101. {
  102. w = Math.max(w, this.getEndArrowWidth());
  103. }
  104. bbox.grow((w / 2 + this.strokewidth) * this.scale);
  105. };
  106. /**
  107. * Function: paintEdgeShape
  108. *
  109. * Paints the line shape.
  110. */
  111. mxArrowConnector.prototype.paintEdgeShape = function(c, pts)
  112. {
  113. // Geometry of arrow
  114. var strokeWidth = this.strokewidth;
  115. if (this.outline)
  116. {
  117. strokeWidth = Math.max(1, mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth));
  118. }
  119. var startWidth = this.getStartArrowWidth() + strokeWidth;
  120. var endWidth = this.getEndArrowWidth() + strokeWidth;
  121. var edgeWidth = this.outline ? this.getEdgeWidth() + strokeWidth : this.getEdgeWidth();
  122. var openEnded = this.isOpenEnded();
  123. var markerStart = this.isMarkerStart();
  124. var markerEnd = this.isMarkerEnd();
  125. var spacing = (openEnded) ? 0 : this.arrowSpacing + strokeWidth / 2;
  126. var startSize = this.startSize + strokeWidth;
  127. var endSize = this.endSize + strokeWidth;
  128. var isRounded = this.isArrowRounded();
  129. // Base vector (between first points)
  130. var pe = pts[pts.length - 1];
  131. // Finds first non-overlapping point
  132. var i0 = 1;
  133. while (i0 < pts.length - 1 && pts[i0].x == pts[0].x && pts[i0].y == pts[0].y)
  134. {
  135. i0++;
  136. }
  137. var dx = pts[i0].x - pts[0].x;
  138. var dy = pts[i0].y - pts[0].y;
  139. var dist = Math.sqrt(dx * dx + dy * dy);
  140. if (dist == 0)
  141. {
  142. return;
  143. }
  144. // Computes the norm and the inverse norm
  145. var nx = dx / dist;
  146. var nx2, nx1 = nx;
  147. var ny = dy / dist;
  148. var ny2, ny1 = ny;
  149. var orthx = edgeWidth * ny;
  150. var orthy = -edgeWidth * nx;
  151. // Stores the inbound function calls in reverse order in fns
  152. var fns = [];
  153. if (isRounded)
  154. {
  155. c.setLineJoin('round');
  156. }
  157. else if (pts.length > 2)
  158. {
  159. // Only mitre if there are waypoints
  160. c.setMiterLimit(1.42);
  161. }
  162. c.begin();
  163. var startNx = nx;
  164. var startNy = ny;
  165. if (markerStart && !openEnded)
  166. {
  167. this.paintMarker(c, pts[0].x, pts[0].y, nx, ny, startSize, startWidth, edgeWidth, spacing, true);
  168. }
  169. else
  170. {
  171. var outStartX = pts[0].x + orthx / 2 + spacing * nx;
  172. var outStartY = pts[0].y + orthy / 2 + spacing * ny;
  173. var inEndX = pts[0].x - orthx / 2 + spacing * nx;
  174. var inEndY = pts[0].y - orthy / 2 + spacing * ny;
  175. if (openEnded)
  176. {
  177. c.moveTo(outStartX, outStartY);
  178. fns.push(function()
  179. {
  180. c.lineTo(inEndX, inEndY);
  181. });
  182. }
  183. else
  184. {
  185. c.moveTo(inEndX, inEndY);
  186. c.lineTo(outStartX, outStartY);
  187. }
  188. }
  189. var dx1 = 0;
  190. var dy1 = 0;
  191. var dist1 = 0;
  192. for (var i = 0; i < pts.length - 2; i++)
  193. {
  194. // Work out in which direction the line is bending
  195. var pos = mxUtils.relativeCcw(pts[i].x, pts[i].y, pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y);
  196. dx1 = pts[i+2].x - pts[i+1].x;
  197. dy1 = pts[i+2].y - pts[i+1].y;
  198. dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
  199. if (dist1 != 0)
  200. {
  201. nx1 = dx1 / dist1;
  202. ny1 = dy1 / dist1;
  203. var tmp1 = nx * nx1 + ny * ny1;
  204. var tmp = Math.max(Math.sqrt((tmp1 + 1) / 2), 0.04);
  205. // Work out the normal orthogonal to the line through the control point and the edge sides intersection
  206. nx2 = (nx + nx1);
  207. ny2 = (ny + ny1);
  208. var dist2 = Math.sqrt(nx2 * nx2 + ny2 * ny2);
  209. if (dist2 != 0)
  210. {
  211. nx2 = nx2 / dist2;
  212. ny2 = ny2 / dist2;
  213. // Higher strokewidths require a larger minimum bend, 0.35 covers all but the most extreme cases
  214. var strokeWidthFactor = Math.max(tmp, Math.min(this.strokewidth / 200 + 0.04, 0.35));
  215. var angleFactor = (pos != 0 && isRounded) ? Math.max(0.1, strokeWidthFactor) : Math.max(tmp, 0.06);
  216. var outX = pts[i+1].x + ny2 * edgeWidth / 2 / angleFactor;
  217. var outY = pts[i+1].y - nx2 * edgeWidth / 2 / angleFactor;
  218. var inX = pts[i+1].x - ny2 * edgeWidth / 2 / angleFactor;
  219. var inY = pts[i+1].y + nx2 * edgeWidth / 2 / angleFactor;
  220. if (pos == 0 || !isRounded)
  221. {
  222. // If the two segments are aligned, or if we're not drawing curved sections between segments
  223. // just draw straight to the intersection point
  224. c.lineTo(outX, outY);
  225. (function(x, y)
  226. {
  227. fns.push(function()
  228. {
  229. c.lineTo(x, y);
  230. });
  231. })(inX, inY);
  232. }
  233. else if (pos == -1)
  234. {
  235. var c1x = inX + ny * edgeWidth;
  236. var c1y = inY - nx * edgeWidth;
  237. var c2x = inX + ny1 * edgeWidth;
  238. var c2y = inY - nx1 * edgeWidth;
  239. c.lineTo(c1x, c1y);
  240. c.quadTo(outX, outY, c2x, c2y);
  241. (function(x, y)
  242. {
  243. fns.push(function()
  244. {
  245. c.lineTo(x, y);
  246. });
  247. })(inX, inY);
  248. }
  249. else
  250. {
  251. c.lineTo(outX, outY);
  252. (function(x, y)
  253. {
  254. var c1x = outX - ny * edgeWidth;
  255. var c1y = outY + nx * edgeWidth;
  256. var c2x = outX - ny1 * edgeWidth;
  257. var c2y = outY + nx1 * edgeWidth;
  258. fns.push(function()
  259. {
  260. c.quadTo(x, y, c1x, c1y);
  261. });
  262. fns.push(function()
  263. {
  264. c.lineTo(c2x, c2y);
  265. });
  266. })(inX, inY);
  267. }
  268. nx = nx1;
  269. ny = ny1;
  270. }
  271. }
  272. }
  273. orthx = edgeWidth * ny1;
  274. orthy = - edgeWidth * nx1;
  275. if (markerEnd && !openEnded)
  276. {
  277. this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, false);
  278. }
  279. else
  280. {
  281. c.lineTo(pe.x - spacing * nx1 + orthx / 2, pe.y - spacing * ny1 + orthy / 2);
  282. var inStartX = pe.x - spacing * nx1 - orthx / 2;
  283. var inStartY = pe.y - spacing * ny1 - orthy / 2;
  284. if (!openEnded)
  285. {
  286. c.lineTo(inStartX, inStartY);
  287. }
  288. else
  289. {
  290. c.moveTo(inStartX, inStartY);
  291. fns.splice(0, 0, function()
  292. {
  293. c.moveTo(inStartX, inStartY);
  294. });
  295. }
  296. }
  297. for (var i = fns.length - 1; i >= 0; i--)
  298. {
  299. fns[i]();
  300. }
  301. if (openEnded)
  302. {
  303. c.end();
  304. c.stroke();
  305. }
  306. else
  307. {
  308. c.close();
  309. c.fillAndStroke();
  310. }
  311. // Workaround for shadow on top of base arrow
  312. c.setShadow(false);
  313. // Need to redraw the markers without the low miter limit
  314. c.setMiterLimit(4);
  315. if (isRounded)
  316. {
  317. c.setLineJoin('flat');
  318. }
  319. if (pts.length > 2)
  320. {
  321. // Only to repaint markers if no waypoints
  322. // Need to redraw the markers without the low miter limit
  323. c.setMiterLimit(4);
  324. if (markerStart && !openEnded)
  325. {
  326. c.begin();
  327. this.paintMarker(c, pts[0].x, pts[0].y, startNx, startNy, startSize, startWidth, edgeWidth, spacing, true);
  328. c.stroke();
  329. c.end();
  330. }
  331. if (markerEnd && !openEnded)
  332. {
  333. c.begin();
  334. this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, true);
  335. c.stroke();
  336. c.end();
  337. }
  338. }
  339. };
  340. /**
  341. * Function: paintMarker
  342. *
  343. * Paints the marker.
  344. */
  345. mxArrowConnector.prototype.paintMarker = function(c, ptX, ptY, nx, ny, size, arrowWidth, edgeWidth, spacing, initialMove)
  346. {
  347. var widthArrowRatio = edgeWidth / arrowWidth;
  348. var orthx = edgeWidth * ny / 2;
  349. var orthy = -edgeWidth * nx / 2;
  350. var spaceX = (spacing + size) * nx;
  351. var spaceY = (spacing + size) * ny;
  352. if (initialMove)
  353. {
  354. c.moveTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
  355. }
  356. else
  357. {
  358. c.lineTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
  359. }
  360. c.lineTo(ptX - orthx / widthArrowRatio + spaceX, ptY - orthy / widthArrowRatio + spaceY);
  361. c.lineTo(ptX + spacing * nx, ptY + spacing * ny);
  362. c.lineTo(ptX + orthx / widthArrowRatio + spaceX, ptY + orthy / widthArrowRatio + spaceY);
  363. c.lineTo(ptX + orthx + spaceX, ptY + orthy + spaceY);
  364. }
  365. /**
  366. * Function: isArrowRounded
  367. *
  368. * Returns wether the arrow is rounded
  369. */
  370. mxArrowConnector.prototype.isArrowRounded = function()
  371. {
  372. return this.isRounded;
  373. };
  374. /**
  375. * Function: getStartArrowWidth
  376. *
  377. * Returns the width of the start arrow
  378. */
  379. mxArrowConnector.prototype.getStartArrowWidth = function()
  380. {
  381. return mxConstants.ARROW_WIDTH;
  382. };
  383. /**
  384. * Function: getEndArrowWidth
  385. *
  386. * Returns the width of the end arrow
  387. */
  388. mxArrowConnector.prototype.getEndArrowWidth = function()
  389. {
  390. return mxConstants.ARROW_WIDTH;
  391. };
  392. /**
  393. * Function: getEdgeWidth
  394. *
  395. * Returns the width of the body of the edge
  396. */
  397. mxArrowConnector.prototype.getEdgeWidth = function()
  398. {
  399. return mxConstants.ARROW_WIDTH / 3;
  400. };
  401. /**
  402. * Function: isOpenEnded
  403. *
  404. * Returns whether the ends of the shape are drawn
  405. */
  406. mxArrowConnector.prototype.isOpenEnded = function()
  407. {
  408. return false;
  409. };
  410. /**
  411. * Function: isMarkerStart
  412. *
  413. * Returns whether the start marker is drawn
  414. */
  415. mxArrowConnector.prototype.isMarkerStart = function()
  416. {
  417. return (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE);
  418. };
  419. /**
  420. * Function: isMarkerEnd
  421. *
  422. * Returns whether the end marker is drawn
  423. */
  424. mxArrowConnector.prototype.isMarkerEnd = function()
  425. {
  426. return (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE);
  427. };
  428. __mxOutput.mxArrowConnector = typeof mxArrowConnector !== 'undefined' ? mxArrowConnector : undefined;