@peter.prib/renderengine 0.2.5 → 0.2.7

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.
Files changed (67) hide show
  1. package/README.md +219 -213
  2. package/REFERENCE.md +272 -6
  3. package/dist/renderengine-workspace.umd.js +11980 -7421
  4. package/dist/renderengine.umd.js +8991 -5784
  5. package/examples/archimate.html +68 -66
  6. package/examples/data-model.html +534 -0
  7. package/package.json +70 -68
  8. package/src/bian.js +182 -0
  9. package/src/charting/CheckboxTreeSelector.js +373 -304
  10. package/src/charting/DiagramEditor.js +53 -32
  11. package/src/charting/DiagramTypeRegistry.js +13 -3
  12. package/src/charting/ModelCheckDialog.js +63 -0
  13. package/src/charting/ModelChecker.js +26 -0
  14. package/src/charting/ViewTabStrip.js +94 -81
  15. package/src/charting/archimate/ArchimateCanvasEditor.js +825 -815
  16. package/src/charting/archimate/ArchimateElementTemplate.js +208 -195
  17. package/src/charting/datamodel/BianLandscapeReader.js +185 -0
  18. package/src/charting/datamodel/BianSemanticApiReader.js +183 -0
  19. package/src/charting/datamodel/DataModel.js +645 -0
  20. package/src/charting/datamodel/DataModelChecker.js +92 -0
  21. package/src/charting/datamodel/DataModelCompared.js +127 -0
  22. package/src/charting/datamodel/DataModelContext.js +108 -0
  23. package/src/charting/datamodel/DataModelFileFormat.js +33 -0
  24. package/src/charting/datamodel/DataModelPerspective.js +129 -0
  25. package/src/charting/datamodel/DataType.js +108 -0
  26. package/src/charting/datamodel/EnumerationType.js +51 -0
  27. package/src/charting/datamodel/LevelMapping.js +38 -0
  28. package/src/charting/datamodel/LevelSchema.js +41 -0
  29. package/src/charting/datamodel/ServiceDomain.js +177 -0
  30. package/src/charting/datamodel/ServiceLandscape.js +104 -0
  31. package/src/charting/datamodel/StructuredType.js +63 -0
  32. package/src/charting/datamodel/index.js +15 -0
  33. package/src/charting/diagramModel/MergeModelCommand.js +43 -37
  34. package/src/charting/diagramModel/OpenViewList.js +75 -0
  35. package/src/charting/diagramModel/ViewSetChecker.js +22 -34
  36. package/src/charting/diagrams/DataModelDiagram.js +160 -86
  37. package/src/charting/diagrams/DataModelRelationshipStrategy.js +94 -38
  38. package/src/charting/diagrams/EdgeGeometry.js +578 -553
  39. package/src/charting/diagrams/GraphDiagram.js +11 -1
  40. package/src/charting/diagrams/NodeGeometry.js +169 -158
  41. package/src/charting/diagrams/nesting.js +31 -0
  42. package/src/charting/glyphs/PersonGlyph.js +24 -23
  43. package/src/charting/modelTree/ModelTree.js +202 -164
  44. package/src/charting/modelTree/ModelTreePanel.js +36 -3
  45. package/src/charting/modelTree/TreeViewMode.js +131 -110
  46. package/src/charting/modelTree/modes/FlatTreeMode.js +32 -46
  47. package/src/charting/modelTree/modes/LayerGroupedTreeMode.js +47 -62
  48. package/src/charting/modelTree/modes/TypeGroupedTreeMode.js +46 -60
  49. package/src/datamodel.js +132 -122
  50. package/src/diagramCatalog.js +35 -30
  51. package/src/index.js +2666 -2659
  52. package/src/workspace/EditorShell.js +166 -67
  53. package/src/workspace/PanelLayout.js +56 -0
  54. package/src/workspace/index.js +1 -0
  55. package/src/workspace/layout/TabsNode.js +205 -191
  56. package/src/workspace/parts/ArchimateCanvasPart.js +10 -9
  57. package/src/workspace/parts/ChartPart.js +6 -1
  58. package/src/workspace/parts/DataModelPart.js +544 -0
  59. package/src/workspace/parts/DiagramEditorPart.js +2 -8
  60. package/src/workspace/parts/EditorPart.js +461 -407
  61. package/src/workspace/parts/ModelFile.js +216 -187
  62. package/src/workspace/parts/ModelsPart.js +483 -192
  63. package/src/workspace/storage/BackupFolder.js +145 -0
  64. package/src/workspace/storage/KeyValueStore.js +92 -0
  65. package/src/workspace/storage/ModelLibrary.js +65 -0
  66. package/src/workspace/workArea.js +417 -413
  67. package/src/yaml/YamlReader.js +372 -0
@@ -1,191 +1,205 @@
1
- import { LayoutNode } from './LayoutNode.js';
2
- import { el } from '../dom.js';
3
-
4
- /**
5
- * TabsNode — a tab strip over a stack of children, one visible at a time.
6
- * This is what workbench's eight mutually exclusive tool-panels become, and
7
- * what a WorkArea uses as its editor groups.
8
- *
9
- * Three deliberate behaviours:
10
- * - **Lazy first mount.** A child is only mounted when its tab is first
11
- * selected, so opening a workspace does not pay for eight panels nobody
12
- * looked at. This is the case ContextBus's retained-value replay exists for:
13
- * a part mounting on first activation still sees current selection.
14
- * - **No unmount on switch.** Once mounted a child stays mounted and is merely
15
- * hidden. Tearing down and rebuilding per switch is what makes a shell feel
16
- * slow, and it would discard scroll position and unsaved input.
17
- * - **Closing is optional and delegable.** `closable` adds a close control per
18
- * tab (per-child `closable: false` opts one out — a pinned welcome tab, say).
19
- * With no `ctx.onTabClose` the node closes the tab itself, so a plain layout
20
- * gets working close buttons; with one, the handler owns it, which is how
21
- * WorkArea keeps closing in its model instead of in the DOM.
22
- */
23
- export class TabsNode extends LayoutNode {
24
- static type = 'tabs';
25
-
26
- constructor(spec = {}) {
27
- super(spec);
28
- this.active = Number.isInteger(spec.active) ? spec.active : 0;
29
- /** Group identity — what "open beside THIS group" addresses. */
30
- this.id = spec.id ?? null;
31
- this.closable = spec.closable ?? false;
32
- this._panes = [];
33
- this._tabs = [];
34
- }
35
-
36
- build(ctx) {
37
- this.el.classList.add('ws-tabs');
38
- this.el.append(
39
- el({ cls: 'ws-tabstrip', attrs: { role: 'tablist' }, ref: n => { this.strip = n; } }),
40
- el({ cls: 'ws-tabbody', ref: n => { this.body = n; } }),
41
- );
42
-
43
- // Capture phase: a part may stopPropagation on its own clicks, and
44
- // "the group you last touched" has to hold even then, or the next
45
- // thing you open lands somewhere you weren't looking.
46
- if (this.id) {
47
- this.el.addEventListener('pointerdown', () => ctx.onGroupFocus?.(this.id, this), true);
48
- }
49
-
50
- this._panes = [];
51
- this._tabs = [];
52
-
53
- this.children.forEach((child, i) => {
54
- const pane = el({ cls: 'ws-tabpane' });
55
- this.body.appendChild(pane);
56
- // Render before reading label(): a PartNode only resolves its part
57
- // — and therefore its title — during render(). Building the tab
58
- // first would label every tab with the raw part id.
59
- child.render(pane, ctx);
60
- this._panes.push(pane);
61
- this._tabs.push(this.strip.appendChild(this._buildTab(child, i)));
62
- });
63
-
64
- if (this.children.length) this.select(Math.min(this.active, this.children.length - 1), true);
65
- }
66
-
67
- /**
68
- * One tab. A `<div role="tab">` rather than a `<button>` because a closable
69
- * tab nests a close button, and a button inside a button is invalid markup
70
- * that browsers reparent — so `tabindex`/`keydown` stand in for the button
71
- * behaviour that is lost.
72
- */
73
- _buildTab(child, i) {
74
- const label = child.label?.() ?? child.spec?.title ?? `Tab ${i + 1}`;
75
- const closable = this.closable && (child.spec?.closable ?? true);
76
- return el({
77
- cls: 'ws-tab',
78
- attrs: { role: 'tab', tabindex: '0', title: label },
79
- on: {
80
- click: () => this.select(i),
81
- keydown: (e) => {
82
- if (e.key !== 'Enter' && e.key !== ' ') return;
83
- e.preventDefault();
84
- this.select(i);
85
- },
86
- // Middle-click closes, as it does in every editor and browser.
87
- auxclick: (e) => { if (closable && e.button === 1) { e.preventDefault(); this.requestClose(i); } },
88
- },
89
- children: [
90
- { tag: 'span', cls: 'ws-tab-label', text: label },
91
- closable && {
92
- tag: 'button',
93
- cls: 'ws-tabclose',
94
- text: '×',
95
- attrs: { type: 'button', tabindex: '-1', 'aria-label': `Close ${label}` },
96
- // Without stopPropagation the tab's own click handler also
97
- // fires and selects the tab we are closing.
98
- on: { click: (e) => { e.stopPropagation(); this.requestClose(i); } },
99
- },
100
- ].filter(Boolean),
101
- });
102
- }
103
-
104
- select(index, silent = false) {
105
- if (index < 0 || index >= this.children.length) return;
106
- this.active = index;
107
- this._tabs.forEach((t, i) => {
108
- const on = i === index;
109
- t.classList.toggle('ws-tab-active', on);
110
- t.setAttribute('aria-selected', String(on));
111
- });
112
- this._panes.forEach((p, i) => { p.style.display = i === index ? '' : 'none'; });
113
- // Mount on first reveal only — activate() is idempotent.
114
- this.children[index].activate?.();
115
- if (!silent) this._emitChange();
116
- }
117
-
118
- /**
119
- * A tab's close control was used. Hands off to `ctx.onTabClose` when the
120
- * shell wants to own the decision (it may prompt about unsaved work, or
121
- * rearrange the surrounding splits); otherwise closes it here.
122
- */
123
- requestClose(index) {
124
- const child = this.children[index];
125
- if (!child) return;
126
- const detail = { tabs: this, index, partId: child.spec?.partId ?? null, node: child };
127
- if (this.ctx?.onTabClose) { this.ctx.onTabClose(detail); return; }
128
- this.closeTab(index);
129
- }
130
-
131
- /**
132
- * Remove a tab and its child. Selection moves to the neighbour rather than
133
- * back to the first tab, which is what closing the tab you were reading
134
- * should feel like.
135
- */
136
- closeTab(index) {
137
- const child = this.children[index];
138
- if (!child) return null;
139
- child.dispose();
140
- this._panes[index]?.remove?.();
141
- this._tabs[index]?.remove?.();
142
- this.children.splice(index, 1);
143
- this._panes.splice(index, 1);
144
- this._tabs.splice(index, 1);
145
- // Rebuilt so each tab's handlers close over its new index.
146
- this._tabs.forEach((tab, i) => {
147
- const fresh = this._buildTab(this.children[i], i);
148
- tab.replaceWith(fresh);
149
- this._tabs[i] = fresh;
150
- });
151
- if (this.children.length) {
152
- this.select(Math.min(index, this.children.length - 1), true);
153
- } else {
154
- this.active = 0;
155
- }
156
- this._emitChange();
157
- return child;
158
- }
159
-
160
- /** Show each tab's label as its child gives it now (a part's title that changed). */
161
- refreshLabels() {
162
- this._tabs.forEach((tab, i) => {
163
- const fresh = this._buildTab(this.children[i], i);
164
- fresh.classList.toggle('ws-tab-active', i === this.active);
165
- fresh.setAttribute('aria-selected', String(i === this.active));
166
- tab.replaceWith(fresh);
167
- this._tabs[i] = fresh;
168
- });
169
- }
170
-
171
- /** Activating the container activates only the visible child. */
172
- activate() {
173
- if (this.children.length) this.children[this.active]?.activate?.();
174
- }
175
-
176
- serialize() {
177
- // Spread the spec first: `id` and `closable` are part of the group's
178
- // identity, and a work area that lost group ids on save would reopen
179
- // with every "open beside this group" pointing at nothing.
180
- // The spec is spread rather than rebuilt field by field: `id` and
181
- // `closable` are part of a group's identity, and a work area that lost
182
- // group ids on save would come back with every "open beside this group"
183
- // pointing at nothing.
184
- return {
185
- ...this.spec,
186
- type: TabsNode.type,
187
- active: this.active,
188
- children: this.children.map(c => c.serialize()),
189
- };
190
- }
191
- }
1
+ import { LayoutNode } from './LayoutNode.js';
2
+ import { el } from '../dom.js';
3
+
4
+ /**
5
+ * TabsNode — a tab strip over a stack of children, one visible at a time.
6
+ * This is what workbench's eight mutually exclusive tool-panels become, and
7
+ * what a WorkArea uses as its editor groups.
8
+ *
9
+ * Three deliberate behaviours:
10
+ * - **Lazy first mount.** A child is only mounted when its tab is first
11
+ * selected, so opening a workspace does not pay for eight panels nobody
12
+ * looked at. This is the case ContextBus's retained-value replay exists for:
13
+ * a part mounting on first activation still sees current selection.
14
+ * - **No unmount on switch.** Once mounted a child stays mounted and is merely
15
+ * hidden. Tearing down and rebuilding per switch is what makes a shell feel
16
+ * slow, and it would discard scroll position and unsaved input.
17
+ * - **Closing is optional and delegable.** `closable` adds a close control per
18
+ * tab (per-child `closable: false` opts one out — a pinned welcome tab, say).
19
+ * With no `ctx.onTabClose` the node closes the tab itself, so a plain layout
20
+ * gets working close buttons; with one, the handler owns it, which is how
21
+ * WorkArea keeps closing in its model instead of in the DOM.
22
+ */
23
+ export class TabsNode extends LayoutNode {
24
+ static type = 'tabs';
25
+
26
+ constructor(spec = {}) {
27
+ super(spec);
28
+ this.active = Number.isInteger(spec.active) ? spec.active : 0;
29
+ /** Group identity — what "open beside THIS group" addresses. */
30
+ this.id = spec.id ?? null;
31
+ this.closable = spec.closable ?? false;
32
+ this._panes = [];
33
+ this._tabs = [];
34
+ }
35
+
36
+ build(ctx) {
37
+ this.el.classList.add('ws-tabs');
38
+ this.el.append(
39
+ el({ cls: 'ws-tabstrip', attrs: { role: 'tablist' }, ref: n => { this.strip = n; } }),
40
+ el({ cls: 'ws-tabbody', ref: n => { this.body = n; } }),
41
+ );
42
+
43
+ // Capture phase: a part may stopPropagation on its own clicks, and
44
+ // "the group you last touched" has to hold even then, or the next
45
+ // thing you open lands somewhere you weren't looking.
46
+ if (this.id) {
47
+ this.el.addEventListener('pointerdown', () => ctx.onGroupFocus?.(this.id, this), true);
48
+ }
49
+
50
+ this._panes = [];
51
+ this._tabs = [];
52
+
53
+ this.children.forEach((child, i) => {
54
+ const pane = el({ cls: 'ws-tabpane' });
55
+ this.body.appendChild(pane);
56
+ // Render before reading label(): a PartNode only resolves its part
57
+ // — and therefore its title — during render(). Building the tab
58
+ // first would label every tab with the raw part id.
59
+ child.render(pane, ctx);
60
+ this._panes.push(pane);
61
+ this._tabs.push(this.strip.appendChild(this._buildTab(child, i)));
62
+ });
63
+
64
+ if (this.children.length) this.select(Math.min(this.active, this.children.length - 1), true);
65
+ }
66
+
67
+ /**
68
+ * One tab. A `<div role="tab">` rather than a `<button>` because a closable
69
+ * tab nests a close button, and a button inside a button is invalid markup
70
+ * that browsers reparent — so `tabindex`/`keydown` stand in for the button
71
+ * behaviour that is lost.
72
+ */
73
+ _buildTab(child, i) {
74
+ const label = child.label?.() ?? child.spec?.title ?? `Tab ${i + 1}`;
75
+ const closable = this.closable && (child.spec?.closable ?? true);
76
+ return el({
77
+ cls: 'ws-tab',
78
+ attrs: { role: 'tab', tabindex: '0', title: label },
79
+ on: {
80
+ click: () => this.select(i),
81
+ keydown: (e) => {
82
+ if (e.key !== 'Enter' && e.key !== ' ') return;
83
+ e.preventDefault();
84
+ this.select(i);
85
+ },
86
+ // Middle-click closes, as it does in every editor and browser.
87
+ auxclick: (e) => { if (closable && e.button === 1) { e.preventDefault(); this.requestClose(i); } },
88
+ // Right-click: the shell's menu for what the tab holds, when it offers one.
89
+ contextmenu: (e) => this.requestMenu(i, e),
90
+ },
91
+ children: [
92
+ { tag: 'span', cls: 'ws-tab-label', text: label },
93
+ closable && {
94
+ tag: 'button',
95
+ cls: 'ws-tabclose',
96
+ text: '×',
97
+ attrs: { type: 'button', tabindex: '-1', 'aria-label': `Close ${label}` },
98
+ // Without stopPropagation the tab's own click handler also
99
+ // fires and selects the tab we are closing.
100
+ on: { click: (e) => { e.stopPropagation(); this.requestClose(i); } },
101
+ },
102
+ ].filter(Boolean),
103
+ });
104
+ }
105
+
106
+ select(index, silent = false) {
107
+ if (index < 0 || index >= this.children.length) return;
108
+ this.active = index;
109
+ this._tabs.forEach((t, i) => {
110
+ const on = i === index;
111
+ t.classList.toggle('ws-tab-active', on);
112
+ t.setAttribute('aria-selected', String(on));
113
+ });
114
+ this._panes.forEach((p, i) => { p.style.display = i === index ? '' : 'none'; });
115
+ // Mount on first reveal only — activate() is idempotent.
116
+ this.children[index].activate?.();
117
+ if (!silent) this._emitChange();
118
+ }
119
+
120
+ /**
121
+ * A tab was right-clicked: `ctx.onTabMenu` (the shell's menu for what it
122
+ * holds) gets it, with the event to place the menu by; without one, the
123
+ * browser's own menu shows.
124
+ */
125
+ requestMenu(index, event) {
126
+ const child = this.children[index];
127
+ if (!child || !this.ctx?.onTabMenu) return;
128
+ const shown = this.ctx.onTabMenu({ tabs: this, index, partId: child.spec?.partId ?? null, node: child, event });
129
+ if (shown) event.preventDefault();
130
+ }
131
+
132
+ /**
133
+ * A tab's close control was used. Hands off to `ctx.onTabClose` when the
134
+ * shell wants to own the decision (it may prompt about unsaved work, or
135
+ * rearrange the surrounding splits); otherwise closes it here.
136
+ */
137
+ requestClose(index) {
138
+ const child = this.children[index];
139
+ if (!child) return;
140
+ const detail = { tabs: this, index, partId: child.spec?.partId ?? null, node: child };
141
+ if (this.ctx?.onTabClose) { this.ctx.onTabClose(detail); return; }
142
+ this.closeTab(index);
143
+ }
144
+
145
+ /**
146
+ * Remove a tab and its child. Selection moves to the neighbour rather than
147
+ * back to the first tab, which is what closing the tab you were reading
148
+ * should feel like.
149
+ */
150
+ closeTab(index) {
151
+ const child = this.children[index];
152
+ if (!child) return null;
153
+ child.dispose();
154
+ this._panes[index]?.remove?.();
155
+ this._tabs[index]?.remove?.();
156
+ this.children.splice(index, 1);
157
+ this._panes.splice(index, 1);
158
+ this._tabs.splice(index, 1);
159
+ // Rebuilt so each tab's handlers close over its new index.
160
+ this._tabs.forEach((tab, i) => {
161
+ const fresh = this._buildTab(this.children[i], i);
162
+ tab.replaceWith(fresh);
163
+ this._tabs[i] = fresh;
164
+ });
165
+ if (this.children.length) {
166
+ this.select(Math.min(index, this.children.length - 1), true);
167
+ } else {
168
+ this.active = 0;
169
+ }
170
+ this._emitChange();
171
+ return child;
172
+ }
173
+
174
+ /** Show each tab's label as its child gives it now (a part's title that changed). */
175
+ refreshLabels() {
176
+ this._tabs.forEach((tab, i) => {
177
+ const fresh = this._buildTab(this.children[i], i);
178
+ fresh.classList.toggle('ws-tab-active', i === this.active);
179
+ fresh.setAttribute('aria-selected', String(i === this.active));
180
+ tab.replaceWith(fresh);
181
+ this._tabs[i] = fresh;
182
+ });
183
+ }
184
+
185
+ /** Activating the container activates only the visible child. */
186
+ activate() {
187
+ if (this.children.length) this.children[this.active]?.activate?.();
188
+ }
189
+
190
+ serialize() {
191
+ // Spread the spec first: `id` and `closable` are part of the group's
192
+ // identity, and a work area that lost group ids on save would reopen
193
+ // with every "open beside this group" pointing at nothing.
194
+ // The spec is spread rather than rebuilt field by field: `id` and
195
+ // `closable` are part of a group's identity, and a work area that lost
196
+ // group ids on save would come back with every "open beside this group"
197
+ // pointing at nothing.
198
+ return {
199
+ ...this.spec,
200
+ type: TabsNode.type,
201
+ active: this.active,
202
+ children: this.children.map(c => c.serialize()),
203
+ };
204
+ }
205
+ }
@@ -6,7 +6,6 @@ import { ArchimateCanvasEditor, ArchimatePalette, typeTitle } from '../../charti
6
6
  import { ArchimateNotation } from '../../archimate.js';
7
7
  import { MenuBar } from '../../charting/MenuBar.js';
8
8
  import { ToolbarText } from '../../charting/toolbar/index.js';
9
- import { ViewTabStrip } from '../../charting/ViewTabStrip.js';
10
9
  import { FormDialog } from '../../charting/FormDialog.js';
11
10
  import { CsvImportDialog } from '../../charting/archimate/CsvImportDialog.js';
12
11
  import { pickFile } from '../../charting/toolbar/pickFile.js';
@@ -55,6 +54,8 @@ export class ArchimateCanvasPart extends EditorPart {
55
54
  this.viewpoint = spec.viewpoint ?? 'All';
56
55
  this.viewpointMode = spec.viewpointMode ?? 'fade';
57
56
  this.showDerived = !!spec.showDerived;
57
+ /** Whether a line to a box nested in its source is drawn (Archi leaves it out). */
58
+ this.showNestedLines = !!spec.showNestedLines;
58
59
  this.settings = new SettingsManager();
59
60
  // A settings change (Model settings…, a view's properties) redraws the canvas.
60
61
  this._unsubscribeSettings = this.settings.onSettingsChange(() => { if (this.mounted) this.editor?.render(); });
@@ -187,13 +188,12 @@ export class ArchimateCanvasPart extends EditorPart {
187
188
  elements.id = 'elements';
188
189
  canvas.appendChild(elements);
189
190
 
190
- const tabs = new ViewTabStrip({
191
- document: doc,
192
- onSelect: (id) => model.switchTo(id),
193
- menuItems: (id) => this.viewMenu.items(id),
194
- }).render(model.views(), model.currentViewId);
191
+ const tabs = this.viewTabs();
195
192
 
196
- const { toolbar, side, main, aside } = new EditorShell({ document: doc, sideWidth: '260px', aside: true, asideWidth: this.spec.analysis ? '260px' : '210px' }).mount(this.container);
193
+ const { toolbar, side, main, aside } = new EditorShell({
194
+ document: doc, sideWidth: '260px', aside: true, asideWidth: this.spec.analysis ? '260px' : '210px',
195
+ layout: this.layout, labels: { side: 'Model', aside: this.spec.analysis ? 'Palette and analysis' : 'Palette' },
196
+ }).mount(this.container);
197
197
  Object.assign(aside.style, { padding: '6px', fontSize: '12px' });
198
198
  // With `analysis`, the palette shares the right with the analysis of the selected element.
199
199
  let paletteHost = aside, analysisHost = null;
@@ -224,7 +224,7 @@ export class ArchimateCanvasPart extends EditorPart {
224
224
  diagramContainerId: canvasId,
225
225
  view,
226
226
  model,
227
- nodeOptions: { archimateVersion: this.version, useDistinctShapes: this.distinctShapes, viewpoint: this.viewpoint, viewpointMode: this.viewpointMode, showDerived: this.showDerived },
227
+ nodeOptions: { archimateVersion: this.version, useDistinctShapes: this.distinctShapes, viewpoint: this.viewpoint, viewpointMode: this.viewpointMode, showDerived: this.showDerived, showNestedLines: this.showNestedLines },
228
228
  chooseKind: (kinds, from, to, options) => this.chooseKind(kinds, from, to, options),
229
229
  // The view's settings: background, title, watermark, how invalid relationships show.
230
230
  viewSettings: () => this.settings.getEffectiveSettings(this.model.currentView, 'default'),
@@ -234,8 +234,9 @@ export class ArchimateCanvasPart extends EditorPart {
234
234
  // Canvas templates: placed on a canvas, saved from its shapes (the browser's library).
235
235
  templateMenu: this.templateMenu,
236
236
  // Version and distinct shapes (⚙ Settings) change the palette too.
237
- onNodeOptionsChange: ({ archimateVersion, useDistinctShapes, viewpoint, viewpointMode, showDerived }) => {
237
+ onNodeOptionsChange: ({ archimateVersion, useDistinctShapes, viewpoint, viewpointMode, showDerived, showNestedLines }) => {
238
238
  this.showDerived = !!showDerived;
239
+ this.showNestedLines = !!showNestedLines;
239
240
  this.version = archimateVersion;
240
241
  this.distinctShapes = !!useDistinctShapes;
241
242
  this.viewpoint = viewpoint;
@@ -94,10 +94,15 @@ export class ChartPart extends Part {
94
94
  // The renderer is sized at construction, so a resize means a new engine.
95
95
  this.canvasHost.textContent = '';
96
96
  this.engine = new RenderEngine(this.canvasHost, { rendererType: this.rendererType });
97
- this.engine.register(this.chartId, this.chartType, this.chartOptions);
97
+ this.engine.register(this.chartId, this.chartType, this.drawOptions());
98
98
  this.engine.refreshChart(this.chartId);
99
99
  }
100
100
 
101
+ /** What is drawn: the part's options (its data among them). A subclass that draws something made from them says so. */
102
+ drawOptions() {
103
+ return this.chartOptions;
104
+ }
105
+
101
106
  /** Append a point to a bounded window — see REFERENCE.md's streaming section. */
102
107
  addDataPoint(pointOrFactory, opts) {
103
108
  this.engine?.addDataPoint(this.chartId, pointOrFactory, opts);