BpmnLayouter.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. import inherits from 'inherits';
  2. import {
  3. assign
  4. } from 'min-dash';
  5. import BaseLayouter from 'diagram-js/lib/layout/BaseLayouter';
  6. import {
  7. repairConnection,
  8. withoutRedundantPoints
  9. } from 'diagram-js/lib/layout/ManhattanLayout';
  10. import {
  11. getMid,
  12. getOrientation
  13. } from 'diagram-js/lib/layout/LayoutUtil';
  14. import {
  15. isExpanded
  16. } from '../../util/DiUtil';
  17. import { is } from '../../util/ModelUtil';
  18. export default function BpmnLayouter() {}
  19. inherits(BpmnLayouter, BaseLayouter);
  20. BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
  21. hints = hints || {};
  22. var source = connection.source,
  23. target = connection.target,
  24. waypoints = connection.waypoints,
  25. start = hints.connectionStart,
  26. end = hints.connectionEnd;
  27. var manhattanOptions,
  28. updatedWaypoints;
  29. if (!start) {
  30. start = getConnectionDocking(waypoints && waypoints[0], source);
  31. }
  32. if (!end) {
  33. end = getConnectionDocking(waypoints && waypoints[waypoints.length - 1], target);
  34. }
  35. // TODO(nikku): support vertical modeling
  36. // and invert preferredLayouts accordingly
  37. if (is(connection, 'bpmn:Association') ||
  38. is(connection, 'bpmn:DataAssociation')) {
  39. if (waypoints && !isCompensationAssociation(connection)) {
  40. return [].concat([ start ], waypoints.slice(1, -1), [ end ]);
  41. }
  42. }
  43. // manhattan layout sequence / message flows
  44. if (is(connection, 'bpmn:MessageFlow')) {
  45. manhattanOptions = getMessageFlowManhattanOptions(source, target);
  46. } else
  47. // layout all connection between flow elements h:h,
  48. //
  49. // except for
  50. //
  51. // (1) outgoing of BoundaryEvents -> layout based on attach orientation and target orientation
  52. // (2) incoming / outgoing of Gateway -> v:h (outgoing), h:v (incoming)
  53. // (3) loops from / to the same element
  54. //
  55. if (is(connection, 'bpmn:SequenceFlow') ||
  56. isCompensationAssociation(connection)) {
  57. if (source === target) {
  58. manhattanOptions = {
  59. preferredLayouts: [ 'b:l' ]
  60. };
  61. } else
  62. if (is(source, 'bpmn:BoundaryEvent')) {
  63. manhattanOptions = {
  64. preferredLayouts: getBoundaryEventPreferredLayouts(source, target)
  65. };
  66. } else
  67. if (is(source, 'bpmn:Gateway')) {
  68. manhattanOptions = {
  69. preferredLayouts: [ 'v:h' ]
  70. };
  71. } else
  72. if (is(target, 'bpmn:Gateway')) {
  73. manhattanOptions = {
  74. preferredLayouts: [ 'h:v' ]
  75. };
  76. }
  77. else {
  78. manhattanOptions = {
  79. preferredLayouts: [ 'h:h' ]
  80. };
  81. }
  82. }
  83. if (manhattanOptions) {
  84. manhattanOptions = assign(manhattanOptions, hints);
  85. updatedWaypoints =
  86. withoutRedundantPoints(
  87. repairConnection(
  88. source, target,
  89. start, end,
  90. waypoints,
  91. manhattanOptions
  92. )
  93. );
  94. }
  95. return updatedWaypoints || [ start, end ];
  96. };
  97. function getAttachOrientation(attachedElement) {
  98. var hostElement = attachedElement.host,
  99. padding = -10;
  100. return getOrientation(getMid(attachedElement), hostElement, padding);
  101. }
  102. function getMessageFlowManhattanOptions(source, target) {
  103. return {
  104. preferredLayouts: [ 'straight', 'v:v' ],
  105. preserveDocking: getMessageFlowPreserveDocking(source, target)
  106. };
  107. }
  108. function getMessageFlowPreserveDocking(source, target) {
  109. // (1) docking element connected to participant has precedence
  110. if (is(target, 'bpmn:Participant')) {
  111. return 'source';
  112. }
  113. if (is(source, 'bpmn:Participant')) {
  114. return 'target';
  115. }
  116. // (2) docking element connected to expanded sub-process has precedence
  117. if (isExpandedSubProcess(target)) {
  118. return 'source';
  119. }
  120. if (isExpandedSubProcess(source)) {
  121. return 'target';
  122. }
  123. // (3) docking event has precedence
  124. if (is(target, 'bpmn:Event')) {
  125. return 'target';
  126. }
  127. if (is(source, 'bpmn:Event')) {
  128. return 'source';
  129. }
  130. return null;
  131. }
  132. function getConnectionDocking(point, shape) {
  133. return point ? (point.original || point) : getMid(shape);
  134. }
  135. function isCompensationAssociation(connection) {
  136. var source = connection.source,
  137. target = connection.target;
  138. return is(target, 'bpmn:Activity') &&
  139. is(source, 'bpmn:BoundaryEvent') &&
  140. target.businessObject.isForCompensation;
  141. }
  142. function isExpandedSubProcess(element) {
  143. return is(element, 'bpmn:SubProcess') && isExpanded(element);
  144. }
  145. function isSame(a, b) {
  146. return a === b;
  147. }
  148. function isAnyOrientation(orientation, orientations) {
  149. return orientations.indexOf(orientation) !== -1;
  150. }
  151. var oppositeOrientationMapping = {
  152. 'top': 'bottom',
  153. 'top-right': 'bottom-left',
  154. 'top-left': 'bottom-right',
  155. 'right': 'left',
  156. 'bottom': 'top',
  157. 'bottom-right': 'top-left',
  158. 'bottom-left': 'top-right',
  159. 'left': 'right'
  160. };
  161. var orientationDirectionMapping = {
  162. top: 't',
  163. right: 'r',
  164. bottom: 'b',
  165. left: 'l'
  166. };
  167. function getHorizontalOrientation(orientation) {
  168. var matches = /right|left/.exec(orientation);
  169. return matches && matches[0];
  170. }
  171. function getVerticalOrientation(orientation) {
  172. var matches = /top|bottom/.exec(orientation);
  173. return matches && matches[0];
  174. }
  175. function isOppositeOrientation(a, b) {
  176. return oppositeOrientationMapping[a] === b;
  177. }
  178. function isOppositeHorizontalOrientation(a, b) {
  179. var horizontalOrientation = getHorizontalOrientation(a);
  180. var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation];
  181. return b.indexOf(oppositeHorizontalOrientation) !== -1;
  182. }
  183. function isOppositeVerticalOrientation(a, b) {
  184. var verticalOrientation = getVerticalOrientation(a);
  185. var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation];
  186. return b.indexOf(oppositeVerticalOrientation) !== -1;
  187. }
  188. function isHorizontalOrientation(orientation) {
  189. return orientation === 'right' || orientation === 'left';
  190. }
  191. function getBoundaryEventPreferredLayouts(source, target) {
  192. var sourceMid = getMid(source),
  193. targetMid = getMid(target),
  194. attachOrientation = getAttachOrientation(source),
  195. sourceLayout,
  196. targetLayout;
  197. var isLoop = isSame(source.host, target);
  198. var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]);
  199. var targetOrientation = getOrientation(targetMid, sourceMid, {
  200. x: source.width / 2 + target.width / 2,
  201. y: source.height / 2 + target.height / 2
  202. });
  203. // source layout
  204. sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isLoop);
  205. // target layout
  206. targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isLoop);
  207. return [ sourceLayout + ':' + targetLayout ];
  208. }
  209. function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isLoop) {
  210. // attached to either top, right, bottom or left side
  211. if (attachedToSide) {
  212. return orientationDirectionMapping[attachOrientation];
  213. }
  214. // attached to either top-right, top-left, bottom-right or bottom-left corner
  215. // loop, same vertical or opposite horizontal orientation
  216. if (isLoop ||
  217. isSame(
  218. getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)
  219. ) ||
  220. isOppositeOrientation(
  221. getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation)
  222. )) {
  223. return orientationDirectionMapping[getVerticalOrientation(attachOrientation)];
  224. }
  225. // fallback
  226. return orientationDirectionMapping[getHorizontalOrientation(attachOrientation)];
  227. }
  228. function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isLoop) {
  229. // attached to either top, right, bottom or left side
  230. if (attachedToSide) {
  231. if (isHorizontalOrientation(attachOrientation)) {
  232. // orientation is 'right' or 'left'
  233. // loop or opposite horizontal orientation or same orientation
  234. if (
  235. isLoop ||
  236. isOppositeHorizontalOrientation(attachOrientation, targetOrientation) ||
  237. isSame(attachOrientation, targetOrientation)
  238. ) {
  239. return 'h';
  240. }
  241. // fallback
  242. return 'v';
  243. } else {
  244. // orientation is 'top' or 'bottom'
  245. // loop or opposite vertical orientation or same orientation
  246. if (
  247. isLoop ||
  248. isOppositeVerticalOrientation(attachOrientation, targetOrientation) ||
  249. isSame(attachOrientation, targetOrientation)
  250. ) {
  251. return 'v';
  252. }
  253. // fallback
  254. return 'h';
  255. }
  256. }
  257. // attached to either top-right, top-left, bottom-right or bottom-left corner
  258. // orientation is 'right', 'left'
  259. // or same vertical orientation but also 'right' or 'left'
  260. if (isHorizontalOrientation(targetOrientation) ||
  261. (isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) &&
  262. getHorizontalOrientation(targetOrientation))) {
  263. return 'h';
  264. } else {
  265. return 'v';
  266. }
  267. }