BendpointUtil.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import {
  2. toPoint
  3. } from '../../util/Event';
  4. import {
  5. getMidPoint,
  6. pointsAligned
  7. } from '../../util/Geometry';
  8. import {
  9. append as svgAppend,
  10. attr as svgAttr,
  11. classes as svgClasses,
  12. create as svgCreate
  13. } from 'tiny-svg';
  14. import {
  15. rotate,
  16. translate
  17. } from '../../util/SvgTransformUtil';
  18. import {
  19. getApproxIntersection
  20. } from '../../util/LineIntersection';
  21. import { getDistancePointLine, perpendicularFoot } from './GeometricUtil';
  22. export var BENDPOINT_CLS = 'djs-bendpoint';
  23. export var SEGMENT_DRAGGER_CLS = 'djs-segment-dragger';
  24. export function toCanvasCoordinates(canvas, event) {
  25. var position = toPoint(event),
  26. clientRect = canvas._container.getBoundingClientRect(),
  27. offset;
  28. // canvas relative position
  29. offset = {
  30. x: clientRect.left,
  31. y: clientRect.top
  32. };
  33. // update actual event payload with canvas relative measures
  34. var viewbox = canvas.viewbox();
  35. return {
  36. x: viewbox.x + (position.x - offset.x) / viewbox.scale,
  37. y: viewbox.y + (position.y - offset.y) / viewbox.scale
  38. };
  39. }
  40. export function getConnectionIntersection(canvas, waypoints, event) {
  41. var localPosition = toCanvasCoordinates(canvas, event),
  42. intersection = getApproxIntersection(waypoints, localPosition);
  43. return intersection;
  44. }
  45. export function addBendpoint(parentGfx, cls) {
  46. var groupGfx = svgCreate('g');
  47. svgClasses(groupGfx).add(BENDPOINT_CLS);
  48. svgAppend(parentGfx, groupGfx);
  49. var visual = svgCreate('circle');
  50. svgAttr(visual, {
  51. cx: 0,
  52. cy: 0,
  53. r: 4
  54. });
  55. svgClasses(visual).add('djs-visual');
  56. svgAppend(groupGfx, visual);
  57. var hit = svgCreate('circle');
  58. svgAttr(hit, {
  59. cx: 0,
  60. cy: 0,
  61. r: 10
  62. });
  63. svgClasses(hit).add('djs-hit');
  64. svgAppend(groupGfx, hit);
  65. if (cls) {
  66. svgClasses(groupGfx).add(cls);
  67. }
  68. return groupGfx;
  69. }
  70. function createParallelDragger(parentGfx, segmentStart, segmentEnd, alignment) {
  71. var draggerGfx = svgCreate('g');
  72. svgAppend(parentGfx, draggerGfx);
  73. var width = 18,
  74. height = 6,
  75. padding = 11,
  76. hitWidth = calculateHitWidth(segmentStart, segmentEnd, alignment),
  77. hitHeight = height + padding;
  78. var visual = svgCreate('rect');
  79. svgAttr(visual, {
  80. x: -width / 2,
  81. y: -height / 2,
  82. width: width,
  83. height: height
  84. });
  85. svgClasses(visual).add('djs-visual');
  86. svgAppend(draggerGfx, visual);
  87. var hit = svgCreate('rect');
  88. svgAttr(hit, {
  89. x: -hitWidth / 2,
  90. y: -hitHeight / 2,
  91. width: hitWidth,
  92. height: hitHeight
  93. });
  94. svgClasses(hit).add('djs-hit');
  95. svgAppend(draggerGfx, hit);
  96. rotate(draggerGfx, alignment === 'v' ? 90 : 0, 0, 0);
  97. return draggerGfx;
  98. }
  99. export function addSegmentDragger(parentGfx, segmentStart, segmentEnd) {
  100. var groupGfx = svgCreate('g'),
  101. mid = getMidPoint(segmentStart, segmentEnd),
  102. alignment = pointsAligned(segmentStart, segmentEnd);
  103. svgAppend(parentGfx, groupGfx);
  104. createParallelDragger(groupGfx, segmentStart, segmentEnd, alignment);
  105. svgClasses(groupGfx).add(SEGMENT_DRAGGER_CLS);
  106. svgClasses(groupGfx).add(alignment === 'h' ? 'horizontal' : 'vertical');
  107. translate(groupGfx, mid.x, mid.y);
  108. return groupGfx;
  109. }
  110. /**
  111. * Calculates region for segment move which is 2/3 of the full segment length
  112. * @param {number} segmentLength
  113. *
  114. * @return {number}
  115. */
  116. export function calculateSegmentMoveRegion(segmentLength) {
  117. return Math.abs(Math.round(segmentLength * 2 / 3));
  118. }
  119. /**
  120. * Returns the point with the closest distance that is on the connection path.
  121. *
  122. * @param {Point} position
  123. * @param {djs.Base.Connection} connection
  124. * @returns {Point}
  125. */
  126. export function getClosestPointOnConnection(position, connection) {
  127. var segment = getClosestSegment(position, connection);
  128. return perpendicularFoot(position, segment);
  129. }
  130. // helper //////////
  131. function calculateHitWidth(segmentStart, segmentEnd, alignment) {
  132. var segmentLengthXAxis = segmentEnd.x - segmentStart.x,
  133. segmentLengthYAxis = segmentEnd.y - segmentStart.y;
  134. return alignment === 'h' ?
  135. calculateSegmentMoveRegion(segmentLengthXAxis) :
  136. calculateSegmentMoveRegion(segmentLengthYAxis);
  137. }
  138. function getClosestSegment(position, connection) {
  139. var waypoints = connection.waypoints;
  140. var minDistance = Infinity,
  141. segmentIndex;
  142. for (var i = 0; i < waypoints.length - 1; i++) {
  143. var start = waypoints[i],
  144. end = waypoints[i + 1],
  145. distance = getDistancePointLine(position, [ start, end ]);
  146. if (distance < minDistance) {
  147. minDistance = distance;
  148. segmentIndex = i;
  149. }
  150. }
  151. return [ waypoints[segmentIndex], waypoints[segmentIndex + 1] ];
  152. }