@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.
Files changed (33) hide show
  1. package/README.md +215 -213
  2. package/REFERENCE.md +94 -6
  3. package/dist/renderengine-workspace.umd.js +3254 -2089
  4. package/dist/renderengine.umd.js +3016 -2086
  5. package/examples/data-model.html +324 -0
  6. package/package.json +70 -68
  7. package/src/charting/CheckboxTreeSelector.js +373 -304
  8. package/src/charting/DiagramEditor.js +48 -6
  9. package/src/charting/DiagramTypeRegistry.js +5 -1
  10. package/src/charting/archimate/ArchimateCanvasEditor.js +825 -815
  11. package/src/charting/archimate/ArchimateElementTemplate.js +208 -195
  12. package/src/charting/datamodel/DataModel.js +299 -0
  13. package/src/charting/datamodel/DataModelContext.js +108 -0
  14. package/src/charting/datamodel/DataModelPerspective.js +84 -0
  15. package/src/charting/datamodel/LevelMapping.js +38 -0
  16. package/src/charting/datamodel/LevelSchema.js +41 -0
  17. package/src/charting/datamodel/index.js +5 -0
  18. package/src/charting/diagrams/DataModelDiagram.js +160 -86
  19. package/src/charting/diagrams/DataModelRelationshipStrategy.js +86 -38
  20. package/src/charting/diagrams/nesting.js +31 -0
  21. package/src/charting/modelTree/ModelTree.js +202 -164
  22. package/src/charting/modelTree/ModelTreePanel.js +36 -3
  23. package/src/charting/modelTree/TreeViewMode.js +131 -110
  24. package/src/charting/modelTree/modes/FlatTreeMode.js +32 -46
  25. package/src/charting/modelTree/modes/LayerGroupedTreeMode.js +47 -62
  26. package/src/charting/modelTree/modes/TypeGroupedTreeMode.js +46 -60
  27. package/src/datamodel.js +132 -122
  28. package/src/diagramCatalog.js +35 -30
  29. package/src/index.js +6 -1
  30. package/src/workspace/index.js +1 -0
  31. package/src/workspace/parts/ArchimateCanvasPart.js +5 -2
  32. package/src/workspace/parts/ChartPart.js +6 -1
  33. package/src/workspace/parts/DataModelPart.js +234 -0
@@ -1,110 +1,131 @@
1
- /**
2
- * Abstract base class for tree grouping/presentation modes.
3
- * Subclasses implement buildGroups() to organize model elements into groups.
4
- */
5
-
6
- export class TreeViewMode {
7
- static key = null; // registry key; subclasses override
8
- static label = ''; // human-readable label; subclasses override
9
-
10
- constructor() {
11
- if (new.target === TreeViewMode) {
12
- throw new TypeError('TreeViewMode is abstract — subclass it');
13
- }
14
- if (!this.constructor.key) {
15
- throw new TypeError(`${this.constructor.name} must define static key`);
16
- }
17
- }
18
-
19
- /**
20
- * Build groups from model elements.
21
- * @param {ArchimateModel} model
22
- * @param {Object} opts
23
- * @param {string} opts.filterText - text to filter by (case-insensitive name/type/layer)
24
- * @param {Set<string>} opts.typeIds - filter by element type (e.g. Set(['type:business-actor']))
25
- * @param {Set<string>} opts.specializationIds - filter by specialization (e.g. Set(['spec:Microservice']))
26
- * @param {Set<string>} opts.propertyIds - filter by property values (e.g. Set(['propval:Owner=Alice']))
27
- * @returns {Array<{label, items: Array<{id, kind, label, type}>}>}
28
- */
29
- buildGroups(model, { filterText = '', typeIds = null, specializationIds = null, propertyIds = null } = {}) {
30
- throw new Error(`${this.constructor.name} must implement buildGroups()`);
31
- }
32
-
33
- /**
34
- * Check if an item matches all active filters (text + type + specialization + properties).
35
- * Subclasses can override for custom filtering logic.
36
- * @protected
37
- */
38
- _matchesFilter(item, { text = '', typeIds = null, specializationIds = null, propertyIds = null } = {}) {
39
- // Text filter (label or type)
40
- if (text) {
41
- const textLower = text.toLowerCase();
42
- const label = (item.label ?? '').toLowerCase();
43
- const type = (item.type ?? '').toLowerCase();
44
- if (!label.includes(textLower) && !type.includes(textLower)) {
45
- return false;
46
- }
47
- }
48
-
49
- // Type filter (elements only; 'type:business-actor' format)
50
- if (typeIds && typeIds.size > 0 && item.kind === 'element') {
51
- const typeId = `type:${item.type}`;
52
- if (!typeIds.has(typeId)) {
53
- return false;
54
- }
55
- }
56
-
57
- // Specialization filter (elements only; 'spec:Microservice' format)
58
- if (specializationIds && specializationIds.size > 0 && item.kind === 'element') {
59
- if (!item.specialization) {
60
- // Elements without specialization don't match specialization filters
61
- return false;
62
- }
63
- const specId = `spec:${item.specialization}`;
64
- if (!specializationIds.has(specId)) {
65
- return false;
66
- }
67
- }
68
-
69
- // Property filter ('propval:PropertyName=Value' format; both elements and relationships)
70
- if (propertyIds && propertyIds.size > 0) {
71
- if (!Array.isArray(item.properties)) {
72
- // No properties defined — doesn't match property filter
73
- return false;
74
- }
75
- // Check if any property matches the filter
76
- const matches = item.properties.some(prop => {
77
- const propId = `propval:${prop.name}=${prop.value}`;
78
- return propertyIds.has(propId);
79
- });
80
- if (!matches) {
81
- return false;
82
- }
83
- }
84
-
85
- return true;
86
- }
87
-
88
- /** Group items into {label, items} structure. @protected */
89
- _group(items, keyFn, labelFn) {
90
- const groups = new Map();
91
- for (const item of items) {
92
- const key = keyFn(item);
93
- if (!groups.has(key)) {
94
- groups.set(key, { label: labelFn(key), items: [] });
95
- }
96
- groups.get(key).items.push(item);
97
- }
98
- return Array.from(groups.values());
99
- }
100
-
101
- /** Get elements from model (handles both ArchimateModel.elements() and ArchimateView.elements). @protected */
102
- _getElements(model) {
103
- return typeof model.elements === 'function' ? model.elements() : model.elements;
104
- }
105
-
106
- /** Get relationships from model (handles both ArchimateModel.relationships() and ArchimateView.relationships). @protected */
107
- _getRelationships(model) {
108
- return typeof model.relationships === 'function' ? model.relationships() : model.relationships;
109
- }
110
- }
1
+ /**
2
+ * Abstract base class for tree grouping/presentation modes.
3
+ * Subclasses implement buildGroups() to organize model elements into groups.
4
+ */
5
+
6
+ export class TreeViewMode {
7
+ static key = null; // registry key; subclasses override
8
+ static label = ''; // human-readable label; subclasses override
9
+
10
+ constructor() {
11
+ if (new.target === TreeViewMode) {
12
+ throw new TypeError('TreeViewMode is abstract — subclass it');
13
+ }
14
+ if (!this.constructor.key) {
15
+ throw new TypeError(`${this.constructor.name} must define static key`);
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Build groups from model elements.
21
+ * @param {ArchimateModel} model
22
+ * @param {Object} opts
23
+ * @param {string} opts.filterText - text to filter by (case-insensitive name/type/layer)
24
+ * @param {boolean} [opts.inDescriptions] - the text may be found in an item's description (documentation) too
25
+ * @param {Set<string>} opts.typeIds - filter by element type (e.g. Set(['type:business-actor']))
26
+ * @param {Set<string>} opts.specializationIds - filter by specialization (e.g. Set(['spec:Microservice']))
27
+ * @param {Set<string>} opts.propertyIds - filter by property values (e.g. Set(['propval:Owner=Alice']))
28
+ * @returns {Array<{label, items: Array<{id, kind, label, type}>}>}
29
+ */
30
+ buildGroups(model, { filterText = '', typeIds = null, specializationIds = null, propertyIds = null, inDescriptions = false } = {}) {
31
+ throw new Error(`${this.constructor.name} must implement buildGroups()`);
32
+ }
33
+
34
+ /** A tree item for a model element: what the filters look at. @protected */
35
+ _elementItem(el) {
36
+ return {
37
+ id: el.id, kind: 'element', label: el.name || '(unnamed)', type: el.type, layer: el.layer, icon: el.icon,
38
+ specialization: el.specialization, properties: el.properties, description: el.documentation ?? el.description ?? '',
39
+ };
40
+ }
41
+
42
+ /** A tree item for a relationship. @protected */
43
+ _relationshipItem(rel) {
44
+ return {
45
+ id: rel.id, kind: 'relationship', label: rel.label || `${rel.source} → ${rel.target}`, type: rel.kind,
46
+ properties: rel.properties, description: rel.documentation ?? rel.description ?? '',
47
+ };
48
+ }
49
+
50
+ /** The items of `list` that every filter keeps. @protected */
51
+ _filtered(list, { filterText = '', typeIds = null, specializationIds = null, propertyIds = null, inDescriptions = false } = {}) {
52
+ return list.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds, inDescriptions }));
53
+ }
54
+
55
+ /**
56
+ * Check if an item matches all active filters (text + type + specialization + properties).
57
+ * Subclasses can override for custom filtering logic.
58
+ * @protected
59
+ */
60
+ _matchesFilter(item, { text = '', typeIds = null, specializationIds = null, propertyIds = null, inDescriptions = false } = {}) {
61
+ // Text filter (label or type; the description too when asked)
62
+ if (text) {
63
+ const textLower = text.toLowerCase();
64
+ const fields = [item.label, item.type, ...(inDescriptions ? [item.description] : [])];
65
+ if (!fields.some(f => String(f ?? '').toLowerCase().includes(textLower))) {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ // Type filter (elements only; 'type:business-actor' format)
71
+ if (typeIds && typeIds.size > 0 && item.kind === 'element') {
72
+ const typeId = `type:${item.type}`;
73
+ if (!typeIds.has(typeId)) {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ // Specialization filter (elements only; 'spec:Microservice' format)
79
+ if (specializationIds && specializationIds.size > 0 && item.kind === 'element') {
80
+ if (!item.specialization) {
81
+ // Elements without specialization don't match specialization filters
82
+ return false;
83
+ }
84
+ const specId = `spec:${item.specialization}`;
85
+ if (!specializationIds.has(specId)) {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ // Property filter ('propval:PropertyName=Value' format; both elements and relationships)
91
+ if (propertyIds && propertyIds.size > 0) {
92
+ if (!Array.isArray(item.properties)) {
93
+ // No properties defined — doesn't match property filter
94
+ return false;
95
+ }
96
+ // Check if any property matches the filter
97
+ const matches = item.properties.some(prop => {
98
+ const propId = `propval:${prop.name}=${prop.value}`;
99
+ return propertyIds.has(propId);
100
+ });
101
+ if (!matches) {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ return true;
107
+ }
108
+
109
+ /** Group items into {label, items} structure. @protected */
110
+ _group(items, keyFn, labelFn) {
111
+ const groups = new Map();
112
+ for (const item of items) {
113
+ const key = keyFn(item);
114
+ if (!groups.has(key)) {
115
+ groups.set(key, { label: labelFn(key), items: [] });
116
+ }
117
+ groups.get(key).items.push(item);
118
+ }
119
+ return Array.from(groups.values());
120
+ }
121
+
122
+ /** Get elements from model (handles both ArchimateModel.elements() and ArchimateView.elements). @protected */
123
+ _getElements(model) {
124
+ return typeof model.elements === 'function' ? model.elements() : model.elements;
125
+ }
126
+
127
+ /** Get relationships from model (handles both ArchimateModel.relationships() and ArchimateView.relationships). @protected */
128
+ _getRelationships(model) {
129
+ return typeof model.relationships === 'function' ? model.relationships() : model.relationships;
130
+ }
131
+ }
@@ -1,46 +1,32 @@
1
- import { TreeViewMode } from '../TreeViewMode.js';
2
-
3
- /**
4
- * Tree mode: flat alphabetical list of all elements and relationships.
5
- */
6
- export class FlatTreeMode extends TreeViewMode {
7
- static key = 'flat';
8
- static label = 'Flat';
9
-
10
- buildGroups(model, { filterText = '', typeIds = null, specializationIds = null, propertyIds = null } = {}) {
11
- // Build element items (include specialization and properties for filtering)
12
- const elementItems = this._getElements(model).map(el => ({
13
- id: el.id,
14
- kind: 'element',
15
- label: el.name || '(unnamed)',
16
- type: el.type,
17
- icon: el.icon,
18
- specialization: el.specialization,
19
- properties: el.properties,
20
- }));
21
-
22
- // Build relationship items
23
- const relationshipItems = this._getRelationships(model).map(rel => ({
24
- id: rel.id,
25
- kind: 'relationship',
26
- label: rel.label || `${rel.source} → ${rel.target}`,
27
- type: rel.kind,
28
- properties: rel.properties,
29
- }));
30
-
31
- // Combine and filter with all criteria
32
- const allItems = [...elementItems, ...relationshipItems];
33
- const filtered = allItems.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds }));
34
-
35
- // Sort alphabetically
36
- filtered.sort((a, b) => a.label.localeCompare(b.label));
37
-
38
- // Single group containing all
39
- if (filtered.length === 0) return [];
40
-
41
- return [{
42
- label: '',
43
- items: filtered,
44
- }];
45
- }
46
- }
1
+ import { TreeViewMode } from '../TreeViewMode.js';
2
+
3
+ /**
4
+ * Tree mode: flat alphabetical list of all elements and relationships.
5
+ */
6
+ export class FlatTreeMode extends TreeViewMode {
7
+ static key = 'flat';
8
+ static label = 'Flat';
9
+
10
+ buildGroups(model, filters = {}) {
11
+ // Build element items (include specialization and properties for filtering)
12
+ const elementItems = this._getElements(model).map(el => this._elementItem(el));
13
+
14
+ // Build relationship items
15
+ const relationshipItems = this._getRelationships(model).map(rel => this._relationshipItem(rel));
16
+
17
+ // Combine and filter with all criteria
18
+ const allItems = [...elementItems, ...relationshipItems];
19
+ const filtered = this._filtered(allItems, filters);
20
+
21
+ // Sort alphabetically
22
+ filtered.sort((a, b) => a.label.localeCompare(b.label));
23
+
24
+ // Single group containing all
25
+ if (filtered.length === 0) return [];
26
+
27
+ return [{
28
+ label: '',
29
+ items: filtered,
30
+ }];
31
+ }
32
+ }
@@ -1,62 +1,47 @@
1
- import { TreeViewMode } from '../TreeViewMode.js';
2
-
3
- /**
4
- * Tree mode: groups elements by ArchiMate layer, then relationships.
5
- * Replicates the demo's current behavior.
6
- */
7
- export class LayerGroupedTreeMode extends TreeViewMode {
8
- static key = 'byLayer';
9
- static label = 'By Layer';
10
-
11
- buildGroups(model, { filterText = '', typeIds = null, specializationIds = null, propertyIds = null } = {}) {
12
- // Build element items (include specialization and properties for filtering)
13
- const elementItems = this._getElements(model).map(el => ({
14
- id: el.id,
15
- kind: 'element',
16
- label: el.name || '(unnamed)',
17
- type: el.type,
18
- layer: el.layer,
19
- icon: el.icon,
20
- specialization: el.specialization,
21
- properties: el.properties,
22
- }));
23
-
24
- // Filter elements with all criteria
25
- const filtered = elementItems.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds }));
26
-
27
- // Group by layer
28
- const layerGroups = this._group(
29
- filtered,
30
- item => item.layer || 'Other',
31
- layer => layer
32
- );
33
-
34
- // Sort layers in a sensible order (layers are lowercase-kebab)
35
- const layerOrder = ['strategy', 'business', 'application', 'technology', 'physical', 'implementation', 'motivation', 'Other'];
36
- layerGroups.sort((a, b) => {
37
- const aIdx = layerOrder.indexOf(a.label);
38
- const bIdx = layerOrder.indexOf(b.label);
39
- return (aIdx === -1 ? 999 : aIdx) - (bIdx === -1 ? 999 : bIdx);
40
- });
41
-
42
- // Add relationships group
43
- const relationshipItems = this._getRelationships(model).map(rel => ({
44
- id: rel.id,
45
- kind: 'relationship',
46
- label: rel.label || `${rel.source} → ${rel.target}`,
47
- type: rel.kind,
48
- properties: rel.properties,
49
- }));
50
-
51
- const filtered_rels = relationshipItems.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds }));
52
-
53
- if (filtered_rels.length > 0) {
54
- layerGroups.push({
55
- label: 'Relationships',
56
- items: filtered_rels,
57
- });
58
- }
59
-
60
- return layerGroups;
61
- }
62
- }
1
+ import { TreeViewMode } from '../TreeViewMode.js';
2
+
3
+ /**
4
+ * Tree mode: groups elements by ArchiMate layer, then relationships.
5
+ * Replicates the demo's current behavior.
6
+ */
7
+ export class LayerGroupedTreeMode extends TreeViewMode {
8
+ static key = 'byLayer';
9
+ static label = 'By Layer';
10
+
11
+ buildGroups(model, filters = {}) {
12
+ // Build element items (include specialization and properties for filtering)
13
+ const elementItems = this._getElements(model).map(el => this._elementItem(el));
14
+
15
+ // Filter elements with all criteria
16
+ const filtered = this._filtered(elementItems, filters);
17
+
18
+ // Group by layer
19
+ const layerGroups = this._group(
20
+ filtered,
21
+ item => item.layer || 'Other',
22
+ layer => layer
23
+ );
24
+
25
+ // Sort layers in a sensible order (layers are lowercase-kebab)
26
+ const layerOrder = ['strategy', 'business', 'application', 'technology', 'physical', 'implementation', 'motivation', 'Other'];
27
+ layerGroups.sort((a, b) => {
28
+ const aIdx = layerOrder.indexOf(a.label);
29
+ const bIdx = layerOrder.indexOf(b.label);
30
+ return (aIdx === -1 ? 999 : aIdx) - (bIdx === -1 ? 999 : bIdx);
31
+ });
32
+
33
+ // Add relationships group
34
+ const relationshipItems = this._getRelationships(model).map(rel => this._relationshipItem(rel));
35
+
36
+ const filtered_rels = this._filtered(relationshipItems, filters);
37
+
38
+ if (filtered_rels.length > 0) {
39
+ layerGroups.push({
40
+ label: 'Relationships',
41
+ items: filtered_rels,
42
+ });
43
+ }
44
+
45
+ return layerGroups;
46
+ }
47
+ }
@@ -1,60 +1,46 @@
1
- import { TreeViewMode } from '../TreeViewMode.js';
2
-
3
- /**
4
- * Tree mode: groups elements by ArchiMate type, then relationships.
5
- */
6
- export class TypeGroupedTreeMode extends TreeViewMode {
7
- static key = 'byType';
8
- static label = 'By Type';
9
-
10
- buildGroups(model, { filterText = '', typeIds = null, specializationIds = null, propertyIds = null } = {}) {
11
- // Build element items (include specialization and properties for filtering)
12
- const elementItems = this._getElements(model).map(el => ({
13
- id: el.id,
14
- kind: 'element',
15
- label: el.name || '(unnamed)',
16
- type: el.type,
17
- icon: el.icon,
18
- specialization: el.specialization,
19
- properties: el.properties,
20
- }));
21
-
22
- // Filter elements with all criteria
23
- const filtered = elementItems.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds }));
24
-
25
- // Group by type
26
- const typeGroups = this._group(
27
- filtered,
28
- item => item.type,
29
- type => type || 'Other'
30
- );
31
-
32
- // Sort alphabetically within each group
33
- typeGroups.forEach(group => {
34
- group.items.sort((a, b) => a.label.localeCompare(b.label));
35
- });
36
-
37
- // Sort groups alphabetically
38
- typeGroups.sort((a, b) => a.label.localeCompare(b.label));
39
-
40
- // Add relationships group
41
- const relationshipItems = this._getRelationships(model).map(rel => ({
42
- id: rel.id,
43
- kind: 'relationship',
44
- label: rel.label || `${rel.source} → ${rel.target}`,
45
- type: rel.kind,
46
- properties: rel.properties,
47
- }));
48
-
49
- const filtered_rels = relationshipItems.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds }));
50
-
51
- if (filtered_rels.length > 0) {
52
- typeGroups.push({
53
- label: 'Relationships',
54
- items: filtered_rels.sort((a, b) => a.label.localeCompare(b.label)),
55
- });
56
- }
57
-
58
- return typeGroups;
59
- }
60
- }
1
+ import { TreeViewMode } from '../TreeViewMode.js';
2
+
3
+ /**
4
+ * Tree mode: groups elements by ArchiMate type, then relationships.
5
+ */
6
+ export class TypeGroupedTreeMode extends TreeViewMode {
7
+ static key = 'byType';
8
+ static label = 'By Type';
9
+
10
+ buildGroups(model, filters = {}) {
11
+ // Build element items (include specialization and properties for filtering)
12
+ const elementItems = this._getElements(model).map(el => this._elementItem(el));
13
+
14
+ // Filter elements with all criteria
15
+ const filtered = this._filtered(elementItems, filters);
16
+
17
+ // Group by type
18
+ const typeGroups = this._group(
19
+ filtered,
20
+ item => item.type,
21
+ type => type || 'Other'
22
+ );
23
+
24
+ // Sort alphabetically within each group
25
+ typeGroups.forEach(group => {
26
+ group.items.sort((a, b) => a.label.localeCompare(b.label));
27
+ });
28
+
29
+ // Sort groups alphabetically
30
+ typeGroups.sort((a, b) => a.label.localeCompare(b.label));
31
+
32
+ // Add relationships group
33
+ const relationshipItems = this._getRelationships(model).map(rel => this._relationshipItem(rel));
34
+
35
+ const filtered_rels = this._filtered(relationshipItems, filters);
36
+
37
+ if (filtered_rels.length > 0) {
38
+ typeGroups.push({
39
+ label: 'Relationships',
40
+ items: filtered_rels.sort((a, b) => a.label.localeCompare(b.label)),
41
+ });
42
+ }
43
+
44
+ return typeGroups;
45
+ }
46
+ }