flowink 0.1.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,1336 @@
1
+ import { select } from 'd3';
2
+ import * as yaml from 'js-yaml';
3
+ import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
4
+ import type { DiagramDB } from '../../diagram-api/types.js';
5
+ import { log } from '../../logger.js';
6
+ import { isValidShape, type ShapeID } from '../../rendering-util/rendering-elements/shapes.js';
7
+ import type { Edge, Node } from '../../rendering-util/types.js';
8
+ import type { EdgeMetaData, NodeMetaData } from '../../types.js';
9
+ import utils, { getEdgeId } from '../../utils.js';
10
+ import common from '../common/common.js';
11
+ import {
12
+ setAccTitle,
13
+ getAccTitle,
14
+ getAccDescription,
15
+ setAccDescription,
16
+ clear as commonClear,
17
+ setDiagramTitle,
18
+ getDiagramTitle,
19
+ } from '../common/commonDb.js';
20
+ import { createTooltip } from '../common/svgDrawCommon.js';
21
+ import type {
22
+ FlowClass,
23
+ FlowEdge,
24
+ FlowLink,
25
+ FlowSubGraph,
26
+ FlowText,
27
+ FlowVertex,
28
+ FlowVertexTypeParam,
29
+ } from './types.js';
30
+ import DOMPurify from 'dompurify';
31
+ interface LinkData {
32
+ id: string;
33
+ }
34
+
35
+ const MERMAID_DOM_ID_PREFIX = 'flowchart-';
36
+
37
+ // We are using arrow functions assigned to class instance fields instead of methods as they are required by flow JISON
38
+ export class FlowDB implements DiagramDB {
39
+ private vertexCounter = 0;
40
+ private config = getConfig();
41
+ private diagramId = '';
42
+ private vertices = new Map<string, FlowVertex>();
43
+ private edges: FlowEdge[] & { defaultInterpolate?: string; defaultStyle?: string[] } = [];
44
+ private classes = new Map<string, FlowClass>();
45
+ private subGraphs: FlowSubGraph[] = [];
46
+ private subGraphLookup = new Map<string, FlowSubGraph>();
47
+ private tooltips = new Map<string, string>();
48
+ private subCount = 0;
49
+ private firstGraphFlag = true;
50
+ private direction: string | undefined;
51
+ private version: string | undefined; // As in graph
52
+ private secCount = -1;
53
+ private posCrossRef: number[] = [];
54
+
55
+ // Functions to be run after graph rendering
56
+ private funs: ((element: Element) => void)[] = []; // cspell:ignore funs
57
+
58
+ constructor() {
59
+ this.funs.push(this.setupToolTips.bind(this));
60
+
61
+ // Needed for JISON since it only supports direct properties
62
+ this.addVertex = this.addVertex.bind(this);
63
+ this.firstGraph = this.firstGraph.bind(this);
64
+ this.setDirection = this.setDirection.bind(this);
65
+ this.addSubGraph = this.addSubGraph.bind(this);
66
+ this.addLink = this.addLink.bind(this);
67
+ this.setLink = this.setLink.bind(this);
68
+ this.updateLink = this.updateLink.bind(this);
69
+ this.addClass = this.addClass.bind(this);
70
+ this.setClass = this.setClass.bind(this);
71
+ this.destructLink = this.destructLink.bind(this);
72
+ this.setClickEvent = this.setClickEvent.bind(this);
73
+ this.setTooltip = this.setTooltip.bind(this);
74
+ this.updateLinkInterpolate = this.updateLinkInterpolate.bind(this);
75
+ this.setClickFun = this.setClickFun.bind(this);
76
+ this.bindFunctions = this.bindFunctions.bind(this);
77
+
78
+ this.lex = {
79
+ firstGraph: this.firstGraph.bind(this),
80
+ };
81
+
82
+ this.clear();
83
+ this.setGen('gen-2');
84
+ }
85
+
86
+ private sanitizeText(txt: string) {
87
+ return common.sanitizeText(txt, this.config);
88
+ }
89
+
90
+ private sanitizeNodeLabelType(labelType?: string) {
91
+ switch (labelType) {
92
+ case 'markdown':
93
+ case 'string':
94
+ case 'text':
95
+ return labelType;
96
+ default:
97
+ return 'markdown';
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Sets the diagram's SVG element ID, used to prefix domIds for uniqueness
103
+ * across multiple diagrams on the same page.
104
+ */
105
+ public setDiagramId(svgElementId: string) {
106
+ this.diagramId = svgElementId;
107
+ }
108
+
109
+ /**
110
+ * Function to lookup domId from id in the graph definition.
111
+ * When diagramId is set, returns the prefixed version for DOM uniqueness.
112
+ *
113
+ * @param id - id of the node
114
+ */
115
+ public lookUpDomId(id: string) {
116
+ for (const vertex of this.vertices.values()) {
117
+ if (vertex.id === id) {
118
+ return this.diagramId ? `${this.diagramId}-${vertex.domId}` : vertex.domId;
119
+ }
120
+ }
121
+ return this.diagramId ? `${this.diagramId}-${id}` : id;
122
+ }
123
+
124
+ /**
125
+ * Function called by parser when a node definition has been found
126
+ */
127
+ public addVertex(
128
+ id: string,
129
+ textObj: FlowText,
130
+ type: FlowVertexTypeParam,
131
+ style: string[],
132
+ classes: string[],
133
+ dir: string,
134
+ props = {},
135
+ metadata: any
136
+ ) {
137
+ if (!id || id.trim().length === 0) {
138
+ return;
139
+ }
140
+ // Extract the metadata from the shapeData, the syntax for adding metadata for nodes and edges is the same
141
+ // so at this point we don't know if it's a node or an edge, but we can still extract the metadata
142
+ let doc;
143
+ if (metadata !== undefined) {
144
+ let yamlData;
145
+ // detect if shapeData contains a newline character
146
+ if (!metadata.includes('\n')) {
147
+ yamlData = '{\n' + metadata + '\n}';
148
+ } else {
149
+ yamlData = metadata + '\n';
150
+ }
151
+ doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
152
+ }
153
+
154
+ // Check if this is metadata for an already-declared subgraph
155
+ // (e.g. `sub1@{ view: collapsed }`). The id refers to a subgraph, so
156
+ // route the metadata onto the subgraph instead of creating a vertex.
157
+ const subGraph = this.subGraphLookup.get(id);
158
+ if (subGraph && doc) {
159
+ subGraph.metadata = { ...subGraph.metadata, ...doc };
160
+ return;
161
+ }
162
+
163
+ // Check if this is an edge
164
+ const edge = this.edges.find((e) => e.id === id);
165
+ if (edge) {
166
+ const edgeDoc = doc as EdgeMetaData;
167
+ if (edgeDoc?.animate !== undefined) {
168
+ edge.animate = edgeDoc.animate;
169
+ }
170
+ if (edgeDoc?.animation !== undefined) {
171
+ edge.animation = edgeDoc.animation;
172
+ }
173
+ if (edgeDoc?.curve !== undefined) {
174
+ edge.interpolate = edgeDoc.curve;
175
+ }
176
+ return;
177
+ }
178
+
179
+ let txt;
180
+
181
+ let vertex = this.vertices.get(id);
182
+ if (vertex === undefined) {
183
+ if (textObj === undefined && type === undefined && style !== undefined && style !== null) {
184
+ log.warn(
185
+ `Style applied to unknown node "${id}". This may indicate a typo. The node will be created automatically.`
186
+ );
187
+ }
188
+ vertex = {
189
+ id,
190
+ labelType: 'text',
191
+ domId: MERMAID_DOM_ID_PREFIX + id + '-' + this.vertexCounter,
192
+ styles: [],
193
+ classes: [],
194
+ };
195
+ this.vertices.set(id, vertex);
196
+ }
197
+ this.vertexCounter++;
198
+
199
+ if (textObj !== undefined) {
200
+ this.config = getConfig();
201
+ txt = this.sanitizeText(textObj.text.trim());
202
+ vertex.labelType = textObj.type;
203
+ // strip quotes if string starts and ends with a quote
204
+ if (txt.startsWith('"') && txt.endsWith('"')) {
205
+ txt = txt.substring(1, txt.length - 1);
206
+ }
207
+ vertex.text = txt;
208
+ } else {
209
+ if (vertex.text === undefined) {
210
+ vertex.text = id;
211
+ }
212
+ }
213
+ if (type !== undefined) {
214
+ vertex.type = type;
215
+ }
216
+ if (style !== undefined && style !== null) {
217
+ style.forEach((s) => {
218
+ vertex.styles.push(s);
219
+ });
220
+ }
221
+ if (classes !== undefined && classes !== null) {
222
+ classes.forEach((s) => {
223
+ vertex.classes.push(s);
224
+ });
225
+ }
226
+ if (dir !== undefined) {
227
+ vertex.dir = dir;
228
+ }
229
+ if (vertex.props === undefined) {
230
+ vertex.props = props;
231
+ } else if (props !== undefined) {
232
+ Object.assign(vertex.props, props);
233
+ }
234
+
235
+ if (doc !== undefined) {
236
+ if (doc.shape) {
237
+ if (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_')) {
238
+ throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
239
+ } else if (!isValidShape(doc.shape)) {
240
+ throw new Error(`No such shape: ${doc.shape}.`);
241
+ }
242
+ vertex.type = doc?.shape;
243
+ }
244
+
245
+ if (doc?.label) {
246
+ vertex.text = doc?.label;
247
+ vertex.labelType = this.sanitizeNodeLabelType(doc?.labelType);
248
+ }
249
+ if (doc?.icon) {
250
+ vertex.icon = doc?.icon;
251
+ if (!doc.label?.trim() && vertex.text === id) {
252
+ vertex.text = '';
253
+ }
254
+ }
255
+ if (doc?.form) {
256
+ vertex.form = doc?.form;
257
+ }
258
+ if (doc?.pos) {
259
+ vertex.pos = doc?.pos;
260
+ }
261
+ if (doc?.img) {
262
+ vertex.img = doc?.img;
263
+ if (!doc.label?.trim() && vertex.text === id) {
264
+ vertex.text = '';
265
+ }
266
+ }
267
+ if (doc?.constraint) {
268
+ vertex.constraint = doc.constraint;
269
+ }
270
+ if (doc.w) {
271
+ vertex.assetWidth = Number(doc.w);
272
+ }
273
+ if (doc.h) {
274
+ vertex.assetHeight = Number(doc.h);
275
+ }
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Function called by parser when a link/edge definition has been found
281
+ *
282
+ */
283
+ public addSingleLink(_start: string, _end: string, type: any, id?: string) {
284
+ const start = _start;
285
+ const end = _end;
286
+
287
+ const edge: FlowEdge = {
288
+ start: start,
289
+ end: end,
290
+ type: undefined,
291
+ text: '',
292
+ labelType: 'text',
293
+ classes: [],
294
+ isUserDefinedId: false,
295
+ interpolate: this.edges.defaultInterpolate,
296
+ };
297
+ log.info('abc78 Got edge...', edge);
298
+ const linkTextObj = type.text;
299
+
300
+ if (linkTextObj !== undefined) {
301
+ edge.text = this.sanitizeText(linkTextObj.text.trim());
302
+
303
+ // strip quotes if string starts and ends with a quote
304
+ if (edge.text.startsWith('"') && edge.text.endsWith('"')) {
305
+ edge.text = edge.text.substring(1, edge.text.length - 1);
306
+ }
307
+ edge.labelType = this.sanitizeNodeLabelType(linkTextObj.type);
308
+ }
309
+
310
+ if (type !== undefined) {
311
+ edge.type = type.type;
312
+ edge.stroke = type.stroke;
313
+ edge.length = type.length > 10 ? 10 : type.length;
314
+ }
315
+ if (id && !this.edges.some((e) => e.id === id)) {
316
+ edge.id = id;
317
+ edge.isUserDefinedId = true;
318
+ } else {
319
+ const existingLinks = this.edges.filter((e) => e.start === edge.start && e.end === edge.end);
320
+ if (existingLinks.length === 0) {
321
+ edge.id = getEdgeId(edge.start, edge.end, { counter: 0, prefix: 'L' });
322
+ } else {
323
+ edge.id = getEdgeId(edge.start, edge.end, {
324
+ counter: existingLinks.length + 1,
325
+ prefix: 'L',
326
+ });
327
+ }
328
+ }
329
+
330
+ if (this.edges.length < (this.config.maxEdges ?? 500)) {
331
+ log.info('Pushing edge...');
332
+ this.edges.push(edge);
333
+ } else {
334
+ throw new Error(
335
+ `Edge limit exceeded. ${this.edges.length} edges found, but the limit is ${this.config.maxEdges}.
336
+
337
+ Initialize mermaid with maxEdges set to a higher number to allow more edges.
338
+ You cannot set this config via configuration inside the diagram as it is a secure config.
339
+ You have to call mermaid.initialize.`
340
+ );
341
+ }
342
+ }
343
+
344
+ private isLinkData(value: unknown): value is LinkData {
345
+ return (
346
+ value !== null &&
347
+ typeof value === 'object' &&
348
+ 'id' in value &&
349
+ typeof (value as LinkData).id === 'string'
350
+ );
351
+ }
352
+
353
+ public addLink(_start: string[], _end: string[], linkData: unknown) {
354
+ const id = this.isLinkData(linkData) ? linkData.id.replace('@', '') : undefined;
355
+
356
+ log.info('addLink', _start, _end, id);
357
+
358
+ // for a group syntax like A e1@--> B & C, only the first edge should have a userDefined id
359
+ // the rest of the edges should have auto generated ids
360
+ for (const start of _start) {
361
+ for (const end of _end) {
362
+ //use the id only for last node in _start and first node in _end
363
+ const isLastStart = start === _start[_start.length - 1];
364
+ const isFirstEnd = end === _end[0];
365
+ if (isLastStart && isFirstEnd) {
366
+ this.addSingleLink(start, end, linkData, id);
367
+ } else {
368
+ this.addSingleLink(start, end, linkData, undefined);
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Updates a link's line interpolation algorithm
376
+ */
377
+ public updateLinkInterpolate(positions: ('default' | number)[], interpolate: string) {
378
+ positions.forEach((pos) => {
379
+ if (pos === 'default') {
380
+ this.edges.defaultInterpolate = interpolate;
381
+ } else {
382
+ this.edges[pos].interpolate = interpolate;
383
+ }
384
+ });
385
+ }
386
+
387
+ /**
388
+ * Updates a link with a style
389
+ *
390
+ */
391
+ public updateLink(positions: ('default' | number)[], style: string[]) {
392
+ positions.forEach((pos) => {
393
+ if (typeof pos === 'number' && pos >= this.edges.length) {
394
+ throw new Error(
395
+ `The index ${pos} for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and ${
396
+ this.edges.length - 1
397
+ }. (Help: Ensure that the index is within the range of existing edges.)`
398
+ );
399
+ }
400
+ if (pos === 'default') {
401
+ this.edges.defaultStyle = style;
402
+ } else {
403
+ this.edges[pos].style = style;
404
+ // if edges[pos].style does have fill not set, set it to none
405
+ if (
406
+ (this.edges[pos]?.style?.length ?? 0) > 0 &&
407
+ !this.edges[pos]?.style?.some((s) => s?.startsWith('fill'))
408
+ ) {
409
+ this.edges[pos]?.style?.push('fill:none');
410
+ }
411
+ }
412
+ });
413
+ }
414
+
415
+ public addClass(ids: string, _style: string[]) {
416
+ const style = _style
417
+ .join()
418
+ .replace(/\\,/g, '§§§')
419
+ .replace(/,/g, ';')
420
+ .replace(/§§§/g, ',')
421
+ .split(';');
422
+ ids.split(',').forEach((id) => {
423
+ let classNode = this.classes.get(id);
424
+ if (classNode === undefined) {
425
+ classNode = { id, styles: [], textStyles: [] };
426
+ this.classes.set(id, classNode);
427
+ }
428
+
429
+ if (style !== undefined && style !== null) {
430
+ style.forEach((s) => {
431
+ if (/color/.exec(s)) {
432
+ const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill');
433
+ classNode.textStyles.push(newStyle);
434
+ }
435
+ classNode.styles.push(s);
436
+ });
437
+ }
438
+ });
439
+ }
440
+
441
+ /**
442
+ * Called by parser when a graph definition is found, stores the direction of the chart.
443
+ *
444
+ */
445
+ public setDirection(dir: string) {
446
+ this.direction = dir.trim();
447
+
448
+ if (/.*</.exec(this.direction)) {
449
+ this.direction = 'RL';
450
+ }
451
+ if (/.*\^/.exec(this.direction)) {
452
+ this.direction = 'BT';
453
+ }
454
+ if (/.*>/.exec(this.direction)) {
455
+ this.direction = 'LR';
456
+ }
457
+ if (/.*v/.exec(this.direction)) {
458
+ this.direction = 'TB';
459
+ }
460
+ if (this.direction === 'TD') {
461
+ this.direction = 'TB';
462
+ }
463
+ }
464
+
465
+ /**
466
+ * Called by parser when a special node is found, e.g. a clickable element.
467
+ *
468
+ * @param ids - Comma separated list of ids
469
+ * @param className - Class to add
470
+ */
471
+ public setClass(ids: string, className: string) {
472
+ for (const id of ids.split(',')) {
473
+ const vertex = this.vertices.get(id);
474
+ if (vertex) {
475
+ vertex.classes.push(className);
476
+ }
477
+ const edge = this.edges.find((e) => e.id === id);
478
+ if (edge) {
479
+ edge.classes.push(className);
480
+ }
481
+ const subGraph = this.subGraphLookup.get(id);
482
+ if (subGraph) {
483
+ subGraph.classes.push(className);
484
+ }
485
+ }
486
+ }
487
+
488
+ public setTooltip(ids: string, tooltip: string) {
489
+ if (tooltip === undefined) {
490
+ return;
491
+ }
492
+ tooltip = this.sanitizeText(tooltip);
493
+ for (const id of ids.split(',')) {
494
+ this.tooltips.set(this.version === 'gen-1' ? this.lookUpDomId(id) : id, tooltip);
495
+ }
496
+ }
497
+
498
+ private setClickFun(id: string, functionName: string, functionArgs: string) {
499
+ // if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
500
+ if (getConfig().securityLevel !== 'loose') {
501
+ return;
502
+ }
503
+ if (functionName === undefined) {
504
+ return;
505
+ }
506
+ let argList: string[] = [];
507
+ if (typeof functionArgs === 'string') {
508
+ /* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
509
+ argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
510
+ for (let i = 0; i < argList.length; i++) {
511
+ let item = argList[i].trim();
512
+ /* Removes all double quotes at the start and end of an argument */
513
+ /* This preserves all starting and ending whitespace inside */
514
+ if (item.startsWith('"') && item.endsWith('"')) {
515
+ item = item.substr(1, item.length - 2);
516
+ }
517
+ argList[i] = item;
518
+ }
519
+ }
520
+
521
+ /* if no arguments passed into callback, default to passing in id */
522
+ if (argList.length === 0) {
523
+ argList.push(id);
524
+ }
525
+
526
+ const vertex = this.vertices.get(id);
527
+ if (vertex) {
528
+ vertex.haveCallback = true;
529
+ this.funs.push(() => {
530
+ // Defer lookUpDomId to bind time so it includes the diagramId prefix
531
+ const domId = this.lookUpDomId(id);
532
+ const elem = document.querySelector(`[id="${domId}"]`);
533
+ if (elem !== null) {
534
+ elem.addEventListener(
535
+ 'click',
536
+ () => {
537
+ utils.runFunc(functionName, ...argList);
538
+ },
539
+ false
540
+ );
541
+ }
542
+ });
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Called by parser when a link is found. Adds the URL to the vertex data.
548
+ *
549
+ * @param ids - Comma separated list of ids
550
+ * @param linkStr - URL to create a link for
551
+ * @param target - Target attribute for the link
552
+ */
553
+ public setLink(ids: string, linkStr: string, target: string) {
554
+ ids.split(',').forEach((id) => {
555
+ const vertex = this.vertices.get(id);
556
+ if (vertex !== undefined) {
557
+ vertex.link = utils.formatUrl(linkStr, this.config);
558
+ vertex.linkTarget = target;
559
+ }
560
+ });
561
+ this.setClass(ids, 'clickable');
562
+ }
563
+
564
+ public getTooltip(id: string) {
565
+ return this.tooltips.get(id);
566
+ }
567
+
568
+ /**
569
+ * Called by parser when a click definition is found. Registers an event handler.
570
+ *
571
+ * @param ids - Comma separated list of ids
572
+ * @param functionName - Function to be called on click
573
+ * @param functionArgs - Arguments to be passed to the function
574
+ */
575
+ public setClickEvent(ids: string, functionName: string, functionArgs: string) {
576
+ ids.split(',').forEach((id) => {
577
+ this.setClickFun(id, functionName, functionArgs);
578
+ });
579
+ this.setClass(ids, 'clickable');
580
+ }
581
+
582
+ public bindFunctions(element: Element) {
583
+ this.funs.forEach((fun) => {
584
+ fun(element);
585
+ });
586
+ }
587
+ public getDirection() {
588
+ return this.direction?.trim();
589
+ }
590
+ /**
591
+ * Retrieval function for fetching the found nodes after parsing has completed.
592
+ *
593
+ */
594
+ public getVertices() {
595
+ return this.vertices;
596
+ }
597
+
598
+ /**
599
+ * Retrieval function for fetching the found links after parsing has completed.
600
+ *
601
+ */
602
+ public getEdges() {
603
+ return this.edges;
604
+ }
605
+
606
+ /**
607
+ * Retrieval function for fetching the found class definitions after parsing has completed.
608
+ *
609
+ */
610
+ public getClasses() {
611
+ return this.classes;
612
+ }
613
+
614
+ private setupToolTips(element: Element) {
615
+ const tooltipElem = createTooltip();
616
+
617
+ const svg = select(element).select('svg');
618
+
619
+ const nodes = svg.selectAll('g.node');
620
+ nodes
621
+ .on('mouseover', (e: MouseEvent) => {
622
+ const el = select(e.currentTarget as Element);
623
+ const title = el.attr('title');
624
+
625
+ // Don't try to draw a tooltip if no data is provided
626
+ if (title === null) {
627
+ return;
628
+ }
629
+ const rect = (e.currentTarget as Element)?.getBoundingClientRect();
630
+
631
+ tooltipElem.transition().duration(200).style('opacity', '.9');
632
+ tooltipElem
633
+ .text(el.attr('title'))
634
+ .style('left', window.scrollX + rect.left + (rect.right - rect.left) / 2 + 'px')
635
+ .style('top', window.scrollY + rect.bottom + 'px');
636
+ tooltipElem.html(DOMPurify.sanitize(title));
637
+ el.classed('hover', true);
638
+ })
639
+ .on('mouseout', (e: MouseEvent) => {
640
+ tooltipElem.transition().duration(500).style('opacity', 0);
641
+ const el = select(e.currentTarget as Element);
642
+ el.classed('hover', false);
643
+ });
644
+ }
645
+
646
+ /**
647
+ * Clears the internal graph db so that a new graph can be parsed.
648
+ *
649
+ */
650
+ public clear(ver = 'gen-2') {
651
+ this.vertices = new Map();
652
+ this.classes = new Map();
653
+ this.edges = [];
654
+ this.funs = [this.setupToolTips.bind(this)];
655
+ this.diagramId = '';
656
+ this.subGraphs = [];
657
+ this.subGraphLookup = new Map();
658
+ this.subCount = 0;
659
+ this.tooltips = new Map();
660
+ this.firstGraphFlag = true;
661
+ this.version = ver;
662
+ this.config = getConfig();
663
+ commonClear();
664
+ }
665
+
666
+ public setGen(ver: string) {
667
+ this.version = ver || 'gen-2';
668
+ }
669
+
670
+ public defaultStyle() {
671
+ return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;';
672
+ }
673
+
674
+ public addSubGraph(
675
+ _id: { text: string },
676
+ list: string[],
677
+ _title: { text: string; type: string }
678
+ ) {
679
+ let id: string | undefined = _id.text.trim();
680
+ let title = _title.text;
681
+ if (_id === _title && /\s/.exec(_title.text)) {
682
+ id = undefined;
683
+ }
684
+
685
+ const uniq = (a: any[]) => {
686
+ const prims: any = { boolean: {}, number: {}, string: {} };
687
+ const objs: any[] = [];
688
+
689
+ let dir: string | undefined;
690
+
691
+ const nodeList = a.filter(function (item) {
692
+ const type = typeof item;
693
+ if (item.stmt && item.stmt === 'dir') {
694
+ dir = item.value;
695
+ return false;
696
+ }
697
+ if (item.trim() === '') {
698
+ return false;
699
+ }
700
+ if (type in prims) {
701
+ return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true);
702
+ } else {
703
+ return objs.includes(item) ? false : objs.push(item);
704
+ }
705
+ });
706
+ return { nodeList, dir };
707
+ };
708
+
709
+ const result = uniq(list.flat());
710
+ const nodeList = result.nodeList;
711
+ let dir = result.dir;
712
+ const flowchartConfig = getConfig().flowchart ?? {};
713
+ dir =
714
+ dir ??
715
+ (flowchartConfig.inheritDir
716
+ ? (this.getDirection() ?? (getConfig() as any).direction ?? undefined)
717
+ : undefined);
718
+
719
+ if (this.version === 'gen-1') {
720
+ for (let i = 0; i < nodeList.length; i++) {
721
+ nodeList[i] = this.lookUpDomId(nodeList[i]);
722
+ }
723
+ }
724
+
725
+ id = id ?? 'subGraph' + this.subCount;
726
+ title = title || '';
727
+ title = this.sanitizeText(title);
728
+ this.subCount = this.subCount + 1;
729
+
730
+ const subGraph = {
731
+ id: id,
732
+ nodes: nodeList,
733
+ title: title.trim(),
734
+ classes: [],
735
+ dir,
736
+ labelType: this.sanitizeNodeLabelType(_title?.type),
737
+ };
738
+
739
+ log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);
740
+
741
+ // Remove the members in the new subgraph if they already belong to another subgraph
742
+ subGraph.nodes = this.makeUniq(subGraph, this.subGraphs).nodes;
743
+ this.subGraphs.push(subGraph);
744
+ this.subGraphLookup.set(id, subGraph);
745
+ return id;
746
+ }
747
+
748
+ private getPosForId(id: string) {
749
+ for (const [i, subGraph] of this.subGraphs.entries()) {
750
+ if (subGraph.id === id) {
751
+ return i;
752
+ }
753
+ }
754
+ return -1;
755
+ }
756
+
757
+ private indexNodes2(id: string, pos: number): { result: boolean; count: number } {
758
+ const nodes = this.subGraphs[pos].nodes;
759
+ this.secCount = this.secCount + 1;
760
+ if (this.secCount > 2000) {
761
+ return {
762
+ result: false,
763
+ count: 0,
764
+ };
765
+ }
766
+ this.posCrossRef[this.secCount] = pos;
767
+ // Check if match
768
+ if (this.subGraphs[pos].id === id) {
769
+ return {
770
+ result: true,
771
+ count: 0,
772
+ };
773
+ }
774
+
775
+ let count = 0;
776
+ let posCount = 1;
777
+ while (count < nodes.length) {
778
+ const childPos = this.getPosForId(nodes[count]);
779
+ // Ignore regular nodes (pos will be -1)
780
+ if (childPos >= 0) {
781
+ const res = this.indexNodes2(id, childPos);
782
+ if (res.result) {
783
+ return {
784
+ result: true,
785
+ count: posCount + res.count,
786
+ };
787
+ } else {
788
+ posCount = posCount + res.count;
789
+ }
790
+ }
791
+ count = count + 1;
792
+ }
793
+
794
+ return {
795
+ result: false,
796
+ count: posCount,
797
+ };
798
+ }
799
+
800
+ public getDepthFirstPos(pos: number) {
801
+ return this.posCrossRef[pos];
802
+ }
803
+ public indexNodes() {
804
+ this.secCount = -1;
805
+ if (this.subGraphs.length > 0) {
806
+ this.indexNodes2('none', this.subGraphs.length - 1);
807
+ }
808
+ }
809
+
810
+ public getSubGraphs() {
811
+ return this.subGraphs;
812
+ }
813
+
814
+ public firstGraph() {
815
+ if (this.firstGraphFlag) {
816
+ this.firstGraphFlag = false;
817
+ return true;
818
+ }
819
+ return false;
820
+ }
821
+
822
+ private destructStartLink(_str: string): FlowLink {
823
+ let str = _str.trim();
824
+ let type = 'arrow_open';
825
+
826
+ switch (str[0]) {
827
+ case '<':
828
+ type = 'arrow_point';
829
+ str = str.slice(1);
830
+ break;
831
+ case 'x':
832
+ type = 'arrow_cross';
833
+ str = str.slice(1);
834
+ break;
835
+ case 'o':
836
+ type = 'arrow_circle';
837
+ str = str.slice(1);
838
+ break;
839
+ }
840
+
841
+ let stroke = 'normal';
842
+
843
+ if (str.includes('=')) {
844
+ stroke = 'thick';
845
+ }
846
+
847
+ if (str.includes('.')) {
848
+ stroke = 'dotted';
849
+ }
850
+
851
+ return { type, stroke };
852
+ }
853
+
854
+ private countChar(char: string, str: string) {
855
+ const length = str.length;
856
+ let count = 0;
857
+ for (let i = 0; i < length; ++i) {
858
+ if (str[i] === char) {
859
+ ++count;
860
+ }
861
+ }
862
+ return count;
863
+ }
864
+
865
+ private destructEndLink(_str: string) {
866
+ const str = _str.trim();
867
+ let line = str.slice(0, -1);
868
+ let type = 'arrow_open';
869
+
870
+ switch (str.slice(-1)) {
871
+ case 'x':
872
+ type = 'arrow_cross';
873
+ if (str.startsWith('x')) {
874
+ type = 'double_' + type;
875
+ line = line.slice(1);
876
+ }
877
+ break;
878
+ case '>':
879
+ type = 'arrow_point';
880
+ if (str.startsWith('<')) {
881
+ type = 'double_' + type;
882
+ line = line.slice(1);
883
+ }
884
+ break;
885
+ case 'o':
886
+ type = 'arrow_circle';
887
+ if (str.startsWith('o')) {
888
+ type = 'double_' + type;
889
+ line = line.slice(1);
890
+ }
891
+ break;
892
+ }
893
+
894
+ let stroke = 'normal';
895
+ let length = line.length - 1;
896
+
897
+ if (line.startsWith('=')) {
898
+ stroke = 'thick';
899
+ }
900
+
901
+ if (line.startsWith('~')) {
902
+ stroke = 'invisible';
903
+ }
904
+
905
+ const dots = this.countChar('.', line);
906
+
907
+ if (dots) {
908
+ stroke = 'dotted';
909
+ length = dots;
910
+ }
911
+
912
+ return { type, stroke, length };
913
+ }
914
+
915
+ public destructLink(_str: string, _startStr: string) {
916
+ const info = this.destructEndLink(_str);
917
+ let startInfo;
918
+ if (_startStr) {
919
+ startInfo = this.destructStartLink(_startStr);
920
+
921
+ if (startInfo.stroke !== info.stroke) {
922
+ return { type: 'INVALID', stroke: 'INVALID' };
923
+ }
924
+
925
+ if (startInfo.type === 'arrow_open') {
926
+ // -- xyz --> - take arrow type from ending
927
+ startInfo.type = info.type;
928
+ } else {
929
+ // x-- xyz --> - not supported
930
+ if (startInfo.type !== info.type) {
931
+ return { type: 'INVALID', stroke: 'INVALID' };
932
+ }
933
+
934
+ startInfo.type = 'double_' + startInfo.type;
935
+ }
936
+
937
+ if (startInfo.type === 'double_arrow') {
938
+ startInfo.type = 'double_arrow_point';
939
+ }
940
+
941
+ startInfo.length = info.length;
942
+ return startInfo;
943
+ }
944
+
945
+ return info;
946
+ }
947
+
948
+ // Todo optimizer this by caching existing nodes
949
+ public exists(allSgs: FlowSubGraph[], _id: string) {
950
+ for (const sg of allSgs) {
951
+ if (sg.nodes.includes(_id)) {
952
+ return true;
953
+ }
954
+ }
955
+ return false;
956
+ }
957
+ /**
958
+ * Deletes an id from all subgraphs
959
+ *
960
+ */
961
+ public makeUniq(sg: FlowSubGraph, allSubgraphs: FlowSubGraph[]) {
962
+ const res: string[] = [];
963
+ sg.nodes.forEach((_id, pos) => {
964
+ if (!this.exists(allSubgraphs, _id)) {
965
+ res.push(sg.nodes[pos]);
966
+ }
967
+ });
968
+ return { nodes: res };
969
+ }
970
+
971
+ public lex: { firstGraph: typeof FlowDB.prototype.firstGraph };
972
+
973
+ private getTypeFromVertex(vertex: FlowVertex): ShapeID {
974
+ if (vertex.img) {
975
+ return 'imageSquare';
976
+ }
977
+ if (vertex.icon) {
978
+ if (vertex.form === 'circle') {
979
+ return 'iconCircle';
980
+ }
981
+ if (vertex.form === 'square') {
982
+ return 'iconSquare';
983
+ }
984
+ if (vertex.form === 'rounded') {
985
+ return 'iconRounded';
986
+ }
987
+ return 'icon';
988
+ }
989
+ switch (vertex.type) {
990
+ case 'square':
991
+ case undefined:
992
+ return 'squareRect';
993
+ case 'round':
994
+ return 'roundedRect';
995
+ case 'ellipse':
996
+ // @ts-expect-error -- Ellipses are broken, see https://github.com/mermaid-js/mermaid/issues/5976
997
+ return 'ellipse';
998
+ default:
999
+ return vertex.type;
1000
+ }
1001
+ }
1002
+
1003
+ private findNode(nodes: Node[], id: string) {
1004
+ return nodes.find((node) => node.id === id);
1005
+ }
1006
+ private destructEdgeType(type: string | undefined) {
1007
+ let arrowTypeStart = 'none';
1008
+ let arrowTypeEnd = 'arrow_point';
1009
+ switch (type) {
1010
+ case 'arrow_point':
1011
+ case 'arrow_circle':
1012
+ case 'arrow_cross':
1013
+ arrowTypeEnd = type;
1014
+ break;
1015
+
1016
+ case 'double_arrow_point':
1017
+ case 'double_arrow_circle':
1018
+ case 'double_arrow_cross':
1019
+ arrowTypeStart = type.replace('double_', '');
1020
+ arrowTypeEnd = arrowTypeStart;
1021
+ break;
1022
+ }
1023
+ return { arrowTypeStart, arrowTypeEnd };
1024
+ }
1025
+
1026
+ private addNodeFromVertex(
1027
+ vertex: FlowVertex,
1028
+ nodes: Node[],
1029
+ parentDB: Map<string, string>,
1030
+ subGraphDB: Map<string, boolean>,
1031
+ config: any,
1032
+ look: string
1033
+ ) {
1034
+ const parentId = parentDB.get(vertex.id);
1035
+ const isGroup = subGraphDB.get(vertex.id) ?? false;
1036
+
1037
+ const node = this.findNode(nodes, vertex.id);
1038
+ if (node) {
1039
+ node.cssStyles = vertex.styles;
1040
+ node.cssCompiledStyles = this.getCompiledStyles(vertex.classes);
1041
+ node.cssClasses = vertex.classes.join(' ');
1042
+ } else {
1043
+ const baseNode = {
1044
+ id: vertex.id,
1045
+ label: vertex.text,
1046
+ labelType: vertex.labelType,
1047
+ labelStyle: '',
1048
+ parentId,
1049
+ padding: config.flowchart?.padding || 8,
1050
+ cssStyles: vertex.styles,
1051
+ cssCompiledStyles: this.getCompiledStyles(['default', 'node', ...vertex.classes]),
1052
+ cssClasses: 'default ' + vertex.classes.join(' '),
1053
+ dir: vertex.dir,
1054
+ domId: vertex.domId,
1055
+ look,
1056
+ link: vertex.link,
1057
+ linkTarget: vertex.linkTarget,
1058
+ tooltip: this.getTooltip(vertex.id),
1059
+ icon: vertex.icon,
1060
+ pos: vertex.pos,
1061
+ img: vertex.img,
1062
+ assetWidth: vertex.assetWidth,
1063
+ assetHeight: vertex.assetHeight,
1064
+ constraint: vertex.constraint,
1065
+ };
1066
+ if (isGroup) {
1067
+ nodes.push({
1068
+ ...baseNode,
1069
+ isGroup: true,
1070
+ shape: 'rect',
1071
+ });
1072
+ } else {
1073
+ nodes.push({
1074
+ ...baseNode,
1075
+ isGroup: false,
1076
+ shape: this.getTypeFromVertex(vertex),
1077
+ minWidth: config.flowchart?.minNodeWidth,
1078
+ });
1079
+ }
1080
+ }
1081
+ }
1082
+
1083
+ private getCompiledStyles(classDefs: string[]) {
1084
+ let compiledStyles: string[] = [];
1085
+ for (const customClass of classDefs) {
1086
+ const cssClass = this.classes.get(customClass);
1087
+ if (cssClass?.styles) {
1088
+ compiledStyles = [...compiledStyles, ...(cssClass.styles ?? [])].map((s) => s.trim());
1089
+ }
1090
+ if (cssClass?.textStyles) {
1091
+ compiledStyles = [...compiledStyles, ...(cssClass.textStyles ?? [])].map((s) => s.trim());
1092
+ }
1093
+ }
1094
+ return compiledStyles;
1095
+ }
1096
+
1097
+ public getData() {
1098
+ const config = getConfig();
1099
+ const nodes: Node[] = [];
1100
+ const edges: Edge[] = [];
1101
+
1102
+ const subGraphs = this.getSubGraphs();
1103
+ const parentDB = new Map<string, string>();
1104
+ const subGraphDB = new Map<string, boolean>();
1105
+
1106
+ // ── Collapsible subgraphs (issue #7784) ──────────────────────────────
1107
+ // A subgraph carrying `@{ view: collapsed }` is drawn as a single compact
1108
+ // node; its descendants are hidden and any edge that crosses the boundary
1109
+ // is redirected to the outermost collapsed ancestor.
1110
+ //
1111
+ // `subGraphParent` maps a subgraph id to the subgraph that directly
1112
+ // contains it, so we can walk up the containment chain to find the
1113
+ // outermost collapsed ancestor regardless of declaration order.
1114
+ const subGraphParent = new Map<string, string>();
1115
+ for (const sg of subGraphs) {
1116
+ for (const childId of sg.nodes) {
1117
+ if (this.subGraphLookup.has(childId)) {
1118
+ subGraphParent.set(childId, sg.id);
1119
+ }
1120
+ }
1121
+ }
1122
+ /* Colour slots follow the order the subgraphs appear in the source.
1123
+ *
1124
+ * `subGraphs` is not in that order: the grammar reduces a subgraph when it *closes*,
1125
+ * so a nested one lands before its parent -- `Outer { InnerOne, InnerTwo }, Sibling`
1126
+ * arrives as [InnerOne, InnerTwo, Outer, Sibling]. Taking the array index directly
1127
+ * would hand Outer slot 2 while its own children took 0 and 1.
1128
+ *
1129
+ * A pre-order walk of the containment forest recovers source order: roots complete in
1130
+ * source order relative to each other, and a parent is always declared before the
1131
+ * children it contains. Assigned once here so the collapsed and expanded branches
1132
+ * below cannot drift apart.
1133
+ */
1134
+ const declarationIndex = new Map<string, number>();
1135
+ const childrenOf = new Map<string, string[]>();
1136
+ for (const sg of subGraphs) {
1137
+ const parent = subGraphParent.get(sg.id);
1138
+ if (parent !== undefined) {
1139
+ childrenOf.set(parent, [...(childrenOf.get(parent) ?? []), sg.id]);
1140
+ }
1141
+ }
1142
+ let nextDeclarationIndex = 0;
1143
+ const walk = (sgId: string) => {
1144
+ declarationIndex.set(sgId, nextDeclarationIndex++);
1145
+ for (const childId of childrenOf.get(sgId) ?? []) {
1146
+ walk(childId);
1147
+ }
1148
+ };
1149
+ for (const sg of subGraphs) {
1150
+ if (!subGraphParent.has(sg.id)) {
1151
+ walk(sg.id);
1152
+ }
1153
+ }
1154
+
1155
+ const isCollapsed = (sgId: string) =>
1156
+ this.subGraphLookup.get(sgId)?.metadata?.view === 'collapsed';
1157
+ const outermostCollapsed = (sgId: string): string | undefined => {
1158
+ let result: string | undefined;
1159
+ const seen = new Set<string>();
1160
+ let current: string | undefined = sgId;
1161
+ while (current !== undefined && !seen.has(current)) {
1162
+ seen.add(current);
1163
+ if (isCollapsed(current)) {
1164
+ result = current;
1165
+ }
1166
+ current = subGraphParent.get(current);
1167
+ }
1168
+ return result;
1169
+ };
1170
+
1171
+ // `hiddenIds` are nodes/subgraphs that are not drawn; `collapsedAncestorMap`
1172
+ // maps each hidden id to the visible collapsed node that replaces it.
1173
+ const hiddenIds = new Set<string>();
1174
+ const collapsedAncestorMap = new Map<string, string>();
1175
+ for (const sg of subGraphs) {
1176
+ const ancestor = outermostCollapsed(sg.id);
1177
+ if (ancestor === undefined) {
1178
+ continue;
1179
+ }
1180
+ // Hide the subgraph itself unless it is the visible collapsed node.
1181
+ if (sg.id !== ancestor) {
1182
+ hiddenIds.add(sg.id);
1183
+ collapsedAncestorMap.set(sg.id, ancestor);
1184
+ }
1185
+ // Hide every member, redirecting it to the visible collapsed node.
1186
+ for (const childId of sg.nodes) {
1187
+ if (childId === ancestor) {
1188
+ continue;
1189
+ }
1190
+ hiddenIds.add(childId);
1191
+ collapsedAncestorMap.set(childId, ancestor);
1192
+ }
1193
+ }
1194
+
1195
+ // Setup the subgraph data for adding nodes
1196
+ for (let i = subGraphs.length - 1; i >= 0; i--) {
1197
+ const subGraph = subGraphs[i];
1198
+ if (hiddenIds.has(subGraph.id)) {
1199
+ continue;
1200
+ }
1201
+ if (subGraph.nodes.length > 0) {
1202
+ subGraphDB.set(subGraph.id, true);
1203
+ }
1204
+ for (const id of subGraph.nodes) {
1205
+ parentDB.set(id, subGraph.id);
1206
+ }
1207
+ }
1208
+
1209
+ // Data is setup, add the nodes
1210
+ for (let i = subGraphs.length - 1; i >= 0; i--) {
1211
+ const subGraph = subGraphs[i];
1212
+ if (hiddenIds.has(subGraph.id)) {
1213
+ continue;
1214
+ }
1215
+ if (subGraph.metadata?.view === 'collapsed') {
1216
+ // Collapsed: draw as a single compact node instead of a container.
1217
+ nodes.push({
1218
+ id: subGraph.id,
1219
+ label: subGraph.title,
1220
+ labelStyle: '',
1221
+ labelType: subGraph.labelType,
1222
+ parentId: parentDB.get(subGraph.id),
1223
+ padding: 8,
1224
+ cssCompiledStyles: this.getCompiledStyles(subGraph.classes),
1225
+ cssClasses: subGraph.classes.join(' '),
1226
+ shape: 'collapsedGroup',
1227
+ dir: subGraph.dir,
1228
+ isGroup: false,
1229
+ look: config.look,
1230
+ // A collapsed subgraph still consumes its slot, so the cycle does not shift
1231
+ // when one is collapsed.
1232
+ colorIndex: declarationIndex.get(subGraph.id),
1233
+ });
1234
+ } else {
1235
+ nodes.push({
1236
+ id: subGraph.id,
1237
+ label: subGraph.title,
1238
+ labelStyle: '',
1239
+ labelType: subGraph.labelType,
1240
+ parentId: parentDB.get(subGraph.id),
1241
+ padding: 8,
1242
+ cssCompiledStyles: this.getCompiledStyles(subGraph.classes),
1243
+ cssClasses: subGraph.classes.join(' '),
1244
+ shape: 'rect',
1245
+ dir: subGraph.dir,
1246
+ isGroup: true,
1247
+ look: config.look,
1248
+ colorIndex: declarationIndex.get(subGraph.id),
1249
+ // Forwarded so layout engines can read per-container settings such as
1250
+ // `@{ algorithm: elk.box }`. `view` is consumed above; everything else
1251
+ // is opaque here and simply passed through. The cast is the
1252
+ // interface-vs-index-signature gap: `NodeMetaData` is an interface, so
1253
+ // it is not structurally assignable to `Record<string, unknown>`.
1254
+ metadata: subGraph.metadata as Record<string, unknown> | undefined,
1255
+ });
1256
+ }
1257
+ }
1258
+
1259
+ const n = this.getVertices();
1260
+ n.forEach((vertex) => {
1261
+ // Skip vertices hidden inside a collapsed subgraph
1262
+ if (hiddenIds.has(vertex.id)) {
1263
+ return;
1264
+ }
1265
+ this.addNodeFromVertex(vertex, nodes, parentDB, subGraphDB, config, config.look || 'classic');
1266
+ });
1267
+
1268
+ const e = this.getEdges();
1269
+ e.forEach((rawEdge, index) => {
1270
+ const { arrowTypeStart, arrowTypeEnd } = this.destructEdgeType(rawEdge.type);
1271
+ const styles = [...(e.defaultStyle ?? [])];
1272
+
1273
+ // Redirect boundary-crossing edges to the visible collapsed node. An
1274
+ // edge that becomes a self-loop purely because both endpoints collapsed
1275
+ // into the same node (i.e. it was internal to a collapsed subgraph) is
1276
+ // dropped — self-loops on nodes that are not collapsed are preserved.
1277
+ const start = collapsedAncestorMap.get(rawEdge.start) ?? rawEdge.start;
1278
+ const end = collapsedAncestorMap.get(rawEdge.end) ?? rawEdge.end;
1279
+ if (
1280
+ start === end &&
1281
+ (collapsedAncestorMap.has(rawEdge.start) || collapsedAncestorMap.has(rawEdge.end))
1282
+ ) {
1283
+ return;
1284
+ }
1285
+
1286
+ if (rawEdge.style) {
1287
+ styles.push(...rawEdge.style);
1288
+ }
1289
+ const edge: Edge = {
1290
+ id: getEdgeId(start, end, { counter: index, prefix: 'L' }, rawEdge.id),
1291
+ isUserDefinedId: rawEdge.isUserDefinedId,
1292
+ start,
1293
+ end,
1294
+ type: rawEdge.type ?? 'normal',
1295
+ label: rawEdge.text,
1296
+ labelType: rawEdge.labelType,
1297
+ labelpos: 'c',
1298
+ thickness: rawEdge.stroke,
1299
+ minlen: rawEdge.length,
1300
+ classes:
1301
+ rawEdge?.stroke === 'invisible'
1302
+ ? ''
1303
+ : 'edge-thickness-normal edge-pattern-solid flowchart-link',
1304
+ arrowTypeStart:
1305
+ rawEdge?.stroke === 'invisible' || rawEdge?.type === 'arrow_open'
1306
+ ? 'none'
1307
+ : arrowTypeStart,
1308
+ arrowTypeEnd:
1309
+ rawEdge?.stroke === 'invisible' || rawEdge?.type === 'arrow_open' ? 'none' : arrowTypeEnd,
1310
+ arrowheadStyle: 'fill: #333',
1311
+ cssCompiledStyles: this.getCompiledStyles(rawEdge.classes),
1312
+ labelStyle: styles,
1313
+ style: styles,
1314
+ pattern: rawEdge.stroke,
1315
+ look: config.look,
1316
+ animate: rawEdge.animate,
1317
+ animation: rawEdge.animation,
1318
+ curve: rawEdge.interpolate || this.edges.defaultInterpolate || config.flowchart?.curve,
1319
+ };
1320
+
1321
+ edges.push(edge);
1322
+ });
1323
+
1324
+ return { nodes, edges, other: {}, config };
1325
+ }
1326
+
1327
+ public defaultConfig() {
1328
+ return defaultConfig.flowchart;
1329
+ }
1330
+ public setAccTitle = setAccTitle;
1331
+ public setAccDescription = setAccDescription;
1332
+ public setDiagramTitle = setDiagramTitle;
1333
+ public getAccTitle = getAccTitle;
1334
+ public getAccDescription = getAccDescription;
1335
+ public getDiagramTitle = getDiagramTitle;
1336
+ }