ConnectionSegmentMove.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. import {
  2. pointsAligned,
  3. pointsOnLine
  4. } from '../../util/Geometry';
  5. import {
  6. addSegmentDragger
  7. } from './BendpointUtil';
  8. import {
  9. getMid,
  10. getOrientation
  11. } from '../../layout/LayoutUtil';
  12. var MARKER_CONNECT_HOVER = 'connect-hover',
  13. MARKER_CONNECT_UPDATING = 'djs-updating';
  14. import {
  15. classes as svgClasses,
  16. remove as svgRemove
  17. } from 'tiny-svg';
  18. import {
  19. translate
  20. } from '../../util/SvgTransformUtil';
  21. function axisAdd(point, axis, delta) {
  22. return axisSet(point, axis, point[axis] + delta);
  23. }
  24. function axisSet(point, axis, value) {
  25. return {
  26. x: (axis === 'x' ? value : point.x),
  27. y: (axis === 'y' ? value : point.y)
  28. };
  29. }
  30. function axisFenced(position, segmentStart, segmentEnd, axis) {
  31. var maxValue = Math.max(segmentStart[axis], segmentEnd[axis]),
  32. minValue = Math.min(segmentStart[axis], segmentEnd[axis]);
  33. var padding = 20;
  34. var fencedValue = Math.min(Math.max(minValue + padding, position[axis]), maxValue - padding);
  35. return axisSet(segmentStart, axis, fencedValue);
  36. }
  37. function flipAxis(axis) {
  38. return axis === 'x' ? 'y' : 'x';
  39. }
  40. /**
  41. * Get the docking point on the given element.
  42. *
  43. * Compute a reasonable docking, if non exists.
  44. *
  45. * @param {Point} point
  46. * @param {djs.model.Shape} referenceElement
  47. * @param {String} moveAxis (x|y)
  48. *
  49. * @return {Point}
  50. */
  51. function getDocking(point, referenceElement, moveAxis) {
  52. var referenceMid,
  53. inverseAxis;
  54. if (point.original) {
  55. return point.original;
  56. } else {
  57. referenceMid = getMid(referenceElement);
  58. inverseAxis = flipAxis(moveAxis);
  59. return axisSet(point, inverseAxis, referenceMid[inverseAxis]);
  60. }
  61. }
  62. /**
  63. * A component that implements moving of bendpoints
  64. */
  65. export default function ConnectionSegmentMove(
  66. injector, eventBus, canvas,
  67. dragging, graphicsFactory, rules,
  68. modeling) {
  69. // optional connection docking integration
  70. var connectionDocking = injector.get('connectionDocking', false);
  71. // API
  72. this.start = function(event, connection, idx) {
  73. var context,
  74. gfx = canvas.getGraphics(connection),
  75. segmentStartIndex = idx - 1,
  76. segmentEndIndex = idx,
  77. waypoints = connection.waypoints,
  78. segmentStart = waypoints[segmentStartIndex],
  79. segmentEnd = waypoints[segmentEndIndex],
  80. direction,
  81. axis;
  82. direction = pointsAligned(segmentStart, segmentEnd);
  83. // do not move diagonal connection
  84. if (!direction) {
  85. return;
  86. }
  87. // the axis where we are going to move things
  88. axis = direction === 'v' ? 'y' : 'x';
  89. if (segmentStartIndex === 0) {
  90. segmentStart = getDocking(segmentStart, connection.source, axis);
  91. }
  92. if (segmentEndIndex === waypoints.length - 1) {
  93. segmentEnd = getDocking(segmentEnd, connection.target, axis);
  94. }
  95. context = {
  96. connection: connection,
  97. segmentStartIndex: segmentStartIndex,
  98. segmentEndIndex: segmentEndIndex,
  99. segmentStart: segmentStart,
  100. segmentEnd: segmentEnd,
  101. axis: axis
  102. };
  103. dragging.init(event, {
  104. x: (segmentStart.x + segmentEnd.x)/2,
  105. y: (segmentStart.y + segmentEnd.y)/2
  106. }, 'connectionSegment.move', {
  107. cursor: axis === 'x' ? 'resize-ew' : 'resize-ns',
  108. data: {
  109. connection: connection,
  110. connectionGfx: gfx,
  111. context: context
  112. }
  113. });
  114. };
  115. /**
  116. * Crop connection if connection cropping is provided.
  117. *
  118. * @param {Connection} connection
  119. * @param {Array<Point>} newWaypoints
  120. *
  121. * @return {Array<Point>} cropped connection waypoints
  122. */
  123. function cropConnection(connection, newWaypoints) {
  124. // crop connection, if docking service is provided only
  125. if (!connectionDocking) {
  126. return newWaypoints;
  127. }
  128. var oldWaypoints = connection.waypoints,
  129. croppedWaypoints;
  130. // temporary set new waypoints
  131. connection.waypoints = newWaypoints;
  132. croppedWaypoints = connectionDocking.getCroppedWaypoints(connection);
  133. // restore old waypoints
  134. connection.waypoints = oldWaypoints;
  135. return croppedWaypoints;
  136. }
  137. // DRAGGING IMPLEMENTATION
  138. function redrawConnection(data) {
  139. graphicsFactory.update('connection', data.connection, data.connectionGfx);
  140. }
  141. function updateDragger(context, segmentOffset, event) {
  142. var newWaypoints = context.newWaypoints,
  143. segmentStartIndex = context.segmentStartIndex + segmentOffset,
  144. segmentStart = newWaypoints[segmentStartIndex],
  145. segmentEndIndex = context.segmentEndIndex + segmentOffset,
  146. segmentEnd = newWaypoints[segmentEndIndex],
  147. axis = flipAxis(context.axis);
  148. // make sure the dragger does not move
  149. // outside the connection
  150. var draggerPosition = axisFenced(event, segmentStart, segmentEnd, axis);
  151. // update dragger
  152. translate(context.draggerGfx, draggerPosition.x, draggerPosition.y);
  153. }
  154. /**
  155. * Filter waypoints for redundant ones (i.e. on the same axis).
  156. * Returns the filtered waypoints and the offset related to the segment move.
  157. *
  158. * @param {Array<Point>} waypoints
  159. * @param {Integer} segmentStartIndex of moved segment start
  160. *
  161. * @return {Object} { filteredWaypoints, segmentOffset }
  162. */
  163. function filterRedundantWaypoints(waypoints, segmentStartIndex) {
  164. var segmentOffset = 0;
  165. var filteredWaypoints = waypoints.filter(function(r, idx) {
  166. if (pointsOnLine(waypoints[idx - 1], waypoints[idx + 1], r)) {
  167. // remove point and increment offset
  168. segmentOffset = idx <= segmentStartIndex ? segmentOffset - 1 : segmentOffset;
  169. return false;
  170. }
  171. // dont remove point
  172. return true;
  173. });
  174. return {
  175. waypoints: filteredWaypoints,
  176. segmentOffset: segmentOffset
  177. };
  178. }
  179. eventBus.on('connectionSegment.move.start', function(e) {
  180. var context = e.context,
  181. connection = e.connection,
  182. layer = canvas.getLayer('overlays');
  183. context.originalWaypoints = connection.waypoints.slice();
  184. // add dragger gfx
  185. context.draggerGfx = addSegmentDragger(layer, context.segmentStart, context.segmentEnd);
  186. svgClasses(context.draggerGfx).add('djs-dragging');
  187. canvas.addMarker(connection, MARKER_CONNECT_UPDATING);
  188. });
  189. eventBus.on('connectionSegment.move.move', function(e) {
  190. var context = e.context,
  191. connection = context.connection,
  192. segmentStartIndex = context.segmentStartIndex,
  193. segmentEndIndex = context.segmentEndIndex,
  194. segmentStart = context.segmentStart,
  195. segmentEnd = context.segmentEnd,
  196. axis = context.axis;
  197. var newWaypoints = context.originalWaypoints.slice(),
  198. newSegmentStart = axisAdd(segmentStart, axis, e['d' + axis]),
  199. newSegmentEnd = axisAdd(segmentEnd, axis, e['d' + axis]);
  200. // original waypoint count and added / removed
  201. // from start waypoint delta. We use the later
  202. // to retrieve the updated segmentStartIndex / segmentEndIndex
  203. var waypointCount = newWaypoints.length,
  204. segmentOffset = 0;
  205. // move segment start / end by axis delta
  206. newWaypoints[segmentStartIndex] = newSegmentStart;
  207. newWaypoints[segmentEndIndex] = newSegmentEnd;
  208. var sourceToSegmentOrientation,
  209. targetToSegmentOrientation;
  210. // handle first segment
  211. if (segmentStartIndex < 2) {
  212. sourceToSegmentOrientation = getOrientation(connection.source, newSegmentStart);
  213. // first bendpoint, remove first segment if intersecting
  214. if (segmentStartIndex === 1) {
  215. if (sourceToSegmentOrientation === 'intersect') {
  216. newWaypoints.shift();
  217. newWaypoints[0] = newSegmentStart;
  218. segmentOffset--;
  219. }
  220. }
  221. // docking point, add segment if not intersecting anymore
  222. else {
  223. if (sourceToSegmentOrientation !== 'intersect') {
  224. newWaypoints.unshift(segmentStart);
  225. segmentOffset++;
  226. }
  227. }
  228. }
  229. // handle last segment
  230. if (segmentEndIndex > waypointCount - 3) {
  231. targetToSegmentOrientation = getOrientation(connection.target, newSegmentEnd);
  232. // last bendpoint, remove last segment if intersecting
  233. if (segmentEndIndex === waypointCount - 2) {
  234. if (targetToSegmentOrientation === 'intersect') {
  235. newWaypoints.pop();
  236. newWaypoints[newWaypoints.length - 1] = newSegmentEnd;
  237. }
  238. }
  239. // last bendpoint, remove last segment if intersecting
  240. else {
  241. if (targetToSegmentOrientation !== 'intersect') {
  242. newWaypoints.push(segmentEnd);
  243. }
  244. }
  245. }
  246. // update connection waypoints
  247. context.newWaypoints = connection.waypoints = cropConnection(connection, newWaypoints);
  248. // update dragger position
  249. updateDragger(context, segmentOffset, e);
  250. // save segmentOffset in context
  251. context.newSegmentStartIndex = segmentStartIndex + segmentOffset;
  252. // redraw connection
  253. redrawConnection(e);
  254. });
  255. eventBus.on('connectionSegment.move.hover', function(e) {
  256. e.context.hover = e.hover;
  257. canvas.addMarker(e.hover, MARKER_CONNECT_HOVER);
  258. });
  259. eventBus.on([
  260. 'connectionSegment.move.out',
  261. 'connectionSegment.move.cleanup'
  262. ], function(e) {
  263. // remove connect marker
  264. // if it was added
  265. var hover = e.context.hover;
  266. if (hover) {
  267. canvas.removeMarker(hover, MARKER_CONNECT_HOVER);
  268. }
  269. });
  270. eventBus.on('connectionSegment.move.cleanup', function(e) {
  271. var context = e.context,
  272. connection = context.connection;
  273. // remove dragger gfx
  274. if (context.draggerGfx) {
  275. svgRemove(context.draggerGfx);
  276. }
  277. canvas.removeMarker(connection, MARKER_CONNECT_UPDATING);
  278. });
  279. eventBus.on([
  280. 'connectionSegment.move.cancel',
  281. 'connectionSegment.move.end'
  282. ], function(e) {
  283. var context = e.context,
  284. connection = context.connection;
  285. connection.waypoints = context.originalWaypoints;
  286. redrawConnection(e);
  287. });
  288. eventBus.on('connectionSegment.move.end', function(e) {
  289. var context = e.context,
  290. connection = context.connection,
  291. newWaypoints = context.newWaypoints,
  292. newSegmentStartIndex = context.newSegmentStartIndex;
  293. // ensure we have actual pixel values bendpoint
  294. // coordinates (important when zoom level was > 1 during move)
  295. newWaypoints = newWaypoints.map(function(p) {
  296. return {
  297. original: p.original,
  298. x: Math.round(p.x),
  299. y: Math.round(p.y)
  300. };
  301. });
  302. // apply filter redunant waypoints
  303. var filtered = filterRedundantWaypoints(newWaypoints, newSegmentStartIndex);
  304. // get filtered waypoints
  305. var filteredWaypoints = filtered.waypoints,
  306. croppedWaypoints = cropConnection(connection, filteredWaypoints),
  307. segmentOffset = filtered.segmentOffset;
  308. var hints = {
  309. segmentMove: {
  310. segmentStartIndex: context.segmentStartIndex,
  311. newSegmentStartIndex: newSegmentStartIndex + segmentOffset
  312. }
  313. };
  314. modeling.updateWaypoints(connection, croppedWaypoints, hints);
  315. });
  316. }
  317. ConnectionSegmentMove.$inject = [
  318. 'injector',
  319. 'eventBus',
  320. 'canvas',
  321. 'dragging',
  322. 'graphicsFactory',
  323. 'rules',
  324. 'modeling'
  325. ];