@peter.prib/renderengine 0.2.5 → 0.2.6
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 +215 -213
- package/REFERENCE.md +94 -6
- package/dist/renderengine-workspace.umd.js +3254 -2089
- package/dist/renderengine.umd.js +3016 -2086
- package/examples/data-model.html +324 -0
- package/package.json +70 -68
- package/src/charting/CheckboxTreeSelector.js +373 -304
- package/src/charting/DiagramEditor.js +48 -6
- package/src/charting/DiagramTypeRegistry.js +5 -1
- package/src/charting/archimate/ArchimateCanvasEditor.js +825 -815
- package/src/charting/archimate/ArchimateElementTemplate.js +208 -195
- package/src/charting/datamodel/DataModel.js +299 -0
- package/src/charting/datamodel/DataModelContext.js +108 -0
- package/src/charting/datamodel/DataModelPerspective.js +84 -0
- package/src/charting/datamodel/LevelMapping.js +38 -0
- package/src/charting/datamodel/LevelSchema.js +41 -0
- package/src/charting/datamodel/index.js +5 -0
- package/src/charting/diagrams/DataModelDiagram.js +160 -86
- package/src/charting/diagrams/DataModelRelationshipStrategy.js +86 -38
- package/src/charting/diagrams/nesting.js +31 -0
- package/src/charting/modelTree/ModelTree.js +202 -164
- package/src/charting/modelTree/ModelTreePanel.js +36 -3
- package/src/charting/modelTree/TreeViewMode.js +131 -110
- package/src/charting/modelTree/modes/FlatTreeMode.js +32 -46
- package/src/charting/modelTree/modes/LayerGroupedTreeMode.js +47 -62
- package/src/charting/modelTree/modes/TypeGroupedTreeMode.js +46 -60
- package/src/datamodel.js +132 -122
- package/src/diagramCatalog.js +35 -30
- package/src/index.js +6 -1
- package/src/workspace/index.js +1 -0
- package/src/workspace/parts/ArchimateCanvasPart.js +5 -2
- package/src/workspace/parts/ChartPart.js +6 -1
- package/src/workspace/parts/DataModelPart.js +234 -0
package/src/datamodel.js
CHANGED
|
@@ -1,122 +1,132 @@
|
|
|
1
|
-
// Copyright 2026 Jaroslav Peter Prib, Purveyor of Excellence
|
|
2
|
-
/**
|
|
3
|
-
* Data-modelling notation — deliberately its own file/OO module, same split
|
|
4
|
-
* as src/archimate.js: keeps this domain's vocabulary (levels, notation
|
|
5
|
-
* styles, cardinality) out of RenderEngine itself, queried through the
|
|
6
|
-
* `DataModelNotation` registry rather than flat string-keyed maps.
|
|
7
|
-
*
|
|
8
|
-
* **Three levels, one entity.** A conceptual/logical/physical data model
|
|
9
|
-
* isn't three unrelated diagrams — it's the same entities and relationships
|
|
10
|
-
* shown at increasing fidelity. `dataModelDiagram` (see RenderEngine)
|
|
11
|
-
* expresses that as ONE entity object carrying per-level detail
|
|
12
|
-
* (`entity.logical.attributes`, `entity.physical.attributes`, ...) under a
|
|
13
|
-
* shared `name`/`description` — the same "object" (level-agnostic identity)
|
|
14
|
-
* vs "visual/detail" (this level's own presentation) split archimateDiagram
|
|
15
|
-
* already draws between an element and its diagram — with `options.level`
|
|
16
|
-
* picking which one's on screen. That's the cross-mapping: switching level
|
|
17
|
-
* re-renders the SAME entities/relationships, not a second, separately
|
|
18
|
-
* maintained diagram, so a conceptual entity, its logical attributes, and
|
|
19
|
-
* its physical column types can never drift out of correspondence.
|
|
20
|
-
*
|
|
21
|
-
* **Three notations, one cardinality vocabulary.** A relationship's
|
|
22
|
-
* cardinality at each end is stored once, as a UML-style multiplicity
|
|
23
|
-
* string (`'0..1'|'1'|'0..*'|'*'|'1..*'`) — CARDINALITIES below — and each
|
|
24
|
-
* `NotationStyle` renders it its own way: Chen turns the relationship
|
|
25
|
-
* itself into a diamond-shaped node with a plain text label ('1'/'N') on
|
|
26
|
-
* each connecting edge; Crow's Foot renders it as an arrowhead-style glyph
|
|
27
|
-
* at the entity end (fork for "many", tick for "one", circle for
|
|
28
|
-
* "optional" — see the `crowsfoot-*` markers in SVGRenderer.js); UML shows
|
|
29
|
-
* the multiplicity string itself as a text label near that end
|
|
30
|
-
* (`edge.sourceLabel`/`targetLabel`, a plain graphDiagram feature, not
|
|
31
|
-
* data-model-specific). None of these are pixel-perfect renditions of the
|
|
32
|
-
* real notations' full rule set (Chen's own attribute-oval convention,
|
|
33
|
-
* every crow's-foot optionality combination, UML's aggregation/composition
|
|
34
|
-
* diamonds) — they're a representative, genuinely-readable version of each,
|
|
35
|
-
* the same calibration archimateDiagram's own icons use.
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/** One data-modelling abstraction level. See DataModelNotation.levels(). */
|
|
39
|
-
export class DataModelLevel {
|
|
40
|
-
constructor(name, description) {
|
|
41
|
-
this.name = name;
|
|
42
|
-
this.description = description;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'0..1': '
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
1
|
+
// Copyright 2026 Jaroslav Peter Prib, Purveyor of Excellence
|
|
2
|
+
/**
|
|
3
|
+
* Data-modelling notation — deliberately its own file/OO module, same split
|
|
4
|
+
* as src/archimate.js: keeps this domain's vocabulary (levels, notation
|
|
5
|
+
* styles, cardinality) out of RenderEngine itself, queried through the
|
|
6
|
+
* `DataModelNotation` registry rather than flat string-keyed maps.
|
|
7
|
+
*
|
|
8
|
+
* **Three levels, one entity.** A conceptual/logical/physical data model
|
|
9
|
+
* isn't three unrelated diagrams — it's the same entities and relationships
|
|
10
|
+
* shown at increasing fidelity. `dataModelDiagram` (see RenderEngine)
|
|
11
|
+
* expresses that as ONE entity object carrying per-level detail
|
|
12
|
+
* (`entity.logical.attributes`, `entity.physical.attributes`, ...) under a
|
|
13
|
+
* shared `name`/`description` — the same "object" (level-agnostic identity)
|
|
14
|
+
* vs "visual/detail" (this level's own presentation) split archimateDiagram
|
|
15
|
+
* already draws between an element and its diagram — with `options.level`
|
|
16
|
+
* picking which one's on screen. That's the cross-mapping: switching level
|
|
17
|
+
* re-renders the SAME entities/relationships, not a second, separately
|
|
18
|
+
* maintained diagram, so a conceptual entity, its logical attributes, and
|
|
19
|
+
* its physical column types can never drift out of correspondence.
|
|
20
|
+
*
|
|
21
|
+
* **Three notations, one cardinality vocabulary.** A relationship's
|
|
22
|
+
* cardinality at each end is stored once, as a UML-style multiplicity
|
|
23
|
+
* string (`'0..1'|'1'|'0..*'|'*'|'1..*'`) — CARDINALITIES below — and each
|
|
24
|
+
* `NotationStyle` renders it its own way: Chen turns the relationship
|
|
25
|
+
* itself into a diamond-shaped node with a plain text label ('1'/'N') on
|
|
26
|
+
* each connecting edge; Crow's Foot renders it as an arrowhead-style glyph
|
|
27
|
+
* at the entity end (fork for "many", tick for "one", circle for
|
|
28
|
+
* "optional" — see the `crowsfoot-*` markers in SVGRenderer.js); UML shows
|
|
29
|
+
* the multiplicity string itself as a text label near that end
|
|
30
|
+
* (`edge.sourceLabel`/`targetLabel`, a plain graphDiagram feature, not
|
|
31
|
+
* data-model-specific). None of these are pixel-perfect renditions of the
|
|
32
|
+
* real notations' full rule set (Chen's own attribute-oval convention,
|
|
33
|
+
* every crow's-foot optionality combination, UML's aggregation/composition
|
|
34
|
+
* diamonds) — they're a representative, genuinely-readable version of each,
|
|
35
|
+
* the same calibration archimateDiagram's own icons use.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** One data-modelling abstraction level. See DataModelNotation.levels(). */
|
|
39
|
+
export class DataModelLevel {
|
|
40
|
+
constructor(name, description) {
|
|
41
|
+
this.name = name;
|
|
42
|
+
this.description = description;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** What a menu calls it ('Conceptual'). */
|
|
46
|
+
get label() {
|
|
47
|
+
return this.name.charAt(0).toUpperCase() + this.name.slice(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The canonical cardinality vocabulary every NotationStyle renders
|
|
52
|
+
* differently — UML-style multiplicity strings, since they're the only one
|
|
53
|
+
* of the three notations expressive enough to BE the canonical form
|
|
54
|
+
* without losing information (Chen/Crow's Foot each collapse some of these
|
|
55
|
+
* distinctions on purpose). */
|
|
56
|
+
const CARDINALITIES = new Set(['0..1', '1', '0..*', '*', '1..*']);
|
|
57
|
+
|
|
58
|
+
function chenCardinalityLabel(cardinality) {
|
|
59
|
+
return cardinality === '1' || cardinality === '0..1' ? '1' : 'N';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const CROWSFOOT_ARROWHEADS = {
|
|
63
|
+
'1': 'crowsfoot-one',
|
|
64
|
+
'0..1': 'crowsfoot-one-optional',
|
|
65
|
+
'1..*': 'crowsfoot-many-mandatory',
|
|
66
|
+
'0..*': 'crowsfoot-many',
|
|
67
|
+
'*': 'crowsfoot-many',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* One drawing technique for entities/relationships — Chen, Crow's Foot, or
|
|
72
|
+
* UML (class-diagram style). `entityShape` picks the graphDiagram node shape
|
|
73
|
+
* (`'rect'` for a plain box, `'table'` for a header + attribute-rows card);
|
|
74
|
+
* `relationshipAsNode` (Chen only) turns a relationship into its own
|
|
75
|
+
* diamond-shaped node with two plain edges instead of one direct edge;
|
|
76
|
+
* `cardinalityArrowhead`/`cardinalityLabel` (each `(cardinality) => value or
|
|
77
|
+
* undefined`) pick how a relationship's own cardinality at one end renders
|
|
78
|
+
* — at most one of the two is ever set for a given notation, the other
|
|
79
|
+
* always returns undefined.
|
|
80
|
+
*/
|
|
81
|
+
export class NotationStyle {
|
|
82
|
+
constructor(name, { label = name, entityShape, relationshipAsNode, cardinalityArrowhead, cardinalityLabel }) {
|
|
83
|
+
this.name = name;
|
|
84
|
+
/** What a menu calls it ("Crow's Foot"). */
|
|
85
|
+
this.label = label;
|
|
86
|
+
this.entityShape = entityShape;
|
|
87
|
+
this.relationshipAsNode = relationshipAsNode;
|
|
88
|
+
this.cardinalityArrowhead = cardinalityArrowhead;
|
|
89
|
+
this.cardinalityLabel = cardinalityLabel;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const LEVELS = {
|
|
94
|
+
conceptual: new DataModelLevel('conceptual', 'Entities and their relationships only — no attributes, no keys, no types.'),
|
|
95
|
+
logical: new DataModelLevel('logical', 'Attributes and keys, still database-agnostic — no column types or engine specifics.'),
|
|
96
|
+
physical: new DataModelLevel('physical', 'Real column types, keys, and constraints as they exist in an actual database.'),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const NOTATIONS = {
|
|
100
|
+
chen: new NotationStyle('chen', {
|
|
101
|
+
label: 'Chen',
|
|
102
|
+
entityShape: 'rect',
|
|
103
|
+
relationshipAsNode: true,
|
|
104
|
+
cardinalityArrowhead: () => undefined,
|
|
105
|
+
cardinalityLabel: chenCardinalityLabel,
|
|
106
|
+
}),
|
|
107
|
+
crowsfoot: new NotationStyle('crowsfoot', {
|
|
108
|
+
label: "Crow's Foot",
|
|
109
|
+
entityShape: 'table',
|
|
110
|
+
relationshipAsNode: false,
|
|
111
|
+
cardinalityArrowhead: (cardinality) => CROWSFOOT_ARROWHEADS[cardinality],
|
|
112
|
+
cardinalityLabel: () => undefined,
|
|
113
|
+
}),
|
|
114
|
+
uml: new NotationStyle('uml', {
|
|
115
|
+
label: 'UML',
|
|
116
|
+
entityShape: 'table',
|
|
117
|
+
relationshipAsNode: false,
|
|
118
|
+
cardinalityArrowhead: () => undefined,
|
|
119
|
+
cardinalityLabel: (cardinality) => cardinality,
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const DataModelNotation = {
|
|
124
|
+
levels() { return Object.values(LEVELS); },
|
|
125
|
+
levelNames() { return Object.keys(LEVELS); },
|
|
126
|
+
level(name) { return LEVELS[name]; },
|
|
127
|
+
notations() { return Object.values(NOTATIONS); },
|
|
128
|
+
notationNames() { return Object.keys(NOTATIONS); },
|
|
129
|
+
notation(name) { return NOTATIONS[name] || NOTATIONS.crowsfoot; },
|
|
130
|
+
isCardinality(value) { return CARDINALITIES.has(value); },
|
|
131
|
+
cardinalities() { return [...CARDINALITIES]; },
|
|
132
|
+
};
|
package/src/diagramCatalog.js
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Single source of truth for "what diagram types does RenderEngine support".
|
|
3
|
-
*
|
|
4
|
-
* Mirrors src/chartCatalog.js: the registry is the object-oriented lookup
|
|
5
|
-
* layer over the flat catalog, and `getDiagramCatalogEntry` is the legacy-style
|
|
6
|
-
* helper kept for backward compatibility.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { DiagramRegistry } from './charting/DiagramRegistry.js';
|
|
10
|
-
import { GraphDiagram } from './charting/diagrams/GraphDiagram.js';
|
|
11
|
-
import { GraphDiagram3D } from './charting/diagrams/GraphDiagram3D.js';
|
|
12
|
-
import { ArchimateDiagram } from './charting/diagrams/ArchimateDiagram.js';
|
|
13
|
-
import { DataModelDiagram } from './charting/diagrams/DataModelDiagram.js';
|
|
14
|
-
import { EbnfDiagram } from './charting/diagrams/EbnfDiagram.js';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for "what diagram types does RenderEngine support".
|
|
3
|
+
*
|
|
4
|
+
* Mirrors src/chartCatalog.js: the registry is the object-oriented lookup
|
|
5
|
+
* layer over the flat catalog, and `getDiagramCatalogEntry` is the legacy-style
|
|
6
|
+
* helper kept for backward compatibility.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { DiagramRegistry } from './charting/DiagramRegistry.js';
|
|
10
|
+
import { GraphDiagram } from './charting/diagrams/GraphDiagram.js';
|
|
11
|
+
import { GraphDiagram3D } from './charting/diagrams/GraphDiagram3D.js';
|
|
12
|
+
import { ArchimateDiagram } from './charting/diagrams/ArchimateDiagram.js';
|
|
13
|
+
import { DataModelDiagram } from './charting/diagrams/DataModelDiagram.js';
|
|
14
|
+
import { EbnfDiagram } from './charting/diagrams/EbnfDiagram.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Each entry: `key`, the RenderEngine `method` that draws it, its `DiagramClass`,
|
|
18
|
+
* and `args`: the options a registered diagram (RenderEngine#register) keeps its
|
|
19
|
+
* data in, in the order the method takes them before its options.
|
|
20
|
+
*/
|
|
21
|
+
export const DIAGRAM_CATALOG = [
|
|
22
|
+
{ key: 'graph', method: 'graphDiagram', DiagramClass: GraphDiagram, args: ['nodes', 'edges'] },
|
|
23
|
+
{ key: 'graph3d', method: 'graphDiagram3D', DiagramClass: GraphDiagram3D, args: ['nodes', 'edges'] },
|
|
24
|
+
{ key: 'archimate', method: 'archimateDiagram', DiagramClass: ArchimateDiagram, args: ['elements', 'relationships'] },
|
|
25
|
+
{ key: 'dataModel', method: 'dataModelDiagram', DiagramClass: DataModelDiagram, args: ['entities', 'relationships'] },
|
|
26
|
+
{ key: 'ebnf', method: 'ebnfDiagram', DiagramClass: EbnfDiagram, args: ['rule'] },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/** Canonical registry instance seeded with the built-in diagram types. */
|
|
30
|
+
export const diagramRegistry = new DiagramRegistry(DIAGRAM_CATALOG);
|
|
31
|
+
|
|
32
|
+
/** Look up one catalog entry by key, or undefined if the key is unknown. */
|
|
33
|
+
export function getDiagramCatalogEntry(key) {
|
|
34
|
+
return diagramRegistry.get(key);
|
|
35
|
+
}
|
package/src/index.js
CHANGED
|
@@ -165,6 +165,7 @@ import { buildAnalysisPanel, buildAnalysisData } from './charting/modelTree/Anal
|
|
|
165
165
|
import { buildTypeCategoryTree, buildPropertyTree } from './charting/modelTree/buildFilterTrees.js';
|
|
166
166
|
import { DiagramTabs } from './charting/modelTree/DiagramTabs.js';
|
|
167
167
|
import { DataModelNotation, DataModelLevel, NotationStyle } from './datamodel.js';
|
|
168
|
+
import { DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping } from './charting/datamodel/index.js';
|
|
168
169
|
import {
|
|
169
170
|
ARCHI_VISUAL_OBJECTS, ARCHI_VIEW_TYPES,
|
|
170
171
|
parseXML, parseArchiModel, toRenderEngineModel, toArchimateModel, toArchiXML, fromArchimateView, toArchiExport,
|
|
@@ -221,6 +222,7 @@ export {
|
|
|
221
222
|
RadialLayout, DerivedRelationships, EdgeGeometry,
|
|
222
223
|
buildModelTreePanel, buildAnalysisPanel, buildAnalysisData, buildTypeCategoryTree, buildPropertyTree, DiagramTabs,
|
|
223
224
|
DataModelNotation, DataModelLevel, NotationStyle,
|
|
225
|
+
DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping,
|
|
224
226
|
exportElementsCSV, exportRelationshipsCSV, exportModelCSV,
|
|
225
227
|
importElementsCSV, importRelationshipsCSV, importModelCSV,
|
|
226
228
|
parseArchiModel, toArchimateModel, fromArchimateView, toArchiExport,
|
|
@@ -2594,7 +2596,10 @@ export class RenderEngine {
|
|
|
2594
2596
|
const methodName = chartEntry?.method ?? diagramEntry?.method ?? config.type;
|
|
2595
2597
|
if (typeof this[methodName] !== 'function') return;
|
|
2596
2598
|
|
|
2597
|
-
if (
|
|
2599
|
+
if (diagramEntry?.args) {
|
|
2600
|
+
// A diagram takes its data as positional args (its catalog entry names them), then the options.
|
|
2601
|
+
this[methodName](...diagramEntry.args.map(arg => config.options[arg] ?? []), config.options);
|
|
2602
|
+
} else if (config.options.nodes !== undefined) {
|
|
2598
2603
|
// Graph-shaped registration: two positional data args, not one.
|
|
2599
2604
|
this[methodName](config.options.nodes, config.options.edges || [], config.options);
|
|
2600
2605
|
} else {
|
package/src/workspace/index.js
CHANGED
|
@@ -56,6 +56,7 @@ export {
|
|
|
56
56
|
LIGHT_THEME, DARK_THEME, MIDNIGHT_THEME, SEPIA_THEME,
|
|
57
57
|
} from './themes.js';
|
|
58
58
|
export { ChartPart } from './parts/ChartPart.js';
|
|
59
|
+
export { DataModelPart } from './parts/DataModelPart.js';
|
|
59
60
|
export { EditorPart } from './parts/EditorPart.js';
|
|
60
61
|
export { DiagramEditorPart } from './parts/DiagramEditorPart.js';
|
|
61
62
|
export { ArchimateEditorPart } from './parts/ArchimateEditorPart.js';
|
|
@@ -55,6 +55,8 @@ export class ArchimateCanvasPart extends EditorPart {
|
|
|
55
55
|
this.viewpoint = spec.viewpoint ?? 'All';
|
|
56
56
|
this.viewpointMode = spec.viewpointMode ?? 'fade';
|
|
57
57
|
this.showDerived = !!spec.showDerived;
|
|
58
|
+
/** Whether a line to a box nested in its source is drawn (Archi leaves it out). */
|
|
59
|
+
this.showNestedLines = !!spec.showNestedLines;
|
|
58
60
|
this.settings = new SettingsManager();
|
|
59
61
|
// A settings change (Model settings…, a view's properties) redraws the canvas.
|
|
60
62
|
this._unsubscribeSettings = this.settings.onSettingsChange(() => { if (this.mounted) this.editor?.render(); });
|
|
@@ -224,7 +226,7 @@ export class ArchimateCanvasPart extends EditorPart {
|
|
|
224
226
|
diagramContainerId: canvasId,
|
|
225
227
|
view,
|
|
226
228
|
model,
|
|
227
|
-
nodeOptions: { archimateVersion: this.version, useDistinctShapes: this.distinctShapes, viewpoint: this.viewpoint, viewpointMode: this.viewpointMode, showDerived: this.showDerived },
|
|
229
|
+
nodeOptions: { archimateVersion: this.version, useDistinctShapes: this.distinctShapes, viewpoint: this.viewpoint, viewpointMode: this.viewpointMode, showDerived: this.showDerived, showNestedLines: this.showNestedLines },
|
|
228
230
|
chooseKind: (kinds, from, to, options) => this.chooseKind(kinds, from, to, options),
|
|
229
231
|
// The view's settings: background, title, watermark, how invalid relationships show.
|
|
230
232
|
viewSettings: () => this.settings.getEffectiveSettings(this.model.currentView, 'default'),
|
|
@@ -234,8 +236,9 @@ export class ArchimateCanvasPart extends EditorPart {
|
|
|
234
236
|
// Canvas templates: placed on a canvas, saved from its shapes (the browser's library).
|
|
235
237
|
templateMenu: this.templateMenu,
|
|
236
238
|
// Version and distinct shapes (⚙ Settings) change the palette too.
|
|
237
|
-
onNodeOptionsChange: ({ archimateVersion, useDistinctShapes, viewpoint, viewpointMode, showDerived }) => {
|
|
239
|
+
onNodeOptionsChange: ({ archimateVersion, useDistinctShapes, viewpoint, viewpointMode, showDerived, showNestedLines }) => {
|
|
238
240
|
this.showDerived = !!showDerived;
|
|
241
|
+
this.showNestedLines = !!showNestedLines;
|
|
239
242
|
this.version = archimateVersion;
|
|
240
243
|
this.distinctShapes = !!useDistinctShapes;
|
|
241
244
|
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.
|
|
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);
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { ChartPart } from './ChartPart.js';
|
|
2
|
+
import { el } from '../dom.js';
|
|
3
|
+
import { MenuBar } from '../../charting/MenuBar.js';
|
|
4
|
+
import { ToolbarSelect, ToolbarMenu, ToolbarText } from '../../charting/toolbar/index.js';
|
|
5
|
+
import { FormDialog } from '../../charting/FormDialog.js';
|
|
6
|
+
import { DataModelNotation } from '../../datamodel.js';
|
|
7
|
+
import { DataModel, DataModelPerspective, DataModelContext } from '../../charting/datamodel/index.js';
|
|
8
|
+
import { downloadPng, downloadSvg } from '../../exportImage.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DataModelPart — a data model (DataModel, drawn by dataModelDiagram) in a
|
|
12
|
+
* pane: its conceptual, logical and physical levels, each its own schema, and
|
|
13
|
+
* how they map; seen through a perspective (whose concepts, under whose names)
|
|
14
|
+
* read in a context (what they mean here, the rules that hold, where and
|
|
15
|
+
* when), in a notation (Chen, Crow's Foot, UML). Its toolbar switches each
|
|
16
|
+
* of them, Level offering Mapping (the levels side by side, joined where they
|
|
17
|
+
* map), and its Model menu makes, changes and removes perspectives and
|
|
18
|
+
* contexts. Boxes and notes stay where they were dragged. Export ▸ saves the
|
|
19
|
+
* picture (PNG, SVG). The drawing, its sizing and redrawing on resize are
|
|
20
|
+
* ChartPart's.
|
|
21
|
+
*
|
|
22
|
+
* Spec: `{ id, type: 'data-model', title?, levels?, mappings?, mappingLayout?,
|
|
23
|
+
* perspectives?, contexts?, perspective?, context?, level?, notation?, options? }`
|
|
24
|
+
* — the model as DataModel takes it (or `entities`/`relationships` in the older
|
|
25
|
+
* one-entity form); `perspective`/`context` the ids chosen; `level` default
|
|
26
|
+
* 'logical' ('mapping' for the mapping view), `notation` default 'crowsfoot';
|
|
27
|
+
* `options` anything else dataModelDiagram takes (editable, resizable by default).
|
|
28
|
+
*/
|
|
29
|
+
export class DataModelPart extends ChartPart {
|
|
30
|
+
static type = 'data-model';
|
|
31
|
+
|
|
32
|
+
constructor(spec = {}) {
|
|
33
|
+
const { perspective = null, context = null, level = 'logical', notation = 'crowsfoot', options = {} } = spec;
|
|
34
|
+
super({ ...spec, chartType: 'dataModel', options: { editable: true, resizable: true, ...options, level, notation } });
|
|
35
|
+
/** The levels, their mappings, perspectives and contexts. */
|
|
36
|
+
this.model = new DataModel(spec);
|
|
37
|
+
/** The ids chosen, or null. */
|
|
38
|
+
this.perspectiveId = perspective;
|
|
39
|
+
this.contextId = context ?? this.model.perspective(perspective)?.context ?? null;
|
|
40
|
+
/** The level the drawing on screen is of (where a drag is kept). */
|
|
41
|
+
this._drawnLevel = level;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The entities at the level shown (the concepts in the mapping view). */
|
|
45
|
+
get entities() {
|
|
46
|
+
return this.model.level(this.level === DataModel.MAPPING ? 'conceptual' : this.level).entities;
|
|
47
|
+
}
|
|
48
|
+
get level() { return this.chartOptions.level; }
|
|
49
|
+
get notation() { return this.chartOptions.notation; }
|
|
50
|
+
/** The perspective chosen (DataModelPerspective), or null. */
|
|
51
|
+
get perspective() { return this.model.perspective(this.perspectiveId); }
|
|
52
|
+
/** The context chosen (DataModelContext), or null. */
|
|
53
|
+
get context() { return this.model.context(this.contextId); }
|
|
54
|
+
|
|
55
|
+
/** Show another level ('conceptual', 'logical', 'physical'), or how they map ('mapping'). */
|
|
56
|
+
setLevel(level) {
|
|
57
|
+
this.chartOptions.level = level;
|
|
58
|
+
this._redraw();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Draw the model in another notation ('chen', 'crowsfoot', 'uml'). */
|
|
62
|
+
setNotation(notation) {
|
|
63
|
+
this.chartOptions.notation = notation;
|
|
64
|
+
this._redraw();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** See the model through a perspective (an id; null: the whole schema), in the context it is read in, if it names one. */
|
|
68
|
+
setPerspective(id) {
|
|
69
|
+
this.perspectiveId = id || null;
|
|
70
|
+
const own = this.perspective?.context;
|
|
71
|
+
if (own) this.contextId = own;
|
|
72
|
+
this._redraw();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Read the model in a context (an id; null: the schema's own descriptions). */
|
|
76
|
+
setContext(id) {
|
|
77
|
+
this.contextId = id || null;
|
|
78
|
+
this._redraw();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** What is drawn: the perspective's view of the level (or the mapping) in the context (DataModel#view), in the notation. */
|
|
82
|
+
drawOptions() {
|
|
83
|
+
return { ...this.chartOptions, ...this.model.view({ perspective: this.perspectiveId, context: this.contextId, level: this.level }) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
render() {
|
|
87
|
+
Object.assign(this.container.style, { display: 'flex', flexDirection: 'column', height: '100%' });
|
|
88
|
+
const toolbar = this.container.appendChild(el({}));
|
|
89
|
+
super.render();
|
|
90
|
+
Object.assign(this.canvasHost.style, { flex: '1 1 auto', minHeight: '0', height: 'auto' });
|
|
91
|
+
this.menuBar = new MenuBar({ container: toolbar, items: this.menuBarItems(), document: this.container.ownerDocument }).render();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Model ▸ (perspectives and contexts of one's own), Perspective and Context
|
|
96
|
+
* (when the model has any; the context's scope beside them), Level (each
|
|
97
|
+
* level from DataModelNotation, then Mapping) and Notation, and Export ▸.
|
|
98
|
+
*/
|
|
99
|
+
menuBarItems() {
|
|
100
|
+
const { perspectives, contexts } = this.model;
|
|
101
|
+
return [
|
|
102
|
+
new ToolbarMenu('Model', () => this.modelItems()),
|
|
103
|
+
...(perspectives.length ? [new ToolbarSelect('Perspective',
|
|
104
|
+
() => [['', 'Whole schema'], ...this.model.perspectives.map(p => [p.id, p.name, p.description])],
|
|
105
|
+
() => this.perspectiveId ?? '', (v) => this.setPerspective(v))] : []),
|
|
106
|
+
...(contexts.length ? [
|
|
107
|
+
new ToolbarSelect('Context',
|
|
108
|
+
() => [['', 'None'], ...this.model.contexts.map(c => [c.id, c.name, c.description])],
|
|
109
|
+
() => this.contextId ?? '', (v) => this.setContext(v)),
|
|
110
|
+
new ToolbarText(() => this.context?.scope ?? ''),
|
|
111
|
+
] : []),
|
|
112
|
+
new ToolbarSelect('Level', () => [
|
|
113
|
+
...DataModelNotation.levels().map(l => [l.name, l.label, l.description]),
|
|
114
|
+
[DataModel.MAPPING, 'Mapping', 'The levels side by side, joined where one realises another'],
|
|
115
|
+
], () => this.level, (v) => this.setLevel(v)),
|
|
116
|
+
new ToolbarSelect('Notation', () => DataModelNotation.notations().map(n => [n.name, n.label]), () => this.notation, (v) => this.setNotation(v)),
|
|
117
|
+
new ToolbarMenu('Export', () => this.exportItems()),
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Model ▸: make, change or remove a perspective or a context (the one chosen). */
|
|
122
|
+
modelItems() {
|
|
123
|
+
return [
|
|
124
|
+
{ label: 'New perspective…', action: () => this.editPerspective(null) },
|
|
125
|
+
{ label: 'Edit perspective…', disabled: !this.perspective || undefined, action: () => this.editPerspective(this.perspective) },
|
|
126
|
+
{ label: 'Delete perspective', disabled: !this.perspective || undefined, action: () => this.removePerspective(this.perspectiveId) },
|
|
127
|
+
{ separator: true },
|
|
128
|
+
{ label: 'New context…', action: () => this.editContext(null) },
|
|
129
|
+
{ label: 'Edit context…', disabled: !this.context || undefined, action: () => this.editContext(this.context) },
|
|
130
|
+
{ label: 'Delete context', disabled: !this.context || undefined, action: () => this.removeContext(this.contextId) },
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A dialog making a perspective (none given) or changing one (DataModelPerspective.fields); applied, it is the one seen through. */
|
|
135
|
+
editPerspective(perspective) {
|
|
136
|
+
return this._dialog(perspective ? `Perspective: ${perspective.name}` : 'New perspective', DataModelPerspective.fields(this.model, perspective), (values) => {
|
|
137
|
+
const spec = DataModelPerspective.specFrom(values, this.model);
|
|
138
|
+
const saved = perspective ? this.model.updatePerspective(perspective.id, spec) : this.model.addPerspective(spec);
|
|
139
|
+
this._changed();
|
|
140
|
+
this.setPerspective(saved.id);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A dialog making a context (none given) or changing one (DataModelContext.fields); applied, it is the one read in. */
|
|
145
|
+
editContext(context) {
|
|
146
|
+
return this._dialog(context ? `Context: ${context.name}` : 'New context', DataModelContext.fields(this.model, context), (values) => {
|
|
147
|
+
const spec = DataModelContext.specFrom(values, this.model, context);
|
|
148
|
+
const saved = context ? this.model.updateContext(context.id, spec) : this.model.addContext(spec);
|
|
149
|
+
this._changed();
|
|
150
|
+
this.setContext(saved.id);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
removePerspective(id) {
|
|
155
|
+
this.model.removePerspective(id);
|
|
156
|
+
this._changed();
|
|
157
|
+
this.setPerspective(null);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
removeContext(id) {
|
|
161
|
+
this.model.removeContext(id);
|
|
162
|
+
this._changed();
|
|
163
|
+
this.setContext(null);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Open a form (a name is needed). @private */
|
|
167
|
+
_dialog(title, fields, apply) {
|
|
168
|
+
return new FormDialog({
|
|
169
|
+
title, fields, submitLabel: 'Save', idPrefix: 'data-model-',
|
|
170
|
+
document: this.container?.ownerDocument ?? globalThis.document,
|
|
171
|
+
validate: ({ name }) => ((name ?? '').trim() ? null : 'Give it a name'),
|
|
172
|
+
onApply: apply,
|
|
173
|
+
}).open();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The perspectives or contexts changed: the toolbar shows them anew. @private */
|
|
177
|
+
_changed() {
|
|
178
|
+
if (this.menuBar) this.menuBar.items = this.menuBarItems();
|
|
179
|
+
this.menuBar?.render?.();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Export ▸: the picture, as PNG or SVG, named after the part. */
|
|
183
|
+
exportItems() {
|
|
184
|
+
const name = this.title || 'data-model';
|
|
185
|
+
return [
|
|
186
|
+
{ label: 'Image (PNG)', action: () => downloadPng(this.canvasHost, { name }) },
|
|
187
|
+
{ label: 'Image (SVG)', action: () => downloadSvg(this.canvasHost, { name }) },
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Redraw now (a choice made), when there is somewhere to draw. @private */
|
|
192
|
+
_redraw() {
|
|
193
|
+
if (this.canvasHost) this._draw();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Keep where the boxes and notes were dragged (the drawing is made anew each
|
|
198
|
+
* time): on the level shown, or in the mapping view's own layout. Then draw.
|
|
199
|
+
*/
|
|
200
|
+
_draw() {
|
|
201
|
+
const pos = this.engine?._graph?.pos;
|
|
202
|
+
if (pos && this._drawnLevel) {
|
|
203
|
+
if (this._drawnLevel === DataModel.MAPPING) {
|
|
204
|
+
for (const [id, p] of pos) if (id.includes(':')) this.model.mappingLayout[id] = { x: p.x, y: p.y };
|
|
205
|
+
} else {
|
|
206
|
+
for (const entity of this.model.level(this._drawnLevel).entities) {
|
|
207
|
+
const p = pos.get(entity.id);
|
|
208
|
+
if (p) { entity.x = p.x; entity.y = p.y; }
|
|
209
|
+
}
|
|
210
|
+
for (const rule of this.model.contexts.flatMap(c => c.constraints)) {
|
|
211
|
+
const p = pos.get(rule.id);
|
|
212
|
+
if (p) this.model.placeNote(this._drawnLevel, rule, p);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this._drawnLevel = this.level;
|
|
217
|
+
super._draw();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
dispose() {
|
|
221
|
+
this.menuBar?.dispose?.();
|
|
222
|
+
super.dispose();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** The spec again: the model (with where its boxes are), what is chosen, and the other options. */
|
|
226
|
+
serialize() {
|
|
227
|
+
const { options, chartType, ...base } = super.serialize();
|
|
228
|
+
const { level, notation, ...rest } = options;
|
|
229
|
+
return {
|
|
230
|
+
...base, ...this.model.toJSON(),
|
|
231
|
+
perspective: this.perspectiveId, context: this.contextId, level, notation, options: rest,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|