LayoutUtil.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import {
  2. getDistancePointPoint,
  3. rotateVector,
  4. getAngle
  5. } from './GeometricUtil';
  6. import {
  7. getAttachment
  8. } from './LineAttachmentUtil';
  9. import {
  10. roundPoint
  11. } from 'diagram-js/lib/layout/LayoutUtil';
  12. /**
  13. * @typedef {import('diagram-js/lib/util/Types').Point} Point
  14. *
  15. * @typedef {import('./LineAttachmentUtil').Attachment} Attachment
  16. *
  17. * @typedef { {
  18. * point: Point;
  19. * delta: Point;
  20. * } } AnchorPointAdjustment
  21. *
  22. * @typedef { {
  23. * segmentMove?: {
  24. * segmentStartIndex: number;
  25. * newSegmentStartIndex: number;
  26. * };
  27. * bendpointMove?: {
  28. * insert: boolean;
  29. * bendpointIndex: number;
  30. * };
  31. * connectionStart: boolean;
  32. * connectionEnd: boolean;
  33. * } } FindNewLineStartIndexHints
  34. */
  35. /**
  36. * @param {Point[]} oldWaypoints
  37. * @param {Point[]} newWaypoints
  38. * @param {Attachment} attachment
  39. * @param {FindNewLineStartIndexHints} hints
  40. *
  41. * @return {number}
  42. */
  43. export function findNewLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) {
  44. var index = attachment.segmentIndex;
  45. var offset = newWaypoints.length - oldWaypoints.length;
  46. // segmentMove happened
  47. if (hints.segmentMove) {
  48. var oldSegmentStartIndex = hints.segmentMove.segmentStartIndex,
  49. newSegmentStartIndex = hints.segmentMove.newSegmentStartIndex;
  50. // if point was on moved segment return new segment index
  51. if (index === oldSegmentStartIndex) {
  52. return newSegmentStartIndex;
  53. }
  54. // point is after new segment index
  55. if (index >= newSegmentStartIndex) {
  56. return (index + offset < newSegmentStartIndex) ? newSegmentStartIndex : index + offset;
  57. }
  58. // if point is before new segment index
  59. return index;
  60. }
  61. // bendpointMove happened
  62. if (hints.bendpointMove) {
  63. var insert = hints.bendpointMove.insert,
  64. bendpointIndex = hints.bendpointMove.bendpointIndex,
  65. newIndex;
  66. // waypoints length didnt change
  67. if (offset === 0) {
  68. return index;
  69. }
  70. // point behind new/removed bendpoint
  71. if (index >= bendpointIndex) {
  72. newIndex = insert ? index + 1 : index - 1;
  73. }
  74. // point before new/removed bendpoint
  75. if (index < bendpointIndex) {
  76. newIndex = index;
  77. // decide point should take right or left segment
  78. if (insert && attachment.type !== 'bendpoint' && bendpointIndex - 1 === index) {
  79. var rel = relativePositionMidWaypoint(newWaypoints, bendpointIndex);
  80. if (rel < attachment.relativeLocation) {
  81. newIndex++;
  82. }
  83. }
  84. }
  85. return newIndex;
  86. }
  87. // start/end changed
  88. if (offset === 0) {
  89. return index;
  90. }
  91. if (hints.connectionStart && index === 0) {
  92. return 0;
  93. }
  94. if (hints.connectionEnd && index === oldWaypoints.length - 2) {
  95. return newWaypoints.length - 2;
  96. }
  97. // if nothing fits, take the middle segment
  98. return Math.floor((newWaypoints.length - 2) / 2);
  99. }
  100. /**
  101. * Calculate the required adjustment (move delta) for the given point
  102. * after the connection waypoints got updated.
  103. *
  104. * @param {Point} position
  105. * @param {Point[]} newWaypoints
  106. * @param {Point[]} oldWaypoints
  107. * @param {FindNewLineStartIndexHints} hints
  108. *
  109. * @return {AnchorPointAdjustment} result
  110. */
  111. export function getAnchorPointAdjustment(position, newWaypoints, oldWaypoints, hints) {
  112. var dx = 0,
  113. dy = 0;
  114. var oldPosition = {
  115. point: position,
  116. delta: { x: 0, y: 0 }
  117. };
  118. // get closest attachment
  119. var attachment = getAttachment(position, oldWaypoints),
  120. oldLabelLineIndex = attachment.segmentIndex,
  121. newLabelLineIndex = findNewLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);
  122. // should never happen
  123. // TODO(@janstuemmel): throw an error here when connectionSegmentMove is refactored
  124. if (newLabelLineIndex < 0 ||
  125. newLabelLineIndex > newWaypoints.length - 2 ||
  126. newLabelLineIndex === null) {
  127. return oldPosition;
  128. }
  129. var oldLabelLine = getLine(oldWaypoints, oldLabelLineIndex),
  130. newLabelLine = getLine(newWaypoints, newLabelLineIndex),
  131. oldFoot = attachment.position;
  132. var relativeFootPosition = getRelativeFootPosition(oldLabelLine, oldFoot),
  133. angleDelta = getAngleDelta(oldLabelLine, newLabelLine);
  134. // special rule if label on bendpoint
  135. if (attachment.type === 'bendpoint') {
  136. var offset = newWaypoints.length - oldWaypoints.length,
  137. oldBendpointIndex = attachment.bendpointIndex,
  138. oldBendpoint = oldWaypoints[oldBendpointIndex];
  139. // bendpoint position hasn't changed, return same position
  140. if (newWaypoints.indexOf(oldBendpoint) !== -1) {
  141. return oldPosition;
  142. }
  143. // new bendpoint and old bendpoint have same index, then just return the offset
  144. if (offset === 0) {
  145. var newBendpoint = newWaypoints[oldBendpointIndex];
  146. dx = newBendpoint.x - attachment.position.x,
  147. dy = newBendpoint.y - attachment.position.y;
  148. return {
  149. delta: {
  150. x: dx,
  151. y: dy
  152. },
  153. point: {
  154. x: position.x + dx,
  155. y: position.y + dy
  156. }
  157. };
  158. }
  159. // if bendpoints get removed
  160. if (offset < 0 && oldBendpointIndex !== 0 && oldBendpointIndex < oldWaypoints.length - 1) {
  161. relativeFootPosition = relativePositionMidWaypoint(oldWaypoints, oldBendpointIndex);
  162. }
  163. }
  164. var newFoot = {
  165. x: (newLabelLine[1].x - newLabelLine[0].x) * relativeFootPosition + newLabelLine[0].x,
  166. y: (newLabelLine[1].y - newLabelLine[0].y) * relativeFootPosition + newLabelLine[0].y
  167. };
  168. // the rotated vector to label
  169. var newLabelVector = rotateVector({
  170. x: position.x - oldFoot.x,
  171. y: position.y - oldFoot.y
  172. }, angleDelta);
  173. // the new relative position
  174. dx = newFoot.x + newLabelVector.x - position.x;
  175. dy = newFoot.y + newLabelVector.y - position.y;
  176. return {
  177. point: roundPoint(newFoot),
  178. delta: roundPoint({
  179. x: dx,
  180. y: dy
  181. })
  182. };
  183. }
  184. // HELPERS //////////////////////
  185. function relativePositionMidWaypoint(waypoints, idx) {
  186. var distanceSegment1 = getDistancePointPoint(waypoints[idx - 1], waypoints[idx]),
  187. distanceSegment2 = getDistancePointPoint(waypoints[idx], waypoints[idx + 1]);
  188. var relativePosition = distanceSegment1 / (distanceSegment1 + distanceSegment2);
  189. return relativePosition;
  190. }
  191. function getAngleDelta(l1, l2) {
  192. var a1 = getAngle(l1),
  193. a2 = getAngle(l2);
  194. return a2 - a1;
  195. }
  196. function getLine(waypoints, idx) {
  197. return [ waypoints[idx], waypoints[idx + 1] ];
  198. }
  199. function getRelativeFootPosition(line, foot) {
  200. var length = getDistancePointPoint(line[0], line[1]),
  201. lengthToFoot = getDistancePointPoint(line[0], foot);
  202. return length === 0 ? 0 : lengthToFoot / length;
  203. }