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/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