bpmn-auto-layout 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/dist/index.cjs +798 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.esm.js +796 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +21 -8
- package/index.js +0 -1
- package/lib/AutoLayout.js +0 -514
- package/lib/DiFactory.js +0 -152
- package/lib/DiUtil.js +0 -263
- package/lib/Tree.js +0 -335
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var BPMNModdle = require('bpmn-moddle');
|
|
4
|
+
var minDash = require('min-dash');
|
|
5
|
+
|
|
6
|
+
function isConnection(element) {
|
|
7
|
+
return !!element.sourceRef;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isBoundaryEvent(element) {
|
|
11
|
+
return !!element.attachedToRef;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class Grid {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.grid = [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
add(element, position) {
|
|
20
|
+
if (!position) {
|
|
21
|
+
this._addStart(element);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const [ row, col ] = position;
|
|
26
|
+
if (!row && !col) {
|
|
27
|
+
this._addStart(element);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!this.grid[row]) {
|
|
31
|
+
this.grid[row] = [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (this.grid[row][col]) {
|
|
35
|
+
throw new Error('Grid is occupied please ensure the place you insert at is not occupied');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.grid[row][col] = element;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
createRow(afterIndex) {
|
|
42
|
+
if (!afterIndex) {
|
|
43
|
+
this.grid.push([]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.grid.splice(afterIndex + 1, 0, []);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_addStart(element) {
|
|
50
|
+
this.grid.push([ element ]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
addAfter(element, newElement) {
|
|
54
|
+
if (!element) {
|
|
55
|
+
this._addStart(newElement);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const [ row, col ] = this.find(element);
|
|
59
|
+
this.grid[row].splice(col + 1, 0, newElement);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
addBelow(element, newElement) {
|
|
63
|
+
if (!element) {
|
|
64
|
+
this._addStart(newElement);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const [ row, col ] = this.find(element);
|
|
68
|
+
|
|
69
|
+
// We are at the bottom of the current grid - add empty row below
|
|
70
|
+
if (!this.grid[row + 1]) {
|
|
71
|
+
this.grid[row + 1] = [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The element below is already occupied - insert new row
|
|
75
|
+
if (this.grid[row + 1][col]) {
|
|
76
|
+
this.grid.splice(row + 1, 0, []);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (this.grid[row + 1][col]) {
|
|
80
|
+
throw new Error('Grid is occupied and we could not find a place - this should not happen');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.grid[row + 1][col] = newElement;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
find(element) {
|
|
87
|
+
let row, col;
|
|
88
|
+
row = this.grid.findIndex((row) => {
|
|
89
|
+
col = row.findIndex((el) => {
|
|
90
|
+
return el === element;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return col !== -1;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return [ row, col ];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get(row, col) {
|
|
100
|
+
return (this.grid[row] || [])[col];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getElementsInRange({ row: startRow, col: startCol }, { row: endRow, col: endCol }) {
|
|
104
|
+
const elements = [];
|
|
105
|
+
|
|
106
|
+
if (startRow > endRow) {
|
|
107
|
+
[ startRow, endRow ] = [ endRow, startRow ];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (startCol > endCol) {
|
|
111
|
+
[ startCol, endCol ] = [ endCol, startCol ];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
115
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
116
|
+
const element = this.get(row, col);
|
|
117
|
+
|
|
118
|
+
if (element) {
|
|
119
|
+
elements.push(element);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return elements;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
elementsByPosition() {
|
|
128
|
+
const elements = [];
|
|
129
|
+
|
|
130
|
+
this.grid.forEach((row, rowIndex) => {
|
|
131
|
+
row.forEach((element, colIndex) => {
|
|
132
|
+
if (!element) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
elements.push({
|
|
136
|
+
element,
|
|
137
|
+
row: rowIndex,
|
|
138
|
+
col: colIndex
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return elements;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class DiFactory {
|
|
148
|
+
constructor(moddle) {
|
|
149
|
+
this.moddle = moddle;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
create(type, attrs) {
|
|
153
|
+
return this.moddle.create(type, attrs || {});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
createDiBounds(bounds) {
|
|
157
|
+
return this.create('dc:Bounds', bounds);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
createDiLabel() {
|
|
161
|
+
return this.create('bpmndi:BPMNLabel', {
|
|
162
|
+
bounds: this.createDiBounds()
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
createDiShape(semantic, bounds, attrs) {
|
|
167
|
+
return this.create('bpmndi:BPMNShape', minDash.assign({
|
|
168
|
+
bpmnElement: semantic,
|
|
169
|
+
bounds: this.createDiBounds(bounds)
|
|
170
|
+
}, attrs));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
createDiWaypoints(waypoints) {
|
|
174
|
+
var self = this;
|
|
175
|
+
|
|
176
|
+
return minDash.map(waypoints, function(pos) {
|
|
177
|
+
return self.createDiWaypoint(pos);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
createDiWaypoint(point) {
|
|
182
|
+
return this.create('dc:Point', minDash.pick(point, [ 'x', 'y' ]));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
createDiEdge(semantic, waypoints, attrs) {
|
|
186
|
+
return this.create('bpmndi:BPMNEdge', minDash.assign({
|
|
187
|
+
bpmnElement: semantic,
|
|
188
|
+
waypoint: this.createDiWaypoints(waypoints)
|
|
189
|
+
}, attrs));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
createDiPlane(attrs) {
|
|
193
|
+
return this.create('bpmndi:BPMNPlane', attrs);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
createDiDiagram(attrs) {
|
|
197
|
+
return this.create('bpmndi:BPMNDiagram', attrs);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getDefaultSize(element) {
|
|
202
|
+
if (is(element, 'bpmn:SubProcess')) {
|
|
203
|
+
return { width: 100, height: 80 };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (is(element, 'bpmn:Task')) {
|
|
207
|
+
return { width: 100, height: 80 };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (is(element, 'bpmn:Gateway')) {
|
|
211
|
+
return { width: 50, height: 50 };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (is(element, 'bpmn:Event')) {
|
|
215
|
+
return { width: 36, height: 36 };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (is(element, 'bpmn:Participant')) {
|
|
219
|
+
return { width: 400, height: 100 };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (is(element, 'bpmn:Lane')) {
|
|
223
|
+
return { width: 400, height: 100 };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (is(element, 'bpmn:DataObjectReference')) {
|
|
227
|
+
return { width: 36, height: 50 };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (is(element, 'bpmn:DataStoreReference')) {
|
|
231
|
+
return { width: 50, height: 50 };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (is(element, 'bpmn:TextAnnotation')) {
|
|
235
|
+
return { width: 100, height: 30 };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { width: 100, height: 80 };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function is(element, type) {
|
|
242
|
+
return element.$instanceOf(type);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const DEFAULT_CELL_WIDTH = 150;
|
|
246
|
+
const DEFAULT_CELL_HEIGHT = 140;
|
|
247
|
+
|
|
248
|
+
function getMid(bounds) {
|
|
249
|
+
return {
|
|
250
|
+
x: bounds.x + bounds.width / 2,
|
|
251
|
+
y: bounds.y + bounds.height / 2
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getDockingPoint(point, rectangle, dockingDirection = 'r', targetOrientation = 'top-left') {
|
|
256
|
+
|
|
257
|
+
// ensure we end up with a specific docking direction
|
|
258
|
+
// based on the targetOrientation, if <h|v> is being passed
|
|
259
|
+
|
|
260
|
+
if (dockingDirection === 'h') {
|
|
261
|
+
dockingDirection = /left/.test(targetOrientation) ? 'l' : 'r';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (dockingDirection === 'v') {
|
|
265
|
+
dockingDirection = /top/.test(targetOrientation) ? 't' : 'b';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (dockingDirection === 't') {
|
|
269
|
+
return { original: point, x: point.x, y: rectangle.y };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (dockingDirection === 'r') {
|
|
273
|
+
return { original: point, x: rectangle.x + rectangle.width, y: point.y };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (dockingDirection === 'b') {
|
|
277
|
+
return { original: point, x: point.x, y: rectangle.y + rectangle.height };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (dockingDirection === 'l') {
|
|
281
|
+
return { original: point, x: rectangle.x, y: point.y };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
throw new Error('unexpected dockingDirection: <' + dockingDirection + '>');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Modified Manhattan layout: Uses space between grid coloumns to route connections
|
|
289
|
+
* if direct connection is not possible.
|
|
290
|
+
* @param {*} source
|
|
291
|
+
* @param {*} target
|
|
292
|
+
* @returns waypoints
|
|
293
|
+
*/
|
|
294
|
+
function connectElements(source, target, layoutGrid) {
|
|
295
|
+
const sourceDi = source.di;
|
|
296
|
+
const targetDi = target.di;
|
|
297
|
+
|
|
298
|
+
const sourceBounds = sourceDi.get('bounds');
|
|
299
|
+
const targetBounds = targetDi.get('bounds');
|
|
300
|
+
|
|
301
|
+
const sourceMid = getMid(sourceBounds);
|
|
302
|
+
const targetMid = getMid(targetBounds);
|
|
303
|
+
|
|
304
|
+
const dX = target.gridPosition.col - source.gridPosition.col;
|
|
305
|
+
const dY = target.gridPosition.row - source.gridPosition.row;
|
|
306
|
+
|
|
307
|
+
const dockingSource = `${(dY > 0 ? 'bottom' : 'top')}-${dX > 0 ? 'right' : 'left'}`;
|
|
308
|
+
const dockingTarget = `${(dY > 0 ? 'top' : 'bottom')}-${dX > 0 ? 'left' : 'right'}`;
|
|
309
|
+
|
|
310
|
+
// Source === Target ==> Build loop
|
|
311
|
+
if (dX === 0 && dY === 0) {
|
|
312
|
+
const { x, y } = coordinatesToPosition(source.gridPosition.row, source.gridPosition.col);
|
|
313
|
+
return [
|
|
314
|
+
getDockingPoint(sourceMid, sourceBounds, 'r', dockingSource),
|
|
315
|
+
{ x: x + DEFAULT_CELL_WIDTH, y: sourceMid.y },
|
|
316
|
+
{ x: x + DEFAULT_CELL_WIDTH, y: y },
|
|
317
|
+
{ x: targetMid.x, y: y },
|
|
318
|
+
getDockingPoint(targetMid, targetBounds, 't', dockingTarget)
|
|
319
|
+
];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// connect horizontally
|
|
323
|
+
if (dY === 0) {
|
|
324
|
+
if (isDirectPathBlocked(source, target, layoutGrid)) {
|
|
325
|
+
|
|
326
|
+
// Route on top
|
|
327
|
+
return [
|
|
328
|
+
getDockingPoint(sourceMid, sourceBounds, 't'),
|
|
329
|
+
{ x: sourceMid.x, y: sourceMid.y - DEFAULT_CELL_HEIGHT / 2 },
|
|
330
|
+
{ x: targetMid.x, y: sourceMid.y - DEFAULT_CELL_HEIGHT / 2 },
|
|
331
|
+
getDockingPoint(targetMid, targetBounds, 't')
|
|
332
|
+
];
|
|
333
|
+
} else {
|
|
334
|
+
|
|
335
|
+
// if space is clear, connect directly
|
|
336
|
+
return [
|
|
337
|
+
getDockingPoint(sourceMid, sourceBounds, 'h', dockingSource),
|
|
338
|
+
getDockingPoint(targetMid, targetBounds, 'h', dockingTarget)
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// connect vertically
|
|
344
|
+
if (dX === 0) {
|
|
345
|
+
if (isDirectPathBlocked(source, target, layoutGrid)) {
|
|
346
|
+
|
|
347
|
+
// Route parallel
|
|
348
|
+
const yOffset = -Math.sign(dY) * DEFAULT_CELL_HEIGHT / 2;
|
|
349
|
+
return [
|
|
350
|
+
getDockingPoint(sourceMid, sourceBounds, 'r'),
|
|
351
|
+
{ x: sourceMid.x + DEFAULT_CELL_WIDTH / 2, y: sourceMid.y }, // out right
|
|
352
|
+
{ x: targetMid.x + DEFAULT_CELL_WIDTH / 2, y: targetMid.y + yOffset },
|
|
353
|
+
{ x: targetMid.x, y: targetMid.y + yOffset },
|
|
354
|
+
getDockingPoint(targetMid, targetBounds, Math.sign(yOffset) > 0 ? 'b' : 't')
|
|
355
|
+
];
|
|
356
|
+
} else {
|
|
357
|
+
|
|
358
|
+
// if space is clear, connect directly
|
|
359
|
+
return [ getDockingPoint(sourceMid, sourceBounds, 'v', dockingSource),
|
|
360
|
+
getDockingPoint(targetMid, targetBounds, 'v', dockingTarget)
|
|
361
|
+
];
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const directManhattan = directManhattanConnect(source, target, layoutGrid);
|
|
366
|
+
|
|
367
|
+
if (directManhattan) {
|
|
368
|
+
const startPoint = getDockingPoint(sourceMid, sourceBounds, directManhattan[0], dockingSource);
|
|
369
|
+
const endPoint = getDockingPoint(targetMid, targetBounds, directManhattan[1], dockingTarget);
|
|
370
|
+
|
|
371
|
+
const midPoint = directManhattan[0] === 'h' ? { x: endPoint.x, y: startPoint.y } : { x: startPoint.x, y: endPoint.y };
|
|
372
|
+
|
|
373
|
+
return [
|
|
374
|
+
startPoint,
|
|
375
|
+
midPoint,
|
|
376
|
+
endPoint
|
|
377
|
+
];
|
|
378
|
+
}
|
|
379
|
+
const yOffset = -Math.sign(dY) * DEFAULT_CELL_HEIGHT / 2;
|
|
380
|
+
|
|
381
|
+
return [
|
|
382
|
+
getDockingPoint(sourceMid, sourceBounds, 'r', dockingSource),
|
|
383
|
+
{ x: sourceMid.x + DEFAULT_CELL_WIDTH / 2, y: sourceMid.y }, // out right
|
|
384
|
+
{ x: sourceMid.x + DEFAULT_CELL_WIDTH / 2, y: targetMid.y + yOffset }, // to target row
|
|
385
|
+
{ x: targetMid.x - DEFAULT_CELL_WIDTH / 2, y: targetMid.y + yOffset }, // to target column
|
|
386
|
+
{ x: targetMid.x - DEFAULT_CELL_WIDTH / 2, y: targetMid.y }, // to mid
|
|
387
|
+
getDockingPoint(targetMid, targetBounds, 'l', dockingTarget)
|
|
388
|
+
];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// helpers /////
|
|
392
|
+
function coordinatesToPosition(row, col) {
|
|
393
|
+
return {
|
|
394
|
+
width: DEFAULT_CELL_WIDTH,
|
|
395
|
+
height: DEFAULT_CELL_HEIGHT,
|
|
396
|
+
x: col * DEFAULT_CELL_WIDTH,
|
|
397
|
+
y: row * DEFAULT_CELL_HEIGHT
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function getBounds(element, row, col, attachedTo) {
|
|
402
|
+
const { width, height } = getDefaultSize(element);
|
|
403
|
+
|
|
404
|
+
// Center in cell
|
|
405
|
+
if (!attachedTo) {
|
|
406
|
+
return {
|
|
407
|
+
width, height,
|
|
408
|
+
x: (col * DEFAULT_CELL_WIDTH) + (DEFAULT_CELL_WIDTH - width) / 2,
|
|
409
|
+
y: row * DEFAULT_CELL_HEIGHT + (DEFAULT_CELL_HEIGHT - height) / 2
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const hostBounds = getBounds(attachedTo, row, col);
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
width, height,
|
|
417
|
+
x: Math.round(hostBounds.x + hostBounds.width / 2 - width / 2),
|
|
418
|
+
y: Math.round(hostBounds.y + hostBounds.height - height / 2)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function isDirectPathBlocked(source, target, layoutGrid) {
|
|
423
|
+
const { row: sourceRow, col: sourceCol } = source.gridPosition;
|
|
424
|
+
const { row: targetRow, col: targetCol } = target.gridPosition;
|
|
425
|
+
|
|
426
|
+
const dX = targetCol - sourceCol;
|
|
427
|
+
const dY = targetRow - sourceRow;
|
|
428
|
+
|
|
429
|
+
let totalElements = 0;
|
|
430
|
+
|
|
431
|
+
if (dX) {
|
|
432
|
+
totalElements += layoutGrid.getElementsInRange({ row: sourceRow, col: sourceCol }, { row: sourceRow, col: targetCol }).length;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (dY) {
|
|
436
|
+
totalElements += layoutGrid.getElementsInRange({ row: sourceRow, col: targetCol }, { row: targetRow, col: targetCol }).length;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return totalElements > 2;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function directManhattanConnect(source, target, layoutGrid) {
|
|
443
|
+
const { row: sourceRow, col: sourceCol } = source.gridPosition;
|
|
444
|
+
const { row: targetRow, col: targetCol } = target.gridPosition;
|
|
445
|
+
|
|
446
|
+
const dX = targetCol - sourceCol;
|
|
447
|
+
const dY = targetRow - sourceRow;
|
|
448
|
+
|
|
449
|
+
// Only directly connect left-to-right flow
|
|
450
|
+
if (!(dX > 0 && dY !== 0)) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// If below, go down then horizontal
|
|
455
|
+
if (dY > 0) {
|
|
456
|
+
let totalElements = 0;
|
|
457
|
+
const bendPoint = { row: targetRow, col: sourceCol };
|
|
458
|
+
totalElements += layoutGrid.getElementsInRange({ row: sourceRow, col: sourceCol }, bendPoint).length;
|
|
459
|
+
totalElements += layoutGrid.getElementsInRange(bendPoint, { row: targetRow, col: targetCol }).length;
|
|
460
|
+
|
|
461
|
+
return totalElements > 2 ? false : [ 'v', 'h' ];
|
|
462
|
+
} else {
|
|
463
|
+
|
|
464
|
+
// If above, go horizontal than vertical
|
|
465
|
+
let totalElements = 0;
|
|
466
|
+
const bendPoint = { row: sourceRow, col: targetCol };
|
|
467
|
+
|
|
468
|
+
totalElements += layoutGrid.getElementsInRange({ row: sourceRow, col: sourceCol }, bendPoint).length;
|
|
469
|
+
totalElements += layoutGrid.getElementsInRange(bendPoint, { row: targetRow, col: targetCol }).length;
|
|
470
|
+
|
|
471
|
+
return totalElements > 2 ? false : [ 'h', 'v' ];
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
var attacherHandler = {
|
|
476
|
+
'addToGrid': ({ element, grid, visited }) => {
|
|
477
|
+
const nextElements = [];
|
|
478
|
+
|
|
479
|
+
const attachedOutgoing = (element.attachers || [])
|
|
480
|
+
.map(att => att.outgoing.reverse())
|
|
481
|
+
.flat()
|
|
482
|
+
.map(out => out.targetRef);
|
|
483
|
+
|
|
484
|
+
// handle boundary events
|
|
485
|
+
attachedOutgoing.forEach((nextElement, index, arr) => {
|
|
486
|
+
if (visited.has(nextElement)) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Add below and to the right of the element
|
|
491
|
+
insertIntoGrid(nextElement, element, grid);
|
|
492
|
+
nextElements.push(nextElement);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
return nextElements;
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
'createElementDi': ({ element, row, col, diFactory }) => {
|
|
499
|
+
const hostBounds = getBounds(element, row, col);
|
|
500
|
+
|
|
501
|
+
const DIs = [];
|
|
502
|
+
(element.attachers || []).forEach((att, i, arr) => {
|
|
503
|
+
att.gridPosition = { row, col };
|
|
504
|
+
const bounds = getBounds(att, row, col, element);
|
|
505
|
+
|
|
506
|
+
// distribute along lower edge
|
|
507
|
+
bounds.x = hostBounds.x + (i + 1) * (hostBounds.width / (arr.length + 1)) - bounds.width / 2;
|
|
508
|
+
|
|
509
|
+
const attacherDi = diFactory.createDiShape(att, bounds, {
|
|
510
|
+
id: att.id + '_di'
|
|
511
|
+
});
|
|
512
|
+
att.di = attacherDi;
|
|
513
|
+
att.gridPosition = { row, col };
|
|
514
|
+
|
|
515
|
+
DIs.push(attacherDi);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
return DIs;
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
'createConnectionDi': ({ element, row, col, layoutGrid, diFactory }) => {
|
|
522
|
+
const attachers = element.attachers || [];
|
|
523
|
+
|
|
524
|
+
return attachers.flatMap(att => {
|
|
525
|
+
const outgoing = att.outgoing || [];
|
|
526
|
+
|
|
527
|
+
return outgoing.map(out => {
|
|
528
|
+
const target = out.targetRef;
|
|
529
|
+
const waypoints = connectElements(att, target, layoutGrid);
|
|
530
|
+
|
|
531
|
+
// Correct waypoints if they don't automatically attach to the bottom
|
|
532
|
+
ensureExitBottom(att, waypoints, [ row, col ]);
|
|
533
|
+
|
|
534
|
+
const connectionDi = diFactory.createDiEdge(out, waypoints, {
|
|
535
|
+
id: out.id + '_di'
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
return connectionDi;
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
function insertIntoGrid(newElement, host, grid) {
|
|
546
|
+
const [ row, col ] = grid.find(host);
|
|
547
|
+
|
|
548
|
+
// Grid is occupied
|
|
549
|
+
if (grid.get(row + 1, col) || grid.get(row + 1, col + 1)) {
|
|
550
|
+
grid.createRow(row);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Host has element directly after, add space
|
|
554
|
+
if (grid.get(row, col + 1)) {
|
|
555
|
+
grid.addAfter(host, null);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
grid.add(newElement, [ row + 1, col + 1 ]);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function ensureExitBottom(source, waypoints, [ row, col ]) {
|
|
562
|
+
|
|
563
|
+
const sourceDi = source.di;
|
|
564
|
+
const sourceBounds = sourceDi.get('bounds');
|
|
565
|
+
const sourceMid = getMid(sourceBounds);
|
|
566
|
+
|
|
567
|
+
const dockingPoint = getDockingPoint(sourceMid, sourceBounds, 'b');
|
|
568
|
+
if (waypoints[0].x === dockingPoint.x && waypoints[0].y === dockingPoint.y) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (waypoints.length === 2) {
|
|
573
|
+
const newStart = [
|
|
574
|
+
dockingPoint,
|
|
575
|
+
{ x: dockingPoint.x, y: (row + 1) * DEFAULT_CELL_HEIGHT },
|
|
576
|
+
{ x: (col + 1) * DEFAULT_CELL_WIDTH, y: (row + 1) * DEFAULT_CELL_HEIGHT },
|
|
577
|
+
{ x: (col + 1) * DEFAULT_CELL_WIDTH, y: (row + 0.5) * DEFAULT_CELL_HEIGHT },
|
|
578
|
+
];
|
|
579
|
+
|
|
580
|
+
waypoints.splice(0, 1, ...newStart);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// add waypoints to exit bottom and connect to existing path
|
|
585
|
+
const newStart = [
|
|
586
|
+
dockingPoint,
|
|
587
|
+
{ x: dockingPoint.x, y: (row + 1) * DEFAULT_CELL_HEIGHT },
|
|
588
|
+
{ x: waypoints[1].x, y: (row + 1) * DEFAULT_CELL_HEIGHT },
|
|
589
|
+
];
|
|
590
|
+
|
|
591
|
+
waypoints.splice(0, 1, ...newStart);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
var elementHandler = {
|
|
596
|
+
'createElementDi': ({ element, row, col, diFactory }) => {
|
|
597
|
+
|
|
598
|
+
const bounds = getBounds(element, row, col);
|
|
599
|
+
|
|
600
|
+
const options = {
|
|
601
|
+
id: element.id + '_di'
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
if (is(element, 'bpmn:ExclusiveGateway')) {
|
|
605
|
+
options.isMarkerVisible = true;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const shapeDi = diFactory.createDiShape(element, bounds, options);
|
|
609
|
+
element.di = shapeDi;
|
|
610
|
+
element.gridPosition = { row, col };
|
|
611
|
+
|
|
612
|
+
return shapeDi;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
var outgoingHandler = {
|
|
617
|
+
'addToGrid': ({ element, grid, visited }) => {
|
|
618
|
+
const nextElements = [];
|
|
619
|
+
|
|
620
|
+
// Handle outgoing paths
|
|
621
|
+
const outgoing = (element.outgoing || [])
|
|
622
|
+
.map(out => out.targetRef)
|
|
623
|
+
.filter(el => el);
|
|
624
|
+
|
|
625
|
+
let previousElement = null;
|
|
626
|
+
outgoing.forEach((nextElement, index, arr) => {
|
|
627
|
+
if (visited.has(nextElement)) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (!previousElement) {
|
|
632
|
+
grid.addAfter(element, nextElement);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
grid.addBelow(arr[index - 1], nextElement);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Is self-looping
|
|
639
|
+
if (nextElement !== element) {
|
|
640
|
+
previousElement = nextElement;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
nextElements.unshift(nextElement);
|
|
644
|
+
visited.add(nextElement);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
return nextElements;
|
|
648
|
+
},
|
|
649
|
+
'createConnectionDi': ({ element, row, col, layoutGrid, diFactory }) => {
|
|
650
|
+
const outgoing = element.outgoing || [];
|
|
651
|
+
|
|
652
|
+
return outgoing.map(out => {
|
|
653
|
+
const target = out.targetRef;
|
|
654
|
+
const waypoints = connectElements(element, target, layoutGrid);
|
|
655
|
+
|
|
656
|
+
const connectionDi = diFactory.createDiEdge(out, waypoints, {
|
|
657
|
+
id: out.id + '_di'
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
return connectionDi;
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const handlers = [ elementHandler, outgoingHandler, attacherHandler ];
|
|
667
|
+
|
|
668
|
+
class Layouter {
|
|
669
|
+
constructor() {
|
|
670
|
+
this.moddle = new BPMNModdle();
|
|
671
|
+
this.diFactory = new DiFactory(this.moddle);
|
|
672
|
+
this._handlers = handlers;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
handle(operation, options) {
|
|
676
|
+
return this._handlers
|
|
677
|
+
.filter(handler => minDash.isFunction(handler[operation]))
|
|
678
|
+
.map(handler => handler[operation](options));
|
|
679
|
+
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async layoutProcess(xml) {
|
|
683
|
+
const { rootElement } = await this.moddle.fromXML(xml);
|
|
684
|
+
|
|
685
|
+
this.diagram = rootElement;
|
|
686
|
+
|
|
687
|
+
const root = this.getProcess();
|
|
688
|
+
|
|
689
|
+
this.cleanDi();
|
|
690
|
+
this.handlePlane(root);
|
|
691
|
+
|
|
692
|
+
return (await this.moddle.toXML(this.diagram, { format: true })).xml;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
handlePlane(planeElement) {
|
|
696
|
+
const layout = this.createGridLayout(planeElement);
|
|
697
|
+
this.generateDi(planeElement, layout);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
cleanDi() {
|
|
701
|
+
this.diagram.diagrams = [];
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
createGridLayout(root) {
|
|
705
|
+
const grid = new Grid();
|
|
706
|
+
|
|
707
|
+
const flowElements = root.flowElements;
|
|
708
|
+
|
|
709
|
+
const startingElements = flowElements.filter(el => {
|
|
710
|
+
return !isConnection(el) && !isBoundaryEvent(el) && (!el.incoming || el.length === 0);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
const boundaryEvents = flowElements.filter(el => isBoundaryEvent(el));
|
|
714
|
+
boundaryEvents.forEach(boundaryEvent => {
|
|
715
|
+
const attachedTask = boundaryEvent.attachedToRef;
|
|
716
|
+
const attachers = attachedTask.attachers || [];
|
|
717
|
+
attachers.push(boundaryEvent);
|
|
718
|
+
attachedTask.attachers = attachers;
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// Depth-first-search
|
|
722
|
+
const stack = [ ...startingElements ];
|
|
723
|
+
const visited = new Set();
|
|
724
|
+
|
|
725
|
+
startingElements.forEach(el => {
|
|
726
|
+
grid.add(el);
|
|
727
|
+
visited.add(el);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
while (stack.length > 0) {
|
|
731
|
+
const currentElement = stack.pop();
|
|
732
|
+
|
|
733
|
+
if (is(currentElement, 'bpmn:SubProcess')) {
|
|
734
|
+
this.handlePlane(currentElement);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const nextElements = this.handle('addToGrid', { element: currentElement, grid, visited });
|
|
738
|
+
|
|
739
|
+
nextElements.flat().forEach(el => {
|
|
740
|
+
stack.push(el);
|
|
741
|
+
visited.add(el);
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return grid;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
generateDi(root, layoutGrid) {
|
|
749
|
+
const diFactory = this.diFactory;
|
|
750
|
+
|
|
751
|
+
// Step 0: Create Root element
|
|
752
|
+
const diagram = this.diagram;
|
|
753
|
+
|
|
754
|
+
var planeDi = diFactory.createDiPlane({
|
|
755
|
+
id: 'BPMNPlane_' + root.id,
|
|
756
|
+
bpmnElement: root
|
|
757
|
+
});
|
|
758
|
+
var diagramDi = diFactory.createDiDiagram({
|
|
759
|
+
id: 'BPMNDiagram_' + root.id,
|
|
760
|
+
plane: planeDi
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// deepest subprocess is added first - insert at the front
|
|
764
|
+
diagram.diagrams.unshift(diagramDi);
|
|
765
|
+
|
|
766
|
+
const planeElement = planeDi.get('planeElement');
|
|
767
|
+
|
|
768
|
+
// Step 1: Create DI for all elements
|
|
769
|
+
layoutGrid.elementsByPosition().forEach(({ element, row, col }) => {
|
|
770
|
+
const dis = this
|
|
771
|
+
.handle('createElementDi', { element, row, col, layoutGrid, diFactory })
|
|
772
|
+
.flat();
|
|
773
|
+
|
|
774
|
+
planeElement.push(...dis);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
// Step 2: Create DI for all connections
|
|
778
|
+
layoutGrid.elementsByPosition().forEach(({ element, row, col }) => {
|
|
779
|
+
const dis = this
|
|
780
|
+
.handle('createConnectionDi', { element, row, col, layoutGrid, diFactory })
|
|
781
|
+
.flat();
|
|
782
|
+
|
|
783
|
+
planeElement.push(...dis);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
getProcess() {
|
|
789
|
+
return this.diagram.get('rootElements').find(el => el.$type === 'bpmn:Process');
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function layoutProcess(xml) {
|
|
794
|
+
return new Layouter().layoutProcess(xml);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
exports.layoutProcess = layoutProcess;
|
|
798
|
+
//# sourceMappingURL=index.cjs.map
|