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