@peter.prib/renderengine 0.5.0 → 0.8.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 +297 -17
- package/dist/renderengine-workspace.umd.js +5369 -2952
- package/dist/renderengine.umd.js +4466 -2320
- 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/chartCatalog.js +10 -0
- package/src/charting/ArchimateDiagramEditor.js +1 -1
- package/src/charting/Axis.js +20 -0
- package/src/charting/ChartChooser.js +229 -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/TableChart.js +98 -0
- 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 +86 -10
- 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
|
@@ -33,6 +33,8 @@ import { LagPlotChart } from './charting/charts/LagPlotChart.js';
|
|
|
33
33
|
import { TreemapChart } from './charting/charts/TreemapChart.js';
|
|
34
34
|
import { SunburstChart } from './charting/charts/SunburstChart.js';
|
|
35
35
|
import { OutlineChart } from './charting/charts/OutlineChart.js';
|
|
36
|
+
import { TableChart } from './charting/charts/TableChart.js';
|
|
37
|
+
import { ChartChooser } from './charting/ChartChooser.js';
|
|
36
38
|
import { RadarChart } from './charting/charts/RadarChart.js';
|
|
37
39
|
import { FunnelChart } from './charting/charts/FunnelChart.js';
|
|
38
40
|
import { WaterfallChart } from './charting/charts/WaterfallChart.js';
|
|
@@ -96,7 +98,7 @@ import { ArchimateSpecializationRegistry } from './archimateSpecializations.js';
|
|
|
96
98
|
import { ArchimateSpecializationSelector } from './charting/ArchimateSpecializationSelector.js';
|
|
97
99
|
import { ImageSelector } from './charting/ImageSelector.js';
|
|
98
100
|
import { CheckboxTreeSelector } from './charting/CheckboxTreeSelector.js';
|
|
99
|
-
import { SettingsManager, settingsManager } from './charting/SettingsManager.js';
|
|
101
|
+
import { SettingsManager, settingsManager, TREE_SETTINGS_PROPERTIES } from './charting/SettingsManager.js';
|
|
100
102
|
import { showGlobalSettingsDialog } from './charting/SettingsDialog.js';
|
|
101
103
|
import { ArchimateModel, ArchimateView } from './charting/diagrams/ArchimateModel.js';
|
|
102
104
|
import { CanvasView } from './charting/diagrams/CanvasView.js';
|
|
@@ -144,6 +146,7 @@ import { PopupMenu } from './charting/PopupMenu.js';
|
|
|
144
146
|
import { ViewTabStrip } from './charting/ViewTabStrip.js';
|
|
145
147
|
import { EditorShell } from './workspace/EditorShell.js';
|
|
146
148
|
import { FormDialog } from './charting/FormDialog.js';
|
|
149
|
+
import { Dialog } from './charting/Dialog.js';
|
|
147
150
|
import { ColorField } from './charting/ColorField.js';
|
|
148
151
|
import { TextStyle } from './charting/TextStyle.js';
|
|
149
152
|
import { ArchimateModelChecker } from './charting/diagrams/ArchimateModelChecker.js';
|
|
@@ -158,6 +161,10 @@ import { PropertiesPanel } from './charting/PropertiesPanel.js';
|
|
|
158
161
|
import { MenuBar } from './charting/MenuBar.js';
|
|
159
162
|
import { ArchimateDiagramEditor } from './charting/ArchimateDiagramEditor.js';
|
|
160
163
|
import { ModelTree } from './charting/modelTree/ModelTree.js';
|
|
164
|
+
import { TreeBuilder } from './charting/modelTree/TreeBuilder.js';
|
|
165
|
+
import { ContentSource } from './charting/content/ContentSource.js';
|
|
166
|
+
import { ContentSources } from './charting/content/ContentSources.js';
|
|
167
|
+
import { DUPLICATE_OPTIONS, NOISE_WORDS, FILLER_WORDS, duplicateGroups, duplicateKey, wordKey, nameKey } from './charting/diagrams/duplicates.js';
|
|
161
168
|
import { TreeModeRegistry } from './charting/modelTree/TreeModeRegistry.js';
|
|
162
169
|
import { TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry } from './treeModeCatalog.js';
|
|
163
170
|
import { buildModelTreePanel } from './charting/modelTree/ModelTreePanel.js';
|
|
@@ -210,20 +217,21 @@ import {
|
|
|
210
217
|
// separate browser-only entry: import 'renderengine/src/workspace.js' directly.
|
|
211
218
|
|
|
212
219
|
export {
|
|
213
|
-
Mat4, OrbitCamera, CHART_CATALOG, getChartCatalogEntry, chartRegistry,
|
|
220
|
+
Mat4, OrbitCamera, CHART_CATALOG, getChartCatalogEntry, chartRegistry, ChartChooser, TableChart,
|
|
214
221
|
DIAGRAM_CATALOG, getDiagramCatalogEntry, diagramRegistry,
|
|
215
222
|
TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry,
|
|
216
223
|
AXIS_KIND, ENCODING_CHANNEL, COORD_SYSTEM, COLOR_MODE, CAMERA_MODE,
|
|
217
224
|
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,
|
|
225
|
+
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
226
|
ebnfAstToDiagram, parseEbnf,
|
|
220
227
|
createDataWindow, GraphDataWindow,
|
|
221
228
|
ArchimateNotation, ArchimateLayer, ArchimateElementType, ArchimateRelationshipType,
|
|
222
229
|
Glyph, GLYPHS, glyphFor, drawGlyph, glyphElement, glyphToSvg, GlyphTemplate, ArchimateElementTemplate, ArchimateEdge, ArchimateCanvasEditor, ArchimatePalette, KIND_PREFERENCE, ARCHIMATE_TYPE_MIME,
|
|
223
230
|
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
231
|
ITIconNotation, ITElementType,
|
|
225
|
-
ArchimateValidator, ArchimateSpecializationSelector, ImageSelector, CheckboxTreeSelector, SettingsManager, settingsManager,
|
|
226
|
-
ModelTree, TreeModeRegistry,
|
|
232
|
+
ArchimateValidator, ArchimateSpecializationSelector, ImageSelector, CheckboxTreeSelector, SettingsManager, settingsManager, TREE_SETTINGS_PROPERTIES,
|
|
233
|
+
ModelTree, TreeBuilder, TreeModeRegistry, ContentSource, ContentSources,
|
|
234
|
+
DUPLICATE_OPTIONS, NOISE_WORDS, FILLER_WORDS, duplicateGroups, duplicateKey, wordKey, nameKey,
|
|
227
235
|
RadialLayout, DerivedRelationships, EdgeGeometry,
|
|
228
236
|
buildModelTreePanel, buildAnalysisPanel, buildAnalysisData, buildTypeCategoryTree, buildPropertyTree, DiagramTabs,
|
|
229
237
|
DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole,
|
|
@@ -251,6 +259,11 @@ export {
|
|
|
251
259
|
* @class RenderEngine
|
|
252
260
|
* This class decides which renderer to use and provides a unified drawing API.
|
|
253
261
|
*/
|
|
262
|
+
// Navigation menus (a sidebar's): DOM built from JSON, safe to import under node.
|
|
263
|
+
export { NavEntry, NavItem, NavGroup, NavSection, NavMenu } from './workspace/nav/index.js';
|
|
264
|
+
// Named icons as data (an <svg> spec each); the built-in pool and its class.
|
|
265
|
+
export { IconSet, ICONS, ICON_DEFS, icon } from './icons/index.js';
|
|
266
|
+
|
|
254
267
|
export class RenderEngine {
|
|
255
268
|
/** Layout constants for graphDiagram's `shape: 'table'` node type. */
|
|
256
269
|
static TABLE_NODE = NodeGeometry.TABLE_NODE;
|
|
@@ -439,8 +452,12 @@ export class RenderEngine {
|
|
|
439
452
|
* @param {HTMLElement} container The container element for the chart.
|
|
440
453
|
* @param {object} [options]
|
|
441
454
|
* @param {'canvas' | 'svg'} [options.rendererType='canvas']
|
|
455
|
+
* @param {false|{wheelModifier?: 'ctrl'|'alt'|'shift'}} [options.zoom] - SVG wheel zoom: off, or only
|
|
456
|
+
* with a modifier held (so a plain wheel scrolls the page); on, with a plain wheel, by default
|
|
442
457
|
*/
|
|
443
458
|
constructor(container, options = {}) {
|
|
459
|
+
// Kept: the engines refreshChart() makes for other containers are made with the same options.
|
|
460
|
+
this.options = options;
|
|
444
461
|
if (container) {
|
|
445
462
|
const { rendererType = 'canvas' } = options;
|
|
446
463
|
const width = container.clientWidth;
|
|
@@ -448,11 +465,9 @@ export class RenderEngine {
|
|
|
448
465
|
|
|
449
466
|
this.container = container; // Store container for event listeners
|
|
450
467
|
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
468
|
}
|
|
469
|
+
// No container: a test, or an engine keeping a page of registered
|
|
470
|
+
// charts, each drawn in its own element (refreshChart).
|
|
456
471
|
|
|
457
472
|
if (this.container) {
|
|
458
473
|
this._interactions = new InteractionManager(this);
|
|
@@ -1404,6 +1419,24 @@ export class RenderEngine {
|
|
|
1404
1419
|
this._renderChart(new FunnelChart(this.renderer, data, options));
|
|
1405
1420
|
}
|
|
1406
1421
|
|
|
1422
|
+
/**
|
|
1423
|
+
* Renders rows as a table — a header of column names, a row per record, numeric columns
|
|
1424
|
+
* right-aligned, values shown exactly. Drawn with the renderer's primitives, so it works under
|
|
1425
|
+
* SVG and canvas. ChartChooser offers it for any result, last.
|
|
1426
|
+
* @param {Array<object>} data one object per row
|
|
1427
|
+
* @param {object} [options]
|
|
1428
|
+
* @param {Array<string|{key:string, label?:string, align?:'start'|'end'}>} [options.columns] every key of the first row when absent
|
|
1429
|
+
* @param {number} [options.maxRows] rows beyond it are counted, not drawn
|
|
1430
|
+
* @param {string} [options.nullText] what a null or missing value shows ('—')
|
|
1431
|
+
* @param {string} [options.title]
|
|
1432
|
+
*/
|
|
1433
|
+
tableChart(data, options = {}) {
|
|
1434
|
+
if (!this.renderer) return;
|
|
1435
|
+
this._chart3D = false;
|
|
1436
|
+
this._chartState = { type: 'table', data, options };
|
|
1437
|
+
this._renderChart(new TableChart(this.renderer, data, options));
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1407
1440
|
/**
|
|
1408
1441
|
* Renders a waterfall chart — running-total bars, each floating from the
|
|
1409
1442
|
* previous cumulative value to the new one (a "delta" bar), except
|
|
@@ -2588,7 +2621,10 @@ export class RenderEngine {
|
|
|
2588
2621
|
|
|
2589
2622
|
/**
|
|
2590
2623
|
* Refreshes a registered chart, re-resolving its data and re-invoking
|
|
2591
|
-
* its render method
|
|
2624
|
+
* its render method, in the element with that id: this engine's own
|
|
2625
|
+
* container, or — so one engine can keep a page of charts — an engine of
|
|
2626
|
+
* its own for that element, made with this engine's options and kept
|
|
2627
|
+
* (so a zoom survives the next refresh) while the element is the same.
|
|
2592
2628
|
* @param {string} id Chart container ID
|
|
2593
2629
|
*/
|
|
2594
2630
|
refreshChart(id) {
|
|
@@ -2596,7 +2632,46 @@ export class RenderEngine {
|
|
|
2596
2632
|
const config = this.registry[id];
|
|
2597
2633
|
const container = document.getElementById(id);
|
|
2598
2634
|
if (!container) return;
|
|
2635
|
+
const target = this._engineFor(id, container);
|
|
2636
|
+
if (target !== this) target.fitToContainer();
|
|
2637
|
+
target._drawRegistered(config);
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
/** Every registered chart, refreshed. */
|
|
2641
|
+
refreshAll() {
|
|
2642
|
+
for (const id of Object.keys(this.registry ?? {})) this.refreshChart(id);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
/**
|
|
2646
|
+
* Set the data registered charts read by `dataPath`, and refresh them all —
|
|
2647
|
+
* a live page's poll: `engine.setData(await fetchMetrics())`.
|
|
2648
|
+
* @param {object} data
|
|
2649
|
+
*/
|
|
2650
|
+
setData(data) {
|
|
2651
|
+
this.setGlobalData(data);
|
|
2652
|
+
this.refreshAll();
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
/** The engine that draws into `container` for registered chart `id`. @private */
|
|
2656
|
+
_engineFor(id, container) {
|
|
2657
|
+
if (container === this.container) return this;
|
|
2658
|
+
this._childEngines ??= new Map();
|
|
2659
|
+
let engine = this._childEngines.get(id);
|
|
2660
|
+
if (engine?.container !== container) {
|
|
2661
|
+
engine = this._newEngine(container);
|
|
2662
|
+
this._childEngines.set(id, engine);
|
|
2663
|
+
}
|
|
2664
|
+
engine.globalData = this.globalData;
|
|
2665
|
+
return engine;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
/** An engine for another container, with this one's options. @private */
|
|
2669
|
+
_newEngine(container) {
|
|
2670
|
+
return new RenderEngine(container, this.options);
|
|
2671
|
+
}
|
|
2599
2672
|
|
|
2673
|
+
/** Draw a registration (`{type, options}`) with this engine. @private */
|
|
2674
|
+
_drawRegistered(config) {
|
|
2600
2675
|
// Catalog keys ('bar', 'heatmap', 'graph', 'archimate', ...) resolve
|
|
2601
2676
|
// through the registries to their real method name; railroadDiagram
|
|
2602
2677
|
// isn't a registry entry, so `type` already IS the method name there.
|
|
@@ -2613,6 +2688,7 @@ export class RenderEngine {
|
|
|
2613
2688
|
this[methodName](config.options.nodes, config.options.edges || [], config.options);
|
|
2614
2689
|
} else {
|
|
2615
2690
|
const data = config.options.data || this.resolvePath(config.options.dataPath, config.options.dataMapper);
|
|
2691
|
+
if (data === undefined) return; // registered before its data has arrived
|
|
2616
2692
|
this[methodName](data, config.options);
|
|
2617
2693
|
}
|
|
2618
2694
|
}
|