@peter.prib/renderengine 0.5.0 → 0.7.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 +4 -0
- package/REFERENCE.md +252 -17
- package/dist/renderengine-workspace.umd.js +4995 -2932
- package/dist/renderengine.umd.js +3985 -2193
- package/examples/archimate.html +8 -28
- package/examples/data-model.html +45 -556
- package/package.json +70 -70
- package/src/archimateArchiFormat.js +11 -3
- package/src/archimateExchangeFormat.js +17 -9
- package/src/charting/ArchimateDiagramEditor.js +1 -1
- package/src/charting/Axis.js +20 -0
- package/src/charting/DiagramEditor.js +35 -3
- package/src/charting/Dialog.js +195 -0
- package/src/charting/DuplicatesDialog.js +108 -0
- package/src/charting/FormDialog.js +28 -97
- package/src/charting/InteractionManager.js +3 -0
- package/src/charting/Legend.js +45 -2
- package/src/charting/MergeCompareDialog.js +81 -0
- package/src/charting/PopupMenu.js +12 -3
- package/src/charting/SettingsDialog.js +6 -2
- package/src/charting/SettingsManager.js +14 -1
- package/src/charting/archimate/ArchimateCanvasEditor.js +190 -15
- package/src/charting/archimate/ArchimatePalette.js +1 -1
- package/src/charting/charts/AreaChart.js +5 -2
- package/src/charting/charts/BarChart.js +5 -3
- package/src/charting/charts/BarChart3DOrbit.js +5 -7
- package/src/charting/charts/BubbleChart.js +5 -7
- package/src/charting/charts/BubbleChart3D.js +5 -7
- package/src/charting/charts/CandlestickChart.js +3 -1
- package/src/charting/charts/DensityChart.js +4 -6
- package/src/charting/charts/GroupedBarChart.js +2 -2
- package/src/charting/charts/LineChart.js +5 -2
- package/src/charting/charts/ParallelCoordinatesChart.js +2 -2
- package/src/charting/charts/PipeChart.js +4 -6
- package/src/charting/charts/RadarChart.js +5 -6
- package/src/charting/charts/ScatterChart.js +5 -7
- package/src/charting/charts/StackedBarChart.js +4 -2
- package/src/charting/charts/StackedBarChart3DOrbit.js +5 -7
- package/src/charting/charts/StreamGraphChart.js +5 -2
- package/src/charting/charts/WaterfallChart.js +2 -1
- package/src/charting/content/ContentSource.js +129 -0
- package/src/charting/content/ContentSources.js +77 -0
- package/src/charting/datamodel/DataModel.js +116 -0
- package/src/charting/diagrams/ArchimateModel.js +38 -3
- package/src/charting/diagrams/ArchimateModelChecker.js +13 -4
- package/src/charting/diagrams/MergeElementsCommand.js +124 -101
- package/src/charting/diagrams/SvgZoom.js +20 -1
- package/src/charting/diagrams/duplicates.js +95 -13
- package/src/charting/duplicateSearchForm.js +58 -0
- package/src/charting/modelTree/ModelTree.js +239 -202
- package/src/charting/modelTree/ModelTreePanel.js +955 -919
- package/src/charting/modelTree/TreeBuilder.js +93 -45
- package/src/charting/modelTree/TreeViewMode.js +9 -6
- package/src/charting/modelTree/viewFolders.js +15 -6
- package/src/icons/IconSet.js +86 -0
- package/src/icons/index.js +59 -0
- package/src/index.js +65 -9
- package/src/workspace/dom.js +56 -42
- package/src/workspace/index.js +1 -1
- package/src/workspace/nav/NavEntry.js +42 -0
- package/src/workspace/nav/NavGroup.js +43 -0
- package/src/workspace/nav/NavItem.js +53 -0
- package/src/workspace/nav/NavMenu.js +143 -0
- package/src/workspace/nav/NavSection.js +17 -0
- package/src/workspace/nav/index.js +5 -0
- package/src/workspace/parts/ArchimateCanvasPart.js +8 -3
- package/src/workspace/parts/DataModelPart.js +91 -0
- package/src/workspace/parts/EditorPart.js +8 -0
- package/src/workspace/parts/ModelHosted.js +1 -0
- package/src/workspace/parts/ModelsPart.js +765 -622
- package/src/workspace/styles.js +28 -1
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/** A row of the tree, branch or leaf: one line, compact; a label too long ends in an ellipsis. */
|
|
2
|
+
const ROW_STYLE = `
|
|
3
|
+
cursor: pointer;
|
|
4
|
+
padding: 1px 4px;
|
|
5
|
+
margin: 0;
|
|
6
|
+
border-radius: 3px;
|
|
7
|
+
user-select: none;
|
|
8
|
+
border-left: 3px solid transparent;
|
|
9
|
+
transition: background-color 0.1s;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 4px;
|
|
13
|
+
line-height: 1.35;
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
`;
|
|
17
|
+
|
|
1
18
|
/**
|
|
2
19
|
* Generic tree component builder for hierarchical node rendering.
|
|
3
20
|
* Handles expand/collapse, selection, context menus, and recursive rendering.
|
|
@@ -19,8 +36,14 @@ export class TreeBuilder {
|
|
|
19
36
|
* @param {Map<string, boolean>} [options.openState] - Which folders are open, by node id: kept
|
|
20
37
|
* by the caller so a rebuilt tree opens them as they were. A folder not in it is open unless
|
|
21
38
|
* its node says `collapsed: true`; opening or closing one records it here.
|
|
39
|
+
* @param {(node) => boolean} [options.filter] - Which leaves are shown: a folder is shown when a
|
|
40
|
+
* leaf under it is, and open while filtering. None: every node.
|
|
41
|
+
* @param {number} [options.leafLimit] - The leaves a folder shows; the rest wait behind a
|
|
42
|
+
* "▸ N more" row, which shows them. The folders inside it always show.
|
|
43
|
+
* @param {Set<string>} [options.shownAll] - The folders (by node id) showing every leaf, kept by
|
|
44
|
+
* the caller as openState is; asking for "more" records the folder here.
|
|
22
45
|
*/
|
|
23
|
-
constructor(doc, { container, rootNodes = [], onNodeClick = null, onNodeContext = null, getNodeChildren = null, openState = new Map(), iconFor = null } = {}) {
|
|
46
|
+
constructor(doc, { container, rootNodes = [], onNodeClick = null, onNodeContext = null, getNodeChildren = null, openState = new Map(), iconFor = null, filter = null, leafLimit = Infinity, shownAll = new Set() } = {}) {
|
|
24
47
|
this.doc = doc;
|
|
25
48
|
this.container = container;
|
|
26
49
|
this.rootNodes = rootNodes;
|
|
@@ -29,7 +52,12 @@ export class TreeBuilder {
|
|
|
29
52
|
this.getNodeChildren = getNodeChildren;
|
|
30
53
|
this.openState = openState;
|
|
31
54
|
this.iconFor = iconFor;
|
|
55
|
+
this.filter = filter;
|
|
56
|
+
this.leafLimit = leafLimit;
|
|
57
|
+
this.shownAll = shownAll;
|
|
32
58
|
this.selectedNode = null;
|
|
59
|
+
/** Each node's shown children, asked for once per render (getNodeChildren makes new nodes). */
|
|
60
|
+
this._children = new WeakMap();
|
|
33
61
|
}
|
|
34
62
|
|
|
35
63
|
/**
|
|
@@ -40,10 +68,25 @@ export class TreeBuilder {
|
|
|
40
68
|
const container = this.container;
|
|
41
69
|
if (!container) return;
|
|
42
70
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
this._children = new WeakMap();
|
|
72
|
+
container.replaceChildren(...nodes.filter(node => this._shown(node)).map(node => this._renderNode(node)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A node's children that are shown: with a filter, the leaves it keeps and the folders holding one. @private */
|
|
76
|
+
_childrenOf(node) {
|
|
77
|
+
if (!this._children.has(node)) {
|
|
78
|
+
const all = this.getNodeChildren?.(node) || [];
|
|
79
|
+
this._children.set(node, this.filter ? all.filter(child => this._shown(child)) : all);
|
|
46
80
|
}
|
|
81
|
+
return this._children.get(node);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Whether a node is shown: always without a filter; else a leaf the filter keeps, or a folder with a shown child. @private */
|
|
85
|
+
_shown(node) {
|
|
86
|
+
if (!this.filter) return true;
|
|
87
|
+
if (this._childrenOf(node).length) return true;
|
|
88
|
+
// A folder whose children are all filtered out is not a leaf to test.
|
|
89
|
+
return !(this.getNodeChildren?.(node) || []).length && !!this.filter(node);
|
|
47
90
|
}
|
|
48
91
|
|
|
49
92
|
/**
|
|
@@ -51,14 +94,8 @@ export class TreeBuilder {
|
|
|
51
94
|
* @private
|
|
52
95
|
*/
|
|
53
96
|
_renderNode(node) {
|
|
54
|
-
const children = this.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (hasChildren) {
|
|
58
|
-
return this._renderExpandable(node, children);
|
|
59
|
-
} else {
|
|
60
|
-
return this._renderLeaf(node);
|
|
61
|
-
}
|
|
97
|
+
const children = this._childrenOf(node);
|
|
98
|
+
return children.length ? this._renderExpandable(node, children) : this._renderLeaf(node);
|
|
62
99
|
}
|
|
63
100
|
|
|
64
101
|
/**
|
|
@@ -67,23 +104,13 @@ export class TreeBuilder {
|
|
|
67
104
|
*/
|
|
68
105
|
_renderExpandable(node, children) {
|
|
69
106
|
const details = this.doc.createElement('details');
|
|
70
|
-
|
|
107
|
+
// Open while filtering, to show what was found (not recorded); else as it was.
|
|
108
|
+
details.open = this.filter ? true : this.openState.get(node.id) ?? !node.collapsed;
|
|
71
109
|
details.setAttribute('data-node-id', node.id);
|
|
72
|
-
details.addEventListener('toggle', () => this.openState.set(node.id, details.open));
|
|
110
|
+
details.addEventListener('toggle', () => { if (!this.filter) this.openState.set(node.id, details.open); });
|
|
73
111
|
|
|
74
112
|
const summary = this.doc.createElement('summary');
|
|
75
|
-
summary.style.cssText =
|
|
76
|
-
cursor: pointer;
|
|
77
|
-
padding: 4px 6px;
|
|
78
|
-
margin: 2px 0;
|
|
79
|
-
border-radius: 3px;
|
|
80
|
-
user-select: none;
|
|
81
|
-
border-left: 3px solid transparent;
|
|
82
|
-
transition: background-color 0.1s;
|
|
83
|
-
display: flex;
|
|
84
|
-
align-items: center;
|
|
85
|
-
gap: 6px;
|
|
86
|
-
`;
|
|
113
|
+
summary.style.cssText = ROW_STYLE;
|
|
87
114
|
summary.setAttribute('data-node-id', node.id);
|
|
88
115
|
|
|
89
116
|
this._applyNodeStyling(summary, node);
|
|
@@ -95,15 +122,45 @@ export class TreeBuilder {
|
|
|
95
122
|
details.appendChild(summary);
|
|
96
123
|
|
|
97
124
|
const childContainer = this.doc.createElement('div');
|
|
98
|
-
childContainer.style.cssText = 'padding-left:
|
|
99
|
-
|
|
100
|
-
childContainer.appendChild(this._renderNode(child));
|
|
101
|
-
}
|
|
125
|
+
childContainer.style.cssText = 'padding-left: 12px;';
|
|
126
|
+
this._fillChildren(childContainer, node, children);
|
|
102
127
|
details.appendChild(childContainer);
|
|
103
128
|
|
|
104
129
|
return details;
|
|
105
130
|
}
|
|
106
131
|
|
|
132
|
+
/**
|
|
133
|
+
* A folder's children: its folders, and its leaves up to leafLimit, then a
|
|
134
|
+
* "▸ N more" row that shows the rest (unless the folder shows them all already).
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
137
|
+
_fillChildren(container, node, children) {
|
|
138
|
+
const limit = this.shownAll.has(node.id) ? Infinity : this.leafLimit;
|
|
139
|
+
let leaves = 0;
|
|
140
|
+
let waiting = 0;
|
|
141
|
+
const rows = [];
|
|
142
|
+
for (const child of children) {
|
|
143
|
+
if (!this._childrenOf(child).length && leaves++ >= limit) { waiting++; continue; }
|
|
144
|
+
rows.push(this._renderNode(child));
|
|
145
|
+
}
|
|
146
|
+
if (waiting) rows.push(this._moreRow(waiting, () => {
|
|
147
|
+
this.shownAll.add(node.id);
|
|
148
|
+
this._fillChildren(container, node, children);
|
|
149
|
+
}));
|
|
150
|
+
container.replaceChildren(...rows);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The row standing for `count` leaves not shown; choosing it calls `showAll`. @private */
|
|
154
|
+
_moreRow(count, showAll) {
|
|
155
|
+
const row = this.doc.createElement('div');
|
|
156
|
+
row.setAttribute('data-tree-more', String(count));
|
|
157
|
+
row.style.cssText = `${ROW_STYLE} color: #666; font-style: italic;`;
|
|
158
|
+
row.textContent = `▸ ${count} more`;
|
|
159
|
+
row.title = 'Show them all';
|
|
160
|
+
row.addEventListener('click', (e) => { e.stopPropagation(); showAll(); });
|
|
161
|
+
return row;
|
|
162
|
+
}
|
|
163
|
+
|
|
107
164
|
/**
|
|
108
165
|
* Render a leaf node without children.
|
|
109
166
|
* @private
|
|
@@ -111,23 +168,12 @@ export class TreeBuilder {
|
|
|
111
168
|
_renderLeaf(node) {
|
|
112
169
|
const leaf = this.doc.createElement('div');
|
|
113
170
|
leaf.setAttribute('data-node-id', node.id);
|
|
114
|
-
// Element leaves are
|
|
171
|
+
// Element leaves are drag sources; mark them here so a rebuilt tree keeps that.
|
|
115
172
|
if (node.type === 'element' && node._element) {
|
|
116
173
|
leaf.setAttribute('data-element-id', node._element.id);
|
|
117
174
|
leaf.draggable = true;
|
|
118
175
|
}
|
|
119
|
-
leaf.style.cssText =
|
|
120
|
-
padding: 4px 6px;
|
|
121
|
-
margin: 2px 0;
|
|
122
|
-
border-radius: 3px;
|
|
123
|
-
cursor: pointer;
|
|
124
|
-
user-select: none;
|
|
125
|
-
border-left: 3px solid transparent;
|
|
126
|
-
transition: background-color 0.1s;
|
|
127
|
-
display: flex;
|
|
128
|
-
align-items: center;
|
|
129
|
-
gap: 6px;
|
|
130
|
-
`;
|
|
176
|
+
leaf.style.cssText = ROW_STYLE;
|
|
131
177
|
|
|
132
178
|
this._applyNodeStyling(leaf, node);
|
|
133
179
|
leaf.addEventListener('click', (e) => this._handleNodeClick(e, leaf, node));
|
|
@@ -153,13 +199,15 @@ export class TreeBuilder {
|
|
|
153
199
|
} else if (node.icon) {
|
|
154
200
|
const icon = this.doc.createElement('span');
|
|
155
201
|
icon.textContent = node.icon;
|
|
156
|
-
icon.style.cssText = 'flex-shrink: 0; font-size:
|
|
202
|
+
icon.style.cssText = 'flex-shrink: 0; font-size: 11px;';
|
|
157
203
|
element.appendChild(icon);
|
|
158
204
|
}
|
|
159
205
|
|
|
160
|
-
// Label
|
|
206
|
+
// Label: one line, cut with an ellipsis; the whole of it on hover.
|
|
161
207
|
const label = this.doc.createElement('span');
|
|
162
208
|
label.textContent = node.label || 'Untitled';
|
|
209
|
+
label.style.cssText = 'overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;';
|
|
210
|
+
element.title = label.textContent;
|
|
163
211
|
element.appendChild(label);
|
|
164
212
|
}
|
|
165
213
|
|
|
@@ -7,6 +7,13 @@ export class TreeViewMode {
|
|
|
7
7
|
static key = null; // registry key; subclasses override
|
|
8
8
|
static label = ''; // human-readable label; subclasses override
|
|
9
9
|
|
|
10
|
+
/** Whether `text` is in any of `fields` (case ignored); no text matches everything. */
|
|
11
|
+
static textMatches(text, fields) {
|
|
12
|
+
if (!text) return true;
|
|
13
|
+
const lower = text.toLowerCase();
|
|
14
|
+
return fields.some(f => String(f ?? '').toLowerCase().includes(lower));
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
constructor() {
|
|
11
18
|
if (new.target === TreeViewMode) {
|
|
12
19
|
throw new TypeError('TreeViewMode is abstract — subclass it');
|
|
@@ -59,12 +66,8 @@ export class TreeViewMode {
|
|
|
59
66
|
*/
|
|
60
67
|
_matchesFilter(item, { text = '', typeIds = null, specializationIds = null, propertyIds = null, inDescriptions = false } = {}) {
|
|
61
68
|
// Text filter (label or type; the description too when asked)
|
|
62
|
-
if (text) {
|
|
63
|
-
|
|
64
|
-
const fields = [item.label, item.type, ...(inDescriptions ? [item.description] : [])];
|
|
65
|
-
if (!fields.some(f => String(f ?? '').toLowerCase().includes(textLower))) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
69
|
+
if (!TreeViewMode.textMatches(text, [item.label, item.type, ...(inDescriptions ? [item.description] : [])])) {
|
|
70
|
+
return false;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
// Type filter (elements only; 'type:business-actor' format)
|
|
@@ -6,23 +6,32 @@ const VIEW_ICONS = Object.freeze({ canvas: '🎨' });
|
|
|
6
6
|
* keeps them: each view remembers its sub-folder path (`archiFolder`, folder
|
|
7
7
|
* steps {id, name}). A folder node per sub-folder at this depth (holding the
|
|
8
8
|
* views under it, opened a level deeper), then the views that sit here, each
|
|
9
|
-
* in the model's order.
|
|
9
|
+
* in the model's order. A sub-folder that holds no view is there too, from
|
|
10
|
+
* its path (the model's `emptyViewFolders`).
|
|
10
11
|
* @param {Array<Object>} views
|
|
11
12
|
* @param {number} [depth]
|
|
13
|
+
* @param {Array<Array<{id, name}>>} [emptyFolders] - The paths of the folders with no view in them
|
|
12
14
|
* @returns {Array<Object>} TreeBuilder nodes: {id, label, icon, type: 'view-folder'|'view', ...}
|
|
13
15
|
*/
|
|
14
|
-
export function viewFolderNodes(views, depth = 0) {
|
|
16
|
+
export function viewFolderNodes(views, depth = 0, emptyFolders = []) {
|
|
15
17
|
const folders = new Map();
|
|
18
|
+
const folderOf = (step) => {
|
|
19
|
+
if (!folders.has(step.id)) folders.set(step.id, { step, views: [], empty: [] });
|
|
20
|
+
return folders.get(step.id);
|
|
21
|
+
};
|
|
16
22
|
const here = [];
|
|
17
23
|
for (const view of views) {
|
|
18
24
|
const step = view.archiFolder?.[depth];
|
|
19
25
|
if (!step) { here.push(view); continue; }
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
folderOf(step).views.push(view);
|
|
27
|
+
}
|
|
28
|
+
for (const path of emptyFolders) {
|
|
29
|
+
if (path.length > depth) folderOf(path[depth]).empty.push(path);
|
|
22
30
|
}
|
|
23
31
|
return [
|
|
24
|
-
...[...folders.values()].map(({ step, views: inside }) => ({
|
|
25
|
-
id: `view-folder-${step.id}`, label: step.name || 'Untitled Folder', icon: '📁', type: 'view-folder',
|
|
32
|
+
...[...folders.values()].map(({ step, views: inside, empty }) => ({
|
|
33
|
+
id: `view-folder-${step.id}`, label: step.name || 'Untitled Folder', icon: '📁', type: 'view-folder',
|
|
34
|
+
_views: inside, _emptyFolders: empty, _depth: depth + 1,
|
|
26
35
|
})),
|
|
27
36
|
...here.map(view => ({
|
|
28
37
|
id: view.id, label: view.name || view.title || 'Untitled View', icon: VIEW_ICONS[view.constructor.kind] ?? '📄', type: 'view', _view: view,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconSet — named icons as data, built as `<svg>` specs for el().
|
|
3
|
+
*
|
|
4
|
+
* An icon is JSON: a viewBox, how it is painted, and its shapes as
|
|
5
|
+
* `[tag, attrs]` pairs:
|
|
6
|
+
*
|
|
7
|
+
* { viewBox: '0 0 24 24', paint: 'stroke', shapes: [['circle', { cx: 12, cy: 12, r: 10 }]] }
|
|
8
|
+
*
|
|
9
|
+
* `paint` is 'stroke' (outlined in currentColor — the default, so an icon
|
|
10
|
+
* takes its parent's colour), 'fill' (filled in currentColor) or 'none' (the
|
|
11
|
+
* shapes carry their own colours: a brand mark). No markup is parsed: el()
|
|
12
|
+
* builds the spec in the SVG namespace.
|
|
13
|
+
*/
|
|
14
|
+
const PAINT = {
|
|
15
|
+
stroke: { fill: 'none', stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap': 'round', 'stroke-linejoin': 'round' },
|
|
16
|
+
fill: { fill: 'currentColor' },
|
|
17
|
+
none: {},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class IconSet {
|
|
21
|
+
/** @param {Object<string, Object>} [defs] - Name → icon */
|
|
22
|
+
constructor(defs = {}) {
|
|
23
|
+
this.defs = new Map();
|
|
24
|
+
this.add(defs);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Add (or replace) icons, name → icon. */
|
|
28
|
+
add(defs) {
|
|
29
|
+
for (const [name, def] of Object.entries(defs)) this.define(name, def);
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Add (or replace) one icon; throws when it is not one. */
|
|
34
|
+
define(name, def) {
|
|
35
|
+
if (typeof def?.viewBox !== 'string' || !Array.isArray(def.shapes) || !def.shapes.length) {
|
|
36
|
+
throw new Error(`IconSet: icon "${name}" needs a viewBox and shapes`);
|
|
37
|
+
}
|
|
38
|
+
if (def.paint != null && !(def.paint in PAINT)) {
|
|
39
|
+
throw new Error(`IconSet: icon "${name}" paint is ${Object.keys(PAINT).join(', ')}, not "${def.paint}"`);
|
|
40
|
+
}
|
|
41
|
+
for (const shape of def.shapes) {
|
|
42
|
+
if (!Array.isArray(shape) || typeof shape[0] !== 'string') throw new Error(`IconSet: icon "${name}" shapes are [tag, attrs] pairs`);
|
|
43
|
+
}
|
|
44
|
+
this.defs.set(name, def);
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
has(name) { return this.defs.has(name); }
|
|
49
|
+
|
|
50
|
+
get(name) { return this.defs.get(name) ?? null; }
|
|
51
|
+
|
|
52
|
+
/** Every icon's name, in the order added. */
|
|
53
|
+
names() { return [...this.defs.keys()]; }
|
|
54
|
+
|
|
55
|
+
/** A copy of this set with `defs` added: a host's own icons over these. */
|
|
56
|
+
with(defs) {
|
|
57
|
+
return new IconSet(Object.fromEntries(this.defs)).add(defs);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The icon as an el() spec.
|
|
62
|
+
* @param {string} name
|
|
63
|
+
* @param {Object} [options]
|
|
64
|
+
* @param {number|string} [options.size] - Width and height (default 24; CSS can override)
|
|
65
|
+
* @param {string} [options.title] - Its accessible name; without one it is hidden from screen readers
|
|
66
|
+
* @param {string|string[]} [options.cls] - Extra class names
|
|
67
|
+
*/
|
|
68
|
+
spec(name, { size = 24, title = null, cls = null } = {}) {
|
|
69
|
+
const def = this.defs.get(name);
|
|
70
|
+
if (!def) throw new Error(`IconSet: no icon "${name}"`);
|
|
71
|
+
return {
|
|
72
|
+
tag: 'svg',
|
|
73
|
+
cls: ['re-icon', ...(Array.isArray(cls) ? cls : [cls])],
|
|
74
|
+
attrs: {
|
|
75
|
+
viewBox: def.viewBox, width: size, height: size, focusable: 'false',
|
|
76
|
+
role: title ? 'img' : null, 'aria-hidden': title ? null : 'true',
|
|
77
|
+
...PAINT[def.paint ?? 'stroke'],
|
|
78
|
+
},
|
|
79
|
+
dataset: { icon: name },
|
|
80
|
+
children: [
|
|
81
|
+
...(title ? [{ tag: 'title', text: title }] : []),
|
|
82
|
+
...def.shapes.map(([tag, attrs]) => ({ tag, attrs })),
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in icons — a shared pool any host draws from (`ICONS.spec('cpu')`,
|
|
3
|
+
* or `icon('cpu')`), extended with its own (`ICONS.with({ ... })`). Outlined
|
|
4
|
+
* icons on a 24-unit grid, stroked in currentColor; the brand marks keep their
|
|
5
|
+
* colours. Names say what the picture is, not where it is used.
|
|
6
|
+
*/
|
|
7
|
+
import { IconSet } from './IconSet.js';
|
|
8
|
+
|
|
9
|
+
const box = (x, y, width, height, rx = 1) => ['rect', { x, y, width, height, rx }];
|
|
10
|
+
const path = (d) => ['path', { d }];
|
|
11
|
+
|
|
12
|
+
export const ICON_DEFS = {
|
|
13
|
+
dashboard: { viewBox: '0 0 24 24', shapes: [box(3, 3, 7, 9), box(14, 3, 7, 5), box(14, 12, 7, 9), box(3, 16, 7, 5)] },
|
|
14
|
+
zap: { viewBox: '0 0 24 24', shapes: [path('m13 2-2 10h8L11 22l2-10H5L13 2z')] },
|
|
15
|
+
clock: { viewBox: '0 0 24 24', shapes: [['circle', { cx: 12, cy: 12, r: 10 }], ['polyline', { points: '12 6 12 12 16 14' }]] },
|
|
16
|
+
'clipboard-list': { viewBox: '0 0 24 24', shapes: [
|
|
17
|
+
box(8, 2, 8, 4),
|
|
18
|
+
path('M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'),
|
|
19
|
+
path('M12 11h4'), path('M12 16h4'), path('M8 11h.01'), path('M8 16h.01'),
|
|
20
|
+
] },
|
|
21
|
+
book: { viewBox: '0 0 24 24', shapes: [
|
|
22
|
+
path('M4 19.5A2.5 2.5 0 0 1 6.5 17H20'),
|
|
23
|
+
path('M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z'),
|
|
24
|
+
] },
|
|
25
|
+
cpu: { viewBox: '0 0 24 24', shapes: [
|
|
26
|
+
box(4, 4, 16, 16, 2), box(9, 9, 6, 6),
|
|
27
|
+
...['M15 2v2', 'M15 20v2', 'M2 15h2', 'M2 9h2', 'M20 15h2', 'M20 9h2', 'M9 2v2', 'M9 20v2'].map(path),
|
|
28
|
+
] },
|
|
29
|
+
memory: { viewBox: '0 0 24 24', shapes: [
|
|
30
|
+
path('M22 12s-3 1-5 1-3-1-5-1-3 1-5 1-5-1-5-1'), path('M7 21h10'), path('M9 21V10'), path('M15 21V10'),
|
|
31
|
+
path('M8 3h8a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z'),
|
|
32
|
+
] },
|
|
33
|
+
activity: { viewBox: '0 0 24 24', shapes: [['polyline', { points: '22 12 18 12 15 21 9 3 6 12 2 12' }]] },
|
|
34
|
+
database: { viewBox: '0 0 24 24', shapes: [
|
|
35
|
+
['ellipse', { cx: 12, cy: 5, rx: 9, ry: 3 }],
|
|
36
|
+
path('M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5'),
|
|
37
|
+
path('M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3'),
|
|
38
|
+
] },
|
|
39
|
+
google: { viewBox: '0 0 24 24', paint: 'none', shapes: [
|
|
40
|
+
['path', { fill: '#4285F4', d: 'M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z' }],
|
|
41
|
+
['path', { fill: '#34A853', d: 'M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z' }],
|
|
42
|
+
['path', { fill: '#FBBC05', d: 'M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z' }],
|
|
43
|
+
['path', { fill: '#EA4335', d: 'M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z' }],
|
|
44
|
+
] },
|
|
45
|
+
microsoft: { viewBox: '0 0 21 21', paint: 'none', shapes: [
|
|
46
|
+
['rect', { x: 1, y: 1, width: 9, height: 9, fill: '#F25022' }],
|
|
47
|
+
['rect', { x: 11, y: 1, width: 9, height: 9, fill: '#7FBA00' }],
|
|
48
|
+
['rect', { x: 1, y: 11, width: 9, height: 9, fill: '#00A4EF' }],
|
|
49
|
+
['rect', { x: 11, y: 11, width: 9, height: 9, fill: '#FFB900' }],
|
|
50
|
+
] },
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** The built-in set. */
|
|
54
|
+
export const ICONS = new IconSet(ICON_DEFS);
|
|
55
|
+
|
|
56
|
+
/** A built-in icon as an el() spec — `ICONS.spec(name, options)`. */
|
|
57
|
+
export const icon = (name, options) => ICONS.spec(name, options);
|
|
58
|
+
|
|
59
|
+
export { IconSet };
|
package/src/index.js
CHANGED
|
@@ -96,7 +96,7 @@ import { ArchimateSpecializationRegistry } from './archimateSpecializations.js';
|
|
|
96
96
|
import { ArchimateSpecializationSelector } from './charting/ArchimateSpecializationSelector.js';
|
|
97
97
|
import { ImageSelector } from './charting/ImageSelector.js';
|
|
98
98
|
import { CheckboxTreeSelector } from './charting/CheckboxTreeSelector.js';
|
|
99
|
-
import { SettingsManager, settingsManager } from './charting/SettingsManager.js';
|
|
99
|
+
import { SettingsManager, settingsManager, TREE_SETTINGS_PROPERTIES } from './charting/SettingsManager.js';
|
|
100
100
|
import { showGlobalSettingsDialog } from './charting/SettingsDialog.js';
|
|
101
101
|
import { ArchimateModel, ArchimateView } from './charting/diagrams/ArchimateModel.js';
|
|
102
102
|
import { CanvasView } from './charting/diagrams/CanvasView.js';
|
|
@@ -144,6 +144,7 @@ import { PopupMenu } from './charting/PopupMenu.js';
|
|
|
144
144
|
import { ViewTabStrip } from './charting/ViewTabStrip.js';
|
|
145
145
|
import { EditorShell } from './workspace/EditorShell.js';
|
|
146
146
|
import { FormDialog } from './charting/FormDialog.js';
|
|
147
|
+
import { Dialog } from './charting/Dialog.js';
|
|
147
148
|
import { ColorField } from './charting/ColorField.js';
|
|
148
149
|
import { TextStyle } from './charting/TextStyle.js';
|
|
149
150
|
import { ArchimateModelChecker } from './charting/diagrams/ArchimateModelChecker.js';
|
|
@@ -158,6 +159,10 @@ import { PropertiesPanel } from './charting/PropertiesPanel.js';
|
|
|
158
159
|
import { MenuBar } from './charting/MenuBar.js';
|
|
159
160
|
import { ArchimateDiagramEditor } from './charting/ArchimateDiagramEditor.js';
|
|
160
161
|
import { ModelTree } from './charting/modelTree/ModelTree.js';
|
|
162
|
+
import { TreeBuilder } from './charting/modelTree/TreeBuilder.js';
|
|
163
|
+
import { ContentSource } from './charting/content/ContentSource.js';
|
|
164
|
+
import { ContentSources } from './charting/content/ContentSources.js';
|
|
165
|
+
import { DUPLICATE_OPTIONS, NOISE_WORDS, FILLER_WORDS, duplicateGroups, duplicateKey, wordKey, nameKey } from './charting/diagrams/duplicates.js';
|
|
161
166
|
import { TreeModeRegistry } from './charting/modelTree/TreeModeRegistry.js';
|
|
162
167
|
import { TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry } from './treeModeCatalog.js';
|
|
163
168
|
import { buildModelTreePanel } from './charting/modelTree/ModelTreePanel.js';
|
|
@@ -215,15 +220,16 @@ export {
|
|
|
215
220
|
TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry,
|
|
216
221
|
AXIS_KIND, ENCODING_CHANNEL, COORD_SYSTEM, COLOR_MODE, CAMERA_MODE,
|
|
217
222
|
Command, CompositeCommand, CommandHistory,
|
|
218
|
-
DiagramEditor, DiagramEditorState, Template, PolygonTemplate, RegularPolygonTemplate, RectangleTemplate, RoundedRectangleTemplate, StadiumTemplate, PredefinedProcessTemplate, ClassTemplate, ComponentTemplate, SwimlaneTemplate, CircleTemplate, FinalStateTemplate, EllipseTemplate, DiamondTemplate, TriangleTemplate, PentagonTemplate, HexagonTemplate, OctagonTemplate, StarTemplate, ParallelogramTemplate, TrapezoidTemplate, ManualInputTemplate, CylinderTemplate, DocumentTemplate, NoteTemplate, CloudTemplate, ActorTemplate, PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate, TemplateCatalog, Palette, PropertiesPanel, MenuBar, MoveElementCommand, BringToFrontCommand, SendToBackCommand, RaiseCommand, LowerCommand, CreateTemplateCommand, EditPropertyCommand, ResizeCommand, RotateCommand, SetPropertiesCommand, ReorderCommand, DIAGRAM_TYPES, getTemplatesForDiagramType, getAllDiagramTypes, getDiagramTypesByGroup, EdgeMarker, ArrowMarker, DiamondMarker, CircleMarker, CrowsFootMarker, HollowDiamondMarker, HollowTriangleMarker, OpenArrowMarker, HalfArrowMarker, EDGE_MARKERS, getEdgeMarker, Edge, AddEdgeCommand, DeleteEdgeCommand, ReconnectEdgeCommand, EDGE_STYLE_PROPERTIES, LINE_ONLY_PROPERTIES, LINE_STYLE_DASH, EdgeStyleCascade, SetStyleCommand, EdgeStyleDialog, DiagramModel, DiagramView, ViewSetModel, ViewMenu, ViewPropertiesDialog, SettingsViewPropertiesDialog, DiagramViewPropertiesDialog, inheritableFields, readInheritableFields, LocalJsonStore, PopupMenu, ViewTabStrip, EditorShell, FormDialog, ToolbarItem, ToolbarButton, ToolbarMenu, ToolbarSelect, ToolbarCheckbox, ToolbarText, ToolbarSeparator, pickFile, reorderedZ, stackingOrder, MoveWaypointCommand, AddWaypointCommand, DeleteWaypointCommand,
|
|
223
|
+
DiagramEditor, DiagramEditorState, Template, PolygonTemplate, RegularPolygonTemplate, RectangleTemplate, RoundedRectangleTemplate, StadiumTemplate, PredefinedProcessTemplate, ClassTemplate, ComponentTemplate, SwimlaneTemplate, CircleTemplate, FinalStateTemplate, EllipseTemplate, DiamondTemplate, TriangleTemplate, PentagonTemplate, HexagonTemplate, OctagonTemplate, StarTemplate, ParallelogramTemplate, TrapezoidTemplate, ManualInputTemplate, CylinderTemplate, DocumentTemplate, NoteTemplate, CloudTemplate, ActorTemplate, PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate, TemplateCatalog, Palette, PropertiesPanel, MenuBar, MoveElementCommand, BringToFrontCommand, SendToBackCommand, RaiseCommand, LowerCommand, CreateTemplateCommand, EditPropertyCommand, ResizeCommand, RotateCommand, SetPropertiesCommand, ReorderCommand, DIAGRAM_TYPES, getTemplatesForDiagramType, getAllDiagramTypes, getDiagramTypesByGroup, EdgeMarker, ArrowMarker, DiamondMarker, CircleMarker, CrowsFootMarker, HollowDiamondMarker, HollowTriangleMarker, OpenArrowMarker, HalfArrowMarker, EDGE_MARKERS, getEdgeMarker, Edge, AddEdgeCommand, DeleteEdgeCommand, ReconnectEdgeCommand, EDGE_STYLE_PROPERTIES, LINE_ONLY_PROPERTIES, LINE_STYLE_DASH, EdgeStyleCascade, SetStyleCommand, EdgeStyleDialog, DiagramModel, DiagramView, ViewSetModel, ViewMenu, ViewPropertiesDialog, SettingsViewPropertiesDialog, DiagramViewPropertiesDialog, inheritableFields, readInheritableFields, LocalJsonStore, PopupMenu, ViewTabStrip, EditorShell, Dialog, FormDialog, ToolbarItem, ToolbarButton, ToolbarMenu, ToolbarSelect, ToolbarCheckbox, ToolbarText, ToolbarSeparator, pickFile, reorderedZ, stackingOrder, MoveWaypointCommand, AddWaypointCommand, DeleteWaypointCommand,
|
|
219
224
|
ebnfAstToDiagram, parseEbnf,
|
|
220
225
|
createDataWindow, GraphDataWindow,
|
|
221
226
|
ArchimateNotation, ArchimateLayer, ArchimateElementType, ArchimateRelationshipType,
|
|
222
227
|
Glyph, GLYPHS, glyphFor, drawGlyph, glyphElement, glyphToSvg, GlyphTemplate, ArchimateElementTemplate, ArchimateEdge, ArchimateCanvasEditor, ArchimatePalette, KIND_PREFERENCE, ARCHIMATE_TYPE_MIME,
|
|
223
228
|
ArchimateModel, ArchimateView, CanvasView, PlaceItemsCommand, RemoveLineCommand, CanvasBoxKind, CANVAS_BOX_KINDS, canvasBoxKind, canvasBoxMenuItems, ArchiHint, HintPanel, EditHintCommand, hintMenuItems, CanvasPalette, CANVAS_BOX_MIME, ViewLineEdge, Arrangement, ItemGroup, BackgroundGroup, GroupCommand, UngroupCommand, LockCommand, BackgroundCommand, arrangeMenuItems, TemplateLibrary, CanvasTemplateLibrary, CanvasTemplateMenu, DEFAULT_CANVAS_TEMPLATES, canvasTemplate, EditViewItemCommand, viewItemChanges, ColorField, TextStyle, ArchimateModelChecker, ViewSetChecker, ViewFit, archiElementType, NEAREST_ARCHI_TYPE, ElementInstance, ViewObjectInstance, RelationshipInstance, ArchimateDiagramEditor,
|
|
224
229
|
ITIconNotation, ITElementType,
|
|
225
|
-
ArchimateValidator, ArchimateSpecializationSelector, ImageSelector, CheckboxTreeSelector, SettingsManager, settingsManager,
|
|
226
|
-
ModelTree, TreeModeRegistry,
|
|
230
|
+
ArchimateValidator, ArchimateSpecializationSelector, ImageSelector, CheckboxTreeSelector, SettingsManager, settingsManager, TREE_SETTINGS_PROPERTIES,
|
|
231
|
+
ModelTree, TreeBuilder, TreeModeRegistry, ContentSource, ContentSources,
|
|
232
|
+
DUPLICATE_OPTIONS, NOISE_WORDS, FILLER_WORDS, duplicateGroups, duplicateKey, wordKey, nameKey,
|
|
227
233
|
RadialLayout, DerivedRelationships, EdgeGeometry,
|
|
228
234
|
buildModelTreePanel, buildAnalysisPanel, buildAnalysisData, buildTypeCategoryTree, buildPropertyTree, DiagramTabs,
|
|
229
235
|
DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole,
|
|
@@ -251,6 +257,11 @@ export {
|
|
|
251
257
|
* @class RenderEngine
|
|
252
258
|
* This class decides which renderer to use and provides a unified drawing API.
|
|
253
259
|
*/
|
|
260
|
+
// Navigation menus (a sidebar's): DOM built from JSON, safe to import under node.
|
|
261
|
+
export { NavEntry, NavItem, NavGroup, NavSection, NavMenu } from './workspace/nav/index.js';
|
|
262
|
+
// Named icons as data (an <svg> spec each); the built-in pool and its class.
|
|
263
|
+
export { IconSet, ICONS, ICON_DEFS, icon } from './icons/index.js';
|
|
264
|
+
|
|
254
265
|
export class RenderEngine {
|
|
255
266
|
/** Layout constants for graphDiagram's `shape: 'table'` node type. */
|
|
256
267
|
static TABLE_NODE = NodeGeometry.TABLE_NODE;
|
|
@@ -439,8 +450,12 @@ export class RenderEngine {
|
|
|
439
450
|
* @param {HTMLElement} container The container element for the chart.
|
|
440
451
|
* @param {object} [options]
|
|
441
452
|
* @param {'canvas' | 'svg'} [options.rendererType='canvas']
|
|
453
|
+
* @param {false|{wheelModifier?: 'ctrl'|'alt'|'shift'}} [options.zoom] - SVG wheel zoom: off, or only
|
|
454
|
+
* with a modifier held (so a plain wheel scrolls the page); on, with a plain wheel, by default
|
|
442
455
|
*/
|
|
443
456
|
constructor(container, options = {}) {
|
|
457
|
+
// Kept: the engines refreshChart() makes for other containers are made with the same options.
|
|
458
|
+
this.options = options;
|
|
444
459
|
if (container) {
|
|
445
460
|
const { rendererType = 'canvas' } = options;
|
|
446
461
|
const width = container.clientWidth;
|
|
@@ -448,11 +463,9 @@ export class RenderEngine {
|
|
|
448
463
|
|
|
449
464
|
this.container = container; // Store container for event listeners
|
|
450
465
|
this.renderer = RendererFactory.create(rendererType, container, width, height);
|
|
451
|
-
} else {
|
|
452
|
-
// Allow initialization without a container for testing
|
|
453
|
-
this.options = options;
|
|
454
|
-
console.log('RenderEngine initialized in test mode.');
|
|
455
466
|
}
|
|
467
|
+
// No container: a test, or an engine keeping a page of registered
|
|
468
|
+
// charts, each drawn in its own element (refreshChart).
|
|
456
469
|
|
|
457
470
|
if (this.container) {
|
|
458
471
|
this._interactions = new InteractionManager(this);
|
|
@@ -2588,7 +2601,10 @@ export class RenderEngine {
|
|
|
2588
2601
|
|
|
2589
2602
|
/**
|
|
2590
2603
|
* Refreshes a registered chart, re-resolving its data and re-invoking
|
|
2591
|
-
* its render method
|
|
2604
|
+
* its render method, in the element with that id: this engine's own
|
|
2605
|
+
* container, or — so one engine can keep a page of charts — an engine of
|
|
2606
|
+
* its own for that element, made with this engine's options and kept
|
|
2607
|
+
* (so a zoom survives the next refresh) while the element is the same.
|
|
2592
2608
|
* @param {string} id Chart container ID
|
|
2593
2609
|
*/
|
|
2594
2610
|
refreshChart(id) {
|
|
@@ -2596,7 +2612,46 @@ export class RenderEngine {
|
|
|
2596
2612
|
const config = this.registry[id];
|
|
2597
2613
|
const container = document.getElementById(id);
|
|
2598
2614
|
if (!container) return;
|
|
2615
|
+
const target = this._engineFor(id, container);
|
|
2616
|
+
if (target !== this) target.fitToContainer();
|
|
2617
|
+
target._drawRegistered(config);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
/** Every registered chart, refreshed. */
|
|
2621
|
+
refreshAll() {
|
|
2622
|
+
for (const id of Object.keys(this.registry ?? {})) this.refreshChart(id);
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
/**
|
|
2626
|
+
* Set the data registered charts read by `dataPath`, and refresh them all —
|
|
2627
|
+
* a live page's poll: `engine.setData(await fetchMetrics())`.
|
|
2628
|
+
* @param {object} data
|
|
2629
|
+
*/
|
|
2630
|
+
setData(data) {
|
|
2631
|
+
this.setGlobalData(data);
|
|
2632
|
+
this.refreshAll();
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
/** The engine that draws into `container` for registered chart `id`. @private */
|
|
2636
|
+
_engineFor(id, container) {
|
|
2637
|
+
if (container === this.container) return this;
|
|
2638
|
+
this._childEngines ??= new Map();
|
|
2639
|
+
let engine = this._childEngines.get(id);
|
|
2640
|
+
if (engine?.container !== container) {
|
|
2641
|
+
engine = this._newEngine(container);
|
|
2642
|
+
this._childEngines.set(id, engine);
|
|
2643
|
+
}
|
|
2644
|
+
engine.globalData = this.globalData;
|
|
2645
|
+
return engine;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
/** An engine for another container, with this one's options. @private */
|
|
2649
|
+
_newEngine(container) {
|
|
2650
|
+
return new RenderEngine(container, this.options);
|
|
2651
|
+
}
|
|
2599
2652
|
|
|
2653
|
+
/** Draw a registration (`{type, options}`) with this engine. @private */
|
|
2654
|
+
_drawRegistered(config) {
|
|
2600
2655
|
// Catalog keys ('bar', 'heatmap', 'graph', 'archimate', ...) resolve
|
|
2601
2656
|
// through the registries to their real method name; railroadDiagram
|
|
2602
2657
|
// isn't a registry entry, so `type` already IS the method name there.
|
|
@@ -2613,6 +2668,7 @@ export class RenderEngine {
|
|
|
2613
2668
|
this[methodName](config.options.nodes, config.options.edges || [], config.options);
|
|
2614
2669
|
} else {
|
|
2615
2670
|
const data = config.options.data || this.resolvePath(config.options.dataPath, config.options.dataMapper);
|
|
2671
|
+
if (data === undefined) return; // registered before its data has arrived
|
|
2616
2672
|
this[methodName](data, config.options);
|
|
2617
2673
|
}
|
|
2618
2674
|
}
|