@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,86 +1,160 @@
1
- import { Diagram } from '../Diagram.js';
2
- import { DataModelNotation } from '../../datamodel.js';
3
- import { RelationshipAsEdgeStrategy, RelationshipAsNodeStrategy } from './DataModelRelationshipStrategy.js';
4
-
5
- /**
6
- * DataModelDiagram — builds entity-relationship nodes/edges and renders them
7
- * through the shared GraphDiagram engine.
8
- */
9
- export class DataModelDiagram extends Diagram {
10
- static diagramType = 'dataModel';
11
-
12
- constructor(renderer, entities, relationships, options = {}, engine = null) {
13
- super(renderer, { entities, relationships }, options);
14
- this.engine = engine;
15
- }
16
-
17
- static _strategyFor(notation) {
18
- return notation.relationshipAsNode
19
- ? new RelationshipAsNodeStrategy()
20
- : new RelationshipAsEdgeStrategy();
21
- }
22
-
23
- render() {
24
- const { entities, relationships } = this.data;
25
- const options = this.options;
26
- const { level = 'logical', notation: notationName = 'crowsfoot', ...rest } = options;
27
- const notation = DataModelNotation.notation(notationName);
28
- const nodes = entities.map(entity => DataModelDiagram.buildNode(entity, { level, notation }));
29
- const strategy = DataModelDiagram._strategyFor(notation);
30
- const { nodes: relNodes, edges } = strategy.apply(relationships, {
31
- buildEdge: DataModelDiagram.buildEdge,
32
- notation,
33
- });
34
- nodes.push(...relNodes);
35
- this.engine.graphDiagram(nodes, edges, { edgeColor: '#666666', ...rest });
36
- }
37
-
38
- static typeGroup(type) {
39
- if (!type) return 'UNKNOWN';
40
- const t = String(type).toUpperCase();
41
- if (/^(INT|INTEGER|BIGINT|SMALLINT|TINYINT|DECIMAL|NUMERIC|FLOAT|DOUBLE|REAL|NUMBER|SERIAL)/.test(t)) return 'NUMBER';
42
- if (/^(VARCHAR|CHAR|TEXT|STRING|CLOB|UUID)/.test(t)) return 'TEXT';
43
- if (/^(TIMESTAMP|DATE|DATETIME|TIME)/.test(t)) return 'TIMESTAMP';
44
- if (/^(BOOL|BOOLEAN)/.test(t)) return 'BOOLEAN';
45
- if (/^(JSON|JSONB)/.test(t)) return 'JSON';
46
- return 'UNKNOWN';
47
- }
48
-
49
- static buildNode(entity, { level, notation }) {
50
- const levelData = entity[level] || {};
51
- const attributes = levelData.attributes;
52
- if (notation.entityShape === 'table' && attributes?.length) {
53
- return {
54
- id: entity.id,
55
- label: levelData.tableName || entity.name,
56
- shape: 'table',
57
- x: entity.x,
58
- y: entity.y,
59
- color: entity.color,
60
- columns: attributes.map(a => ({ name: a.name, typeGroup: DataModelDiagram.typeGroup(a.type), isPK: !!a.isPK, isFK: !!a.isFK })),
61
- };
62
- }
63
- return {
64
- id: entity.id,
65
- label: entity.name,
66
- shape: entity.shape || 'rect',
67
- x: entity.x,
68
- y: entity.y,
69
- color: entity.color,
70
- description: entity.description,
71
- labelPosition: entity.labelPosition || 'inside',
72
- };
73
- }
74
-
75
- static buildEdge(relationship, notation) {
76
- return {
77
- ...relationship,
78
- label: relationship.name,
79
- lineStyle: relationship.identifying === false ? 'dashed' : 'solid',
80
- arrowheadStart: notation.cardinalityArrowhead(relationship.sourceCardinality) || 'none',
81
- arrowhead: notation.cardinalityArrowhead(relationship.targetCardinality) || 'none',
82
- sourceLabel: notation.cardinalityLabel(relationship.sourceCardinality),
83
- targetLabel: notation.cardinalityLabel(relationship.targetCardinality),
84
- };
85
- }
86
- }
1
+ import { Diagram } from '../Diagram.js';
2
+ import { DataModelNotation } from '../../datamodel.js';
3
+ import { RelationshipAsEdgeStrategy, RelationshipAsNodeStrategy } from './DataModelRelationshipStrategy.js';
4
+ import { NodeGeometry } from './NodeGeometry.js';
5
+
6
+ /**
7
+ * DataModelDiagram — builds entity-relationship nodes/edges and renders them
8
+ * through the shared GraphDiagram engine.
9
+ */
10
+ export class DataModelDiagram extends Diagram {
11
+ static diagramType = 'dataModel';
12
+
13
+ /** The size of an entity drawn as a box with its name inside (an entity's own width/height win). */
14
+ static ENTITY_BOX = Object.freeze({ width: 140, height: 56 });
15
+
16
+ /** The size of an annotation's note. */
17
+ static NOTE = Object.freeze({ width: 180, height: 64 });
18
+
19
+ /** The size of a shape with no size of its own (a person, a pin), and the room its name takes below it. */
20
+ static SHAPE_SIZE = 50;
21
+ static LABEL_BELOW = 18;
22
+
23
+ constructor(renderer, entities, relationships, options = {}, engine = null) {
24
+ super(renderer, { entities, relationships }, options);
25
+ this.engine = engine;
26
+ }
27
+
28
+ static _strategyFor(notation) {
29
+ return notation.relationshipAsNode
30
+ ? new RelationshipAsNodeStrategy()
31
+ : new RelationshipAsEdgeStrategy();
32
+ }
33
+
34
+ render() {
35
+ const { entities, relationships } = this.data;
36
+ const options = this.options;
37
+ const { level = 'logical', notation: notationName = 'crowsfoot', annotations = [], links = [], ...rest } = options;
38
+ const notation = DataModelNotation.notation(notationName);
39
+ const nodes = entities.map(entity => DataModelDiagram.buildNode(entity, { level, notation }));
40
+ const strategy = DataModelDiagram._strategyFor(notation);
41
+ const byId = new Map(entities.map(e => [e.id, e]));
42
+ const notes = annotations.map(annotation => DataModelDiagram.buildAnnotation(annotation, byId));
43
+ const { nodes: relNodes, edges } = strategy.apply(relationships, {
44
+ buildEdge: DataModelDiagram.buildEdge,
45
+ notation,
46
+ entity: (id) => byId.get(id),
47
+ occupied: [...nodes, ...notes.map(n => n.node)].map(DataModelDiagram.boxOf).filter(Boolean),
48
+ });
49
+ nodes.push(...relNodes);
50
+ for (const { node, edges: lines } of notes) {
51
+ nodes.push(node);
52
+ edges.push(...lines);
53
+ }
54
+ edges.push(...links.map(link => DataModelDiagram.buildLink(link)));
55
+ this.engine.graphDiagram(nodes, edges, { edgeColor: '#666666', ...rest });
56
+ }
57
+
58
+ /**
59
+ * Where a built node is drawn, as a box {x0, y0, x1, y1} (a table as tall
60
+ * as its columns; a shape with its name below, the name too), or null
61
+ * when it has no place yet.
62
+ */
63
+ static boxOf(node) {
64
+ if (!Number.isFinite(node?.x) || !Number.isFinite(node?.y)) return null;
65
+ const { w, h } = node.shape === 'table'
66
+ ? NodeGeometry.tableNodeSize(node)
67
+ : { w: node.width ?? DataModelDiagram.SHAPE_SIZE, h: (node.height ?? DataModelDiagram.SHAPE_SIZE) + (node.labelPosition === 'inside' ? 0 : DataModelDiagram.LABEL_BELOW) };
68
+ return { x0: node.x - w / 2, x1: node.x + w / 2, y0: node.y - h / 2, y1: node.y + h / 2 };
69
+ }
70
+
71
+ /**
72
+ * A note on the diagram (a rule, a remark) and a dotted line to each entity
73
+ * it is about, the same in every notation. Without its own place it sits
74
+ * above the first entity it is about.
75
+ * @param {{id: string, text: string, attachTo?: string[], x?: number, y?: number, color?: string}} annotation
76
+ * @param {Map<string, Object>} entities - By id (where they are)
77
+ */
78
+ static buildAnnotation(annotation, entities) {
79
+ const attachTo = annotation.attachTo ?? [];
80
+ const first = entities.get(attachTo[0]);
81
+ const place = Number.isFinite(annotation.x) ? { x: annotation.x, y: annotation.y }
82
+ : Number.isFinite(first?.x) ? { x: first.x, y: first.y - 110 } : {};
83
+ return {
84
+ node: {
85
+ id: annotation.id, label: annotation.text, shape: 'note', labelPosition: 'inside',
86
+ color: annotation.color ?? '#fff8c5', ...DataModelDiagram.NOTE, ...place,
87
+ },
88
+ edges: attachTo.filter(id => entities.has(id)).map(id => DataModelDiagram.buildLink({ source: annotation.id, target: id })),
89
+ };
90
+ }
91
+
92
+ /**
93
+ * A plain dotted line between two boxes, the same in every notation: a
94
+ * note to what it is about, or (the `links` option) a mapping between levels.
95
+ * @param {{id?: string, source: string, target: string, label?: string}} link
96
+ */
97
+ static buildLink({ id, source, target, label }) {
98
+ return { ...(id && { id }), source, target, ...(label && { label }), lineStyle: 'dotted', arrowhead: 'none', arrowheadStart: 'none' };
99
+ }
100
+
101
+ static typeGroup(type) {
102
+ if (!type) return 'UNKNOWN';
103
+ const t = String(type).toUpperCase();
104
+ if (/^(INT|INTEGER|BIGINT|SMALLINT|TINYINT|DECIMAL|NUMERIC|FLOAT|DOUBLE|REAL|NUMBER|SERIAL)/.test(t)) return 'NUMBER';
105
+ if (/^(VARCHAR|CHAR|TEXT|STRING|CLOB|UUID)/.test(t)) return 'TEXT';
106
+ if (/^(TIMESTAMP|DATE|DATETIME|TIME)/.test(t)) return 'TIMESTAMP';
107
+ if (/^(BOOL|BOOLEAN)/.test(t)) return 'BOOLEAN';
108
+ if (/^(JSON|JSONB)/.test(t)) return 'JSON';
109
+ return 'UNKNOWN';
110
+ }
111
+
112
+ /** An entity drawn at a level: its own `level` (a view of several levels) else the diagram's. */
113
+ static buildNode(entity, { level, notation }) {
114
+ const levelData = entity[entity.level ?? level] || {};
115
+ const attributes = levelData.attributes;
116
+ if (notation.entityShape === 'table' && attributes?.length) {
117
+ return {
118
+ id: entity.id,
119
+ label: levelData.tableName || entity.name,
120
+ shape: 'table',
121
+ x: entity.x,
122
+ y: entity.y,
123
+ color: entity.color,
124
+ description: entity.description,
125
+ ...entity.details,
126
+ columns: attributes.map(a => ({ name: a.name, typeGroup: DataModelDiagram.typeGroup(a.type), isPK: !!a.isPK, isFK: !!a.isFK })),
127
+ };
128
+ }
129
+ const labelPosition = entity.labelPosition || 'inside';
130
+ // A name inside the box needs room; a shape with its name beside it keeps its own size.
131
+ const box = labelPosition === 'inside' ? DataModelDiagram.ENTITY_BOX : {};
132
+ const width = entity.width ?? box.width;
133
+ const height = entity.height ?? box.height;
134
+ return {
135
+ id: entity.id,
136
+ label: entity.name,
137
+ shape: entity.shape || 'rect',
138
+ x: entity.x,
139
+ y: entity.y,
140
+ color: entity.color,
141
+ description: entity.description,
142
+ ...entity.details,
143
+ labelPosition,
144
+ ...(width !== undefined && { width }),
145
+ ...(height !== undefined && { height }),
146
+ };
147
+ }
148
+
149
+ static buildEdge(relationship, notation) {
150
+ return {
151
+ ...relationship,
152
+ label: relationship.name,
153
+ lineStyle: relationship.identifying === false ? 'dashed' : 'solid',
154
+ arrowheadStart: notation.cardinalityArrowhead(relationship.sourceCardinality) || 'none',
155
+ arrowhead: notation.cardinalityArrowhead(relationship.targetCardinality) || 'none',
156
+ sourceLabel: notation.cardinalityLabel(relationship.sourceCardinality),
157
+ targetLabel: notation.cardinalityLabel(relationship.targetCardinality),
158
+ };
159
+ }
160
+ }
@@ -1,38 +1,94 @@
1
- /**
2
- * Strategy interface for turning data-model relationships into diagram nodes
3
- * and edges. Crow's Foot and UML keep relationships as direct edges; Chen
4
- * turns each relationship into a diamond node plus two connecting edges.
5
- */
6
- export class DataModelRelationshipStrategy {
7
- /**
8
- * @param {Array<object>} relationships
9
- * @param {object} context
10
- * @param {Function} context.buildEdge
11
- * @returns {{nodes: Array<object>, edges: Array<object>}}
12
- */
13
- apply(relationships, context) {
14
- throw new Error(`${this.constructor.name} must implement apply()`);
15
- }
16
- }
17
-
18
- export class RelationshipAsEdgeStrategy extends DataModelRelationshipStrategy {
19
- apply(relationships, { buildEdge, notation }) {
20
- const edges = relationships.map(r => buildEdge(r, notation));
21
- return { nodes: [], edges };
22
- }
23
- }
24
-
25
- export class RelationshipAsNodeStrategy extends DataModelRelationshipStrategy {
26
- apply(relationships, { notation }) {
27
- const nodes = [];
28
- const edges = [];
29
- relationships.forEach((relationship, i) => {
30
- const relId = relationship.id || `${relationship.source}__${relationship.target}__rel${i}`;
31
- const lineStyle = relationship.identifying === false ? 'dashed' : 'solid';
32
- nodes.push({ id: relId, label: relationship.name || '', shape: 'diamond', color: relationship.color || '#f6c344' });
33
- edges.push({ source: relationship.source, target: relId, label: notation.cardinalityLabel(relationship.sourceCardinality), lineStyle });
34
- edges.push({ source: relId, target: relationship.target, label: notation.cardinalityLabel(relationship.targetCardinality), lineStyle });
35
- });
36
- return { nodes, edges };
37
- }
38
- }
1
+ /**
2
+ * Strategy interface for turning data-model relationships into diagram nodes
3
+ * and edges. Crow's Foot and UML keep relationships as direct edges; Chen
4
+ * turns each relationship into a diamond node plus two connecting edges.
5
+ */
6
+ export class DataModelRelationshipStrategy {
7
+ /**
8
+ * @param {Array<object>} relationships
9
+ * @param {object} context
10
+ * @param {Function} context.buildEdge
11
+ * @param {(id: string) => object|undefined} [context.entity] - An entity by id (where it is)
12
+ * @param {Array<{x0, y0, x1, y1}>} [context.occupied] - What is already drawn where (boxes, notes), to keep clear of
13
+ * @returns {{nodes: Array<object>, edges: Array<object>}}
14
+ */
15
+ apply(relationships, context) {
16
+ throw new Error(`${this.constructor.name} must implement apply()`);
17
+ }
18
+ }
19
+
20
+ export class RelationshipAsEdgeStrategy extends DataModelRelationshipStrategy {
21
+ apply(relationships, { buildEdge, notation }) {
22
+ const edges = relationships.map(r => buildEdge(r, notation));
23
+ return { nodes: [], edges };
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Chen: each relationship a diamond holding its name, between its two entities
29
+ * when both have a place (else laid out with the rest): midway, or when
30
+ * something is drawn there already, the nearest clear spot to one side of the
31
+ * line (spotFor). Joined to each by a plain line (no arrowhead) labelled with
32
+ * that end's cardinality.
33
+ */
34
+ export class RelationshipAsNodeStrategy extends DataModelRelationshipStrategy {
35
+ /** A relationship diamond's size. */
36
+ static DIAMOND = Object.freeze({ width: 110, height: 60 });
37
+
38
+ /** How far each try steps to the side of the line, and how many tries each way. */
39
+ static STEP = 45;
40
+ static TRIES = 6;
41
+
42
+ /**
43
+ * Where a diamond between two places goes: their midpoint when it is clear
44
+ * of everything in `occupied`, else the nearest clear spot stepping out
45
+ * across the line, one side then the other; when none is clear, the least covered.
46
+ * @returns {{x: number, y: number}}
47
+ */
48
+ static spotFor(from, to, occupied) {
49
+ const { width, height } = RelationshipAsNodeStrategy.DIAMOND;
50
+ const mid = { x: (from.x + to.x) / 2, y: (from.y + to.y) / 2 };
51
+ const length = Math.hypot(to.x - from.x, to.y - from.y) || 1;
52
+ const across = { x: -(to.y - from.y) / length, y: (to.x - from.x) / length };
53
+ // How much of a diamond at a spot is covered by what is there (0: clear).
54
+ const covered = ({ x, y }) => occupied.reduce((sum, o) => {
55
+ const w = Math.min(x + width / 2, o.x1) - Math.max(x - width / 2, o.x0);
56
+ const h = Math.min(y + height / 2, o.y1) - Math.max(y - height / 2, o.y0);
57
+ return sum + (w > 0 && h > 0 ? w * h : 0);
58
+ }, 0);
59
+ let best = mid, least = Infinity;
60
+ for (let k = 0; k <= RelationshipAsNodeStrategy.TRIES; k++) {
61
+ for (const side of k ? [1, -1] : [0]) {
62
+ const at = { x: mid.x + across.x * side * k * RelationshipAsNodeStrategy.STEP, y: mid.y + across.y * side * k * RelationshipAsNodeStrategy.STEP };
63
+ const c = covered(at);
64
+ if (c === 0) return at;
65
+ if (c < least) { best = at; least = c; }
66
+ }
67
+ }
68
+ return best; // none clear: the least covered
69
+ }
70
+
71
+ apply(relationships, { notation, entity = () => undefined, occupied = [] }) {
72
+ const nodes = [];
73
+ const edges = [];
74
+ const taken = [...occupied];
75
+ const { width, height } = RelationshipAsNodeStrategy.DIAMOND;
76
+ relationships.forEach((relationship, i) => {
77
+ const relId = relationship.id || `${relationship.source}__${relationship.target}__rel${i}`;
78
+ const lineStyle = relationship.identifying === false ? 'dashed' : 'solid';
79
+ const [from, to] = [entity(relationship.source), entity(relationship.target)];
80
+ const placed = [from, to].every(e => Number.isFinite(e?.x) && Number.isFinite(e?.y));
81
+ const at = placed ? RelationshipAsNodeStrategy.spotFor(from, to, taken) : null;
82
+ if (at) taken.push({ x0: at.x - width / 2, x1: at.x + width / 2, y0: at.y - height / 2, y1: at.y + height / 2 });
83
+ nodes.push({
84
+ id: relId, label: relationship.name || '', shape: 'diamond', color: relationship.color || '#f6c344',
85
+ labelPosition: 'inside', ...RelationshipAsNodeStrategy.DIAMOND,
86
+ ...(at && { x: at.x, y: at.y }),
87
+ });
88
+ const line = { lineStyle, arrowhead: 'none', arrowheadStart: 'none' };
89
+ edges.push({ source: relationship.source, target: relId, label: notation.cardinalityLabel(relationship.sourceCardinality), ...line });
90
+ edges.push({ source: relId, target: relationship.target, label: notation.cardinalityLabel(relationship.targetCardinality), ...line });
91
+ });
92
+ return { nodes, edges };
93
+ }
94
+ }