Bendpoints.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import { forEach } from 'min-dash';
  2. import {
  3. event as domEvent,
  4. query as domQuery,
  5. queryAll as domQueryAll
  6. } from 'min-dom';
  7. import {
  8. BENDPOINT_CLS,
  9. SEGMENT_DRAGGER_CLS,
  10. addBendpoint,
  11. addSegmentDragger,
  12. toCanvasCoordinates
  13. } from './BendpointUtil';
  14. import {
  15. escapeCSS
  16. } from '../../util/EscapeUtil';
  17. import {
  18. pointsAligned,
  19. getMidPoint
  20. } from '../../util/Geometry';
  21. import {
  22. getApproxIntersection
  23. } from '../../util/LineIntersection';
  24. import {
  25. append as svgAppend,
  26. attr as svgAttr,
  27. classes as svgClasses,
  28. create as svgCreate,
  29. remove as svgRemove
  30. } from 'tiny-svg';
  31. import { translate } from '../../util/SvgTransformUtil';
  32. /**
  33. * A service that adds editable bendpoints to connections.
  34. */
  35. export default function Bendpoints(
  36. eventBus, canvas, interactionEvents,
  37. bendpointMove, connectionSegmentMove) {
  38. function getConnectionIntersection(waypoints, event) {
  39. var localPosition = toCanvasCoordinates(canvas, event),
  40. intersection = getApproxIntersection(waypoints, localPosition);
  41. return intersection;
  42. }
  43. function isIntersectionMiddle(intersection, waypoints, treshold) {
  44. var idx = intersection.index,
  45. p = intersection.point,
  46. p0, p1, mid, aligned, xDelta, yDelta;
  47. if (idx <= 0 || intersection.bendpoint) {
  48. return false;
  49. }
  50. p0 = waypoints[idx - 1];
  51. p1 = waypoints[idx];
  52. mid = getMidPoint(p0, p1),
  53. aligned = pointsAligned(p0, p1);
  54. xDelta = Math.abs(p.x - mid.x);
  55. yDelta = Math.abs(p.y - mid.y);
  56. return aligned && xDelta <= treshold && yDelta <= treshold;
  57. }
  58. function activateBendpointMove(event, connection) {
  59. var waypoints = connection.waypoints,
  60. intersection = getConnectionIntersection(waypoints, event);
  61. if (!intersection) {
  62. return;
  63. }
  64. if (isIntersectionMiddle(intersection, waypoints, 10)) {
  65. connectionSegmentMove.start(event, connection, intersection.index);
  66. } else {
  67. bendpointMove.start(event, connection, intersection.index, !intersection.bendpoint);
  68. }
  69. // we've handled the event
  70. return true;
  71. }
  72. function bindInteractionEvents(node, eventName, element) {
  73. domEvent.bind(node, eventName, function(event) {
  74. interactionEvents.triggerMouseEvent(eventName, event, element);
  75. event.stopPropagation();
  76. });
  77. }
  78. function getBendpointsContainer(element, create) {
  79. var layer = canvas.getLayer('overlays'),
  80. gfx = domQuery('.djs-bendpoints[data-element-id="' + escapeCSS(element.id) + '"]', layer);
  81. if (!gfx && create) {
  82. gfx = svgCreate('g');
  83. svgAttr(gfx, { 'data-element-id': element.id });
  84. svgClasses(gfx).add('djs-bendpoints');
  85. svgAppend(layer, gfx);
  86. bindInteractionEvents(gfx, 'mousedown', element);
  87. bindInteractionEvents(gfx, 'click', element);
  88. bindInteractionEvents(gfx, 'dblclick', element);
  89. }
  90. return gfx;
  91. }
  92. function createBendpoints(gfx, connection) {
  93. connection.waypoints.forEach(function(p, idx) {
  94. var bendpoint = addBendpoint(gfx);
  95. svgAppend(gfx, bendpoint);
  96. translate(bendpoint, p.x, p.y);
  97. });
  98. // add floating bendpoint
  99. addBendpoint(gfx, 'floating');
  100. }
  101. function createSegmentDraggers(gfx, connection) {
  102. var waypoints = connection.waypoints;
  103. var segmentStart,
  104. segmentEnd;
  105. for (var i = 1; i < waypoints.length; i++) {
  106. segmentStart = waypoints[i - 1];
  107. segmentEnd = waypoints[i];
  108. if (pointsAligned(segmentStart, segmentEnd)) {
  109. addSegmentDragger(gfx, segmentStart, segmentEnd);
  110. }
  111. }
  112. }
  113. function clearBendpoints(gfx) {
  114. forEach(domQueryAll('.' + BENDPOINT_CLS, gfx), function(node) {
  115. svgRemove(node);
  116. });
  117. }
  118. function clearSegmentDraggers(gfx) {
  119. forEach(domQueryAll('.' + SEGMENT_DRAGGER_CLS, gfx), function(node) {
  120. svgRemove(node);
  121. });
  122. }
  123. function addHandles(connection) {
  124. var gfx = getBendpointsContainer(connection);
  125. if (!gfx) {
  126. gfx = getBendpointsContainer(connection, true);
  127. createBendpoints(gfx, connection);
  128. createSegmentDraggers(gfx, connection);
  129. }
  130. return gfx;
  131. }
  132. function updateHandles(connection) {
  133. var gfx = getBendpointsContainer(connection);
  134. if (gfx) {
  135. clearSegmentDraggers(gfx);
  136. clearBendpoints(gfx);
  137. createSegmentDraggers(gfx, connection);
  138. createBendpoints(gfx, connection);
  139. }
  140. }
  141. eventBus.on('connection.changed', function(event) {
  142. updateHandles(event.element);
  143. });
  144. eventBus.on('connection.remove', function(event) {
  145. var gfx = getBendpointsContainer(event.element);
  146. if (gfx) {
  147. svgRemove(gfx);
  148. }
  149. });
  150. eventBus.on('element.marker.update', function(event) {
  151. var element = event.element,
  152. bendpointsGfx;
  153. if (!element.waypoints) {
  154. return;
  155. }
  156. bendpointsGfx = addHandles(element);
  157. if (event.add) {
  158. svgClasses(bendpointsGfx).add(event.marker);
  159. } else {
  160. svgClasses(bendpointsGfx).remove(event.marker);
  161. }
  162. });
  163. eventBus.on('element.mousemove', function(event) {
  164. var element = event.element,
  165. waypoints = element.waypoints,
  166. bendpointsGfx,
  167. floating,
  168. intersection;
  169. if (waypoints) {
  170. bendpointsGfx = getBendpointsContainer(element, true);
  171. floating = domQuery('.floating', bendpointsGfx);
  172. if (!floating) {
  173. return;
  174. }
  175. intersection = getConnectionIntersection(waypoints, event.originalEvent);
  176. if (intersection) {
  177. translate(floating, intersection.point.x, intersection.point.y);
  178. }
  179. }
  180. });
  181. eventBus.on('element.mousedown', function(event) {
  182. var originalEvent = event.originalEvent,
  183. element = event.element,
  184. waypoints = element.waypoints;
  185. if (!waypoints) {
  186. return;
  187. }
  188. return activateBendpointMove(originalEvent, element, waypoints);
  189. });
  190. eventBus.on('selection.changed', function(event) {
  191. var newSelection = event.newSelection,
  192. primary = newSelection[0];
  193. if (primary && primary.waypoints) {
  194. addHandles(primary);
  195. }
  196. });
  197. eventBus.on('element.hover', function(event) {
  198. var element = event.element;
  199. if (element.waypoints) {
  200. addHandles(element);
  201. interactionEvents.registerEvent(event.gfx, 'mousemove', 'element.mousemove');
  202. }
  203. });
  204. eventBus.on('element.out', function(event) {
  205. interactionEvents.unregisterEvent(event.gfx, 'mousemove', 'element.mousemove');
  206. });
  207. // update bendpoint container data attribute on element ID change
  208. eventBus.on('element.updateId', function(context) {
  209. var element = context.element,
  210. newId = context.newId;
  211. if (element.waypoints) {
  212. var bendpointContainer = getBendpointsContainer(element);
  213. if (bendpointContainer) {
  214. svgAttr(bendpointContainer, { 'data-element-id': newId });
  215. }
  216. }
  217. });
  218. // API
  219. this.addHandles = addHandles;
  220. this.updateHandles = updateHandles;
  221. this.getBendpointsContainer = getBendpointsContainer;
  222. }
  223. Bendpoints.$inject = [
  224. 'eventBus',
  225. 'canvas',
  226. 'interactionEvents',
  227. 'bendpointMove',
  228. 'connectionSegmentMove'
  229. ];