AdaptiveLabelPositioningBehavior.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import inherits from 'inherits';
  2. import {
  3. getOrientation,
  4. getMid,
  5. asTRBL
  6. } from 'diagram-js/lib/layout/LayoutUtil';
  7. import {
  8. substract
  9. } from 'diagram-js/lib/util/Math';
  10. import {
  11. hasExternalLabel
  12. } from '../../../util/LabelUtil';
  13. import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
  14. var ALIGNMENTS = [
  15. 'top',
  16. 'bottom',
  17. 'left',
  18. 'right'
  19. ];
  20. var ELEMENT_LABEL_DISTANCE = 10;
  21. /**
  22. * A component that makes sure that external labels are added
  23. * together with respective elements and properly updated (DI wise)
  24. * during move.
  25. *
  26. * @param {EventBus} eventBus
  27. * @param {Modeling} modeling
  28. */
  29. export default function AdaptiveLabelPositioningBehavior(eventBus, modeling) {
  30. CommandInterceptor.call(this, eventBus);
  31. this.postExecuted([
  32. 'connection.create',
  33. 'connection.layout',
  34. 'connection.updateWaypoints'
  35. ], function(event) {
  36. var context = event.context,
  37. connection = context.connection,
  38. source = connection.source,
  39. target = connection.target,
  40. hints = context.hints || {};
  41. if (hints.createElementsBehavior !== false) {
  42. checkLabelAdjustment(source);
  43. checkLabelAdjustment(target);
  44. }
  45. });
  46. this.postExecuted([
  47. 'label.create'
  48. ], function(event) {
  49. var context = event.context,
  50. shape = context.shape,
  51. hints = context.hints || {};
  52. if (hints.createElementsBehavior !== false) {
  53. checkLabelAdjustment(shape.labelTarget);
  54. }
  55. });
  56. this.postExecuted([
  57. 'elements.create'
  58. ], function(event) {
  59. var context = event.context,
  60. elements = context.elements,
  61. hints = context.hints || {};
  62. if (hints.createElementsBehavior !== false) {
  63. elements.forEach(function(element) {
  64. checkLabelAdjustment(element);
  65. });
  66. }
  67. });
  68. function checkLabelAdjustment(element) {
  69. // skip non-existing labels
  70. if (!hasExternalLabel(element)) {
  71. return;
  72. }
  73. var optimalPosition = getOptimalPosition(element);
  74. // no optimal position found
  75. if (!optimalPosition) {
  76. return;
  77. }
  78. adjustLabelPosition(element, optimalPosition);
  79. }
  80. function adjustLabelPosition(element, orientation) {
  81. var elementMid = getMid(element),
  82. label = element.label,
  83. labelMid = getMid(label);
  84. // ignore labels that are being created
  85. if (!label.parent) {
  86. return;
  87. }
  88. var elementTrbl = asTRBL(element);
  89. var newLabelMid;
  90. switch (orientation) {
  91. case 'top':
  92. newLabelMid = {
  93. x: elementMid.x,
  94. y: elementTrbl.top - ELEMENT_LABEL_DISTANCE - label.height / 2
  95. };
  96. break;
  97. case 'left':
  98. newLabelMid = {
  99. x: elementTrbl.left - ELEMENT_LABEL_DISTANCE - label.width / 2,
  100. y: elementMid.y
  101. };
  102. break;
  103. case 'bottom':
  104. newLabelMid = {
  105. x: elementMid.x,
  106. y: elementTrbl.bottom + ELEMENT_LABEL_DISTANCE + label.height / 2
  107. };
  108. break;
  109. case 'right':
  110. newLabelMid = {
  111. x: elementTrbl.right + ELEMENT_LABEL_DISTANCE + label.width / 2,
  112. y: elementMid.y
  113. };
  114. break;
  115. }
  116. var delta = substract(newLabelMid, labelMid);
  117. modeling.moveShape(label, delta);
  118. }
  119. }
  120. inherits(AdaptiveLabelPositioningBehavior, CommandInterceptor);
  121. AdaptiveLabelPositioningBehavior.$inject = [
  122. 'eventBus',
  123. 'modeling'
  124. ];
  125. // helpers //////////////////////
  126. /**
  127. * Return alignments which are taken by a boundary's host element
  128. *
  129. * @param {Shape} element
  130. *
  131. * @return {Array<string>}
  132. */
  133. function getTakenHostAlignments(element) {
  134. var hostElement = element.host,
  135. elementMid = getMid(element),
  136. hostOrientation = getOrientation(elementMid, hostElement);
  137. var freeAlignments;
  138. // check whether there is a multi-orientation, e.g. 'top-left'
  139. if (hostOrientation.indexOf('-') >= 0) {
  140. freeAlignments = hostOrientation.split('-');
  141. } else {
  142. freeAlignments = [ hostOrientation ];
  143. }
  144. var takenAlignments = ALIGNMENTS.filter(function(alignment) {
  145. return freeAlignments.indexOf(alignment) === -1;
  146. });
  147. return takenAlignments;
  148. }
  149. /**
  150. * Return alignments which are taken by related connections
  151. *
  152. * @param {Shape} element
  153. *
  154. * @return {Array<string>}
  155. */
  156. function getTakenConnectionAlignments(element) {
  157. var elementMid = getMid(element);
  158. var takenAlignments = [].concat(
  159. element.incoming.map(function(c) {
  160. return c.waypoints[c.waypoints.length - 2 ];
  161. }),
  162. element.outgoing.map(function(c) {
  163. return c.waypoints[1];
  164. })
  165. ).map(function(point) {
  166. return getApproximateOrientation(elementMid, point);
  167. });
  168. return takenAlignments;
  169. }
  170. /**
  171. * Return the optimal label position around an element
  172. * or _undefined_, if none was found.
  173. *
  174. * @param {Shape} element
  175. *
  176. * @return {string} positioning identifier
  177. */
  178. function getOptimalPosition(element) {
  179. var labelMid = getMid(element.label);
  180. var elementMid = getMid(element);
  181. var labelOrientation = getApproximateOrientation(elementMid, labelMid);
  182. if (!isAligned(labelOrientation)) {
  183. return;
  184. }
  185. var takenAlignments = getTakenConnectionAlignments(element);
  186. if (element.host) {
  187. var takenHostAlignments = getTakenHostAlignments(element);
  188. takenAlignments = takenAlignments.concat(takenHostAlignments);
  189. }
  190. var freeAlignments = ALIGNMENTS.filter(function(alignment) {
  191. return takenAlignments.indexOf(alignment) === -1;
  192. });
  193. // NOTHING TO DO; label already aligned a.O.K.
  194. if (freeAlignments.indexOf(labelOrientation) !== -1) {
  195. return;
  196. }
  197. return freeAlignments[0];
  198. }
  199. function getApproximateOrientation(p0, p1) {
  200. return getOrientation(p1, p0, 5);
  201. }
  202. function isAligned(orientation) {
  203. return ALIGNMENTS.indexOf(orientation) !== -1;
  204. }