@peter.prib/renderengine 0.15.1 → 0.15.3
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/dist/renderengine-workspace.umd.js +6485 -3992
- package/dist/renderengine.umd.js +6251 -3877
- package/documentation/REFERENCE.md +384 -28
- package/package.json +1 -1
- package/src/archimateArchiFormat.js +114 -10
- package/src/archimateArchive.js +27 -4
- package/src/archimateExchangeFormat.js +12 -4
- package/src/archimateSpecializations.js +11 -1
- package/src/chartCatalog.js +6 -4
- package/src/charting/ArchimateDiagramEditor.js +7 -109
- package/src/charting/DiagramTypeRegistry.js +20 -1
- package/src/charting/DiagramViewPropertiesDialog.js +54 -10
- package/src/charting/Edge.js +22 -0
- package/src/charting/Legend.js +1 -2
- package/src/charting/Statistics.js +14 -0
- package/src/charting/archimate/ArchimateCanvasEditor.js +21 -5
- package/src/charting/archimate/ViewGroupTemplate.js +5 -2
- package/src/charting/charts/BarChart.js +59 -8
- package/src/charting/charts/HexbinChart.js +15 -0
- package/src/charting/charts/LollipopChart.js +42 -7
- package/src/charting/decision/DecisionWorkbench.js +71 -4
- package/src/charting/diagramModel/DiagramJsonFileFormat.js +32 -0
- package/src/charting/diagramModel/DiagramModel.js +39 -0
- package/src/charting/diagramModel/DiagramView.js +102 -61
- package/src/charting/diagramModel/index.js +11 -10
- package/src/charting/diagrams/AddGroupToViewCommand.js +35 -0
- package/src/charting/diagrams/ArchimateModel.js +53 -13
- package/src/charting/diagrams/GraphDiagram3D.js +15 -0
- package/src/charting/diagrams/NestInstanceCommand.js +2 -1
- package/src/charting/graph/GraphSource.js +72 -0
- package/src/charting/modelTree/NavigatorModel.js +22 -8
- package/src/charting/report/TemplatedReport.js +27 -377
- package/src/charting/report/ViewsReport.js +118 -0
- package/src/charting/report/reportWriters.js +496 -0
- package/src/charting/templates/C4BoundaryTemplate.js +34 -0
- package/src/charting/templates/C4ComponentTemplate.js +30 -0
- package/src/charting/templates/C4ContainerTemplate.js +49 -0
- package/src/charting/templates/C4PersonTemplate.js +35 -0
- package/src/charting/templates/C4SystemTemplate.js +32 -0
- package/src/charting/templates/C4Template.js +95 -0
- package/src/charting/templates/index.js +6 -0
- package/src/charting/templates/templateRegistry.js +77 -0
- package/src/compute/ComputeBackend.js +132 -0
- package/src/compute/CpuBackend.js +209 -0
- package/src/compute/WebGpuBackend.js +630 -0
- package/src/compute/index.js +5 -0
- package/src/compute/selectBackend.js +85 -0
- package/src/index.js +3403 -3370
- package/src/kernelExpr.js +80 -5
- package/src/workspace/parts/ArchimateEditorPart.js +2 -2
- package/src/workspace/parts/DiagramEditorPart.js +146 -131
- package/src/workspace/parts/ViewsPart.js +106 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ArchimateNotation } from './archimate.js';
|
|
2
2
|
import { archiElementType, typeProfileId, typeDisplayName, ARCHI_TYPE_FEATURE, ARCHI_TYPE_PROFILE_PREFIX } from './archimateArchiTypes.js';
|
|
3
|
-
import { ArchimateModel } from './charting/diagrams/ArchimateModel.js';
|
|
3
|
+
import { ArchimateModel } from './charting/diagrams/ArchimateModel.js';
|
|
4
|
+
import { ArchimateSpecializationRegistry } from './archimateSpecializations.js';
|
|
4
5
|
import './charting/diagrams/CanvasView.js'; // registers the canvas view type
|
|
5
6
|
import './charting/diagrams/SketchView.js'; // registers the sketch view type
|
|
6
7
|
import { ElementInstance } from './charting/diagrams/ElementInstance.js';
|
|
@@ -720,7 +721,39 @@ function _childText(el, tagName) {
|
|
|
720
721
|
* @param {Object} archiModel - parsed Archi XML model
|
|
721
722
|
* @returns {ArchimateModel} with one view per original view (elementIds track which elements appear in each view)
|
|
722
723
|
* Now applies visual bounds/waypoints per view. */
|
|
723
|
-
|
|
724
|
+
/** Register any model-level Archi specializations (<profile>) with ArchimateSpecializationRegistry. @private */
|
|
725
|
+
function _registerModelProfiles(archiModel) {
|
|
726
|
+
const profilesById = new Map();
|
|
727
|
+
for (const e of archiModel?.archi?.elements || []) {
|
|
728
|
+
if (e.tag === 'profile' && !String(e.attributes?.id ?? '').startsWith(ARCHI_TYPE_PROFILE_PREFIX)) {
|
|
729
|
+
const id = e.attributes?.id || e.attributes?.name;
|
|
730
|
+
const name = e.attributes?.name || e.attributes?.id;
|
|
731
|
+
const conceptType = e.attributes?.conceptType;
|
|
732
|
+
const imagePath = e.attributes?.imagePath;
|
|
733
|
+
const archiImagePath = e.attributes?.archiImagePath;
|
|
734
|
+
const profileDef = {
|
|
735
|
+
id,
|
|
736
|
+
name,
|
|
737
|
+
conceptType,
|
|
738
|
+
baseTypes: conceptType ? [_toKebabType(conceptType)] : [],
|
|
739
|
+
iconUrl: imagePath || undefined,
|
|
740
|
+
archiImagePath,
|
|
741
|
+
};
|
|
742
|
+
if (id) profilesById.set(id, profileDef);
|
|
743
|
+
if (name) profilesById.set(name, profileDef);
|
|
744
|
+
ArchimateSpecializationRegistry.register({
|
|
745
|
+
id,
|
|
746
|
+
name,
|
|
747
|
+
baseTypes: profileDef.baseTypes,
|
|
748
|
+
iconUrl: profileDef.iconUrl,
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return profilesById;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
export function toArchimateModel(archiModel) {
|
|
756
|
+
const profilesById = _registerModelProfiles(archiModel);
|
|
724
757
|
const elements = [];
|
|
725
758
|
const relationships = [];
|
|
726
759
|
const folders = [];
|
|
@@ -731,7 +764,7 @@ export function toArchimateModel(archiModel) {
|
|
|
731
764
|
|
|
732
765
|
// Map all elements first
|
|
733
766
|
for (const el of archiModel.elements || []) {
|
|
734
|
-
const mapped = _mapElement(el);
|
|
767
|
+
const mapped = _mapElement(el, profilesById);
|
|
735
768
|
elementById.set(el.id, mapped);
|
|
736
769
|
elements.push(mapped);
|
|
737
770
|
}
|
|
@@ -978,7 +1011,8 @@ function _viewInstances(archiView, { elementById, relationshipById }) {
|
|
|
978
1011
|
* Returns `{ elements, relationships, options }` ready for `archimateDiagram()`.
|
|
979
1012
|
* If `viewId` is omitted, the first view is used.
|
|
980
1013
|
* Kept as original for backward compatibility: applies visual bounds/features and handles all view visuals. */
|
|
981
|
-
export function toRenderEngineModel(archiModel, viewId) {
|
|
1014
|
+
export function toRenderEngineModel(archiModel, viewId) {
|
|
1015
|
+
const profilesById = _registerModelProfiles(archiModel);
|
|
982
1016
|
const view = viewId
|
|
983
1017
|
? archiModel.views.find(v => v.id === viewId)
|
|
984
1018
|
: archiModel.views[0];
|
|
@@ -990,7 +1024,7 @@ export function toRenderEngineModel(archiModel, viewId) {
|
|
|
990
1024
|
const relationshipById = new Map();
|
|
991
1025
|
|
|
992
1026
|
for (const el of archiModel.elements || []) {
|
|
993
|
-
const mapped = _mapElement(el);
|
|
1027
|
+
const mapped = _mapElement(el, profilesById);
|
|
994
1028
|
elementById.set(el.id, mapped);
|
|
995
1029
|
elements.push(mapped);
|
|
996
1030
|
}
|
|
@@ -1020,7 +1054,7 @@ export function toRenderEngineModel(archiModel, viewId) {
|
|
|
1020
1054
|
return { elements, relationships, folders, folderLayout, elementFolders, options };
|
|
1021
1055
|
}
|
|
1022
1056
|
|
|
1023
|
-
function _mapElement(el) {
|
|
1057
|
+
function _mapElement(el, profilesById = null) {
|
|
1024
1058
|
const archiType = _toKebabType(el.type);
|
|
1025
1059
|
const layer = ArchimateNotation.elementLayer({ type: archiType });
|
|
1026
1060
|
// A type Archi has no class for was written as the nearest one, with its own kept as a feature.
|
|
@@ -1032,6 +1066,21 @@ function _mapElement(el) {
|
|
|
1032
1066
|
layer: layer !== 'other' ? layer : undefined,
|
|
1033
1067
|
documentation: el.documentation || undefined,
|
|
1034
1068
|
};
|
|
1069
|
+
const profileAttr = el.archi?.attributes?.profiles || el.attributes?.profiles;
|
|
1070
|
+
if (profileAttr && !profileAttr.startsWith(ARCHI_TYPE_PROFILE_PREFIX)) {
|
|
1071
|
+
const firstId = profileAttr.trim().split(/\s+/)[0];
|
|
1072
|
+
const profileDef = profilesById?.get(firstId) || ArchimateSpecializationRegistry.resolve(firstId);
|
|
1073
|
+
mapped.specialization = profileDef?.name || firstId;
|
|
1074
|
+
if (profileDef?.iconUrl && !mapped.iconUrl) {
|
|
1075
|
+
mapped.iconUrl = profileDef.iconUrl;
|
|
1076
|
+
}
|
|
1077
|
+
} else if (el.specialization) {
|
|
1078
|
+
mapped.specialization = el.specialization;
|
|
1079
|
+
const spec = ArchimateSpecializationRegistry.resolve(el.specialization);
|
|
1080
|
+
if (spec?.iconUrl && !mapped.iconUrl) {
|
|
1081
|
+
mapped.iconUrl = spec.iconUrl;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1035
1084
|
const props = _propertiesToTypedArray(el.properties);
|
|
1036
1085
|
if (props) mapped.properties = props;
|
|
1037
1086
|
if (el.archiFolder) mapped.archiFolder = el.archiFolder;
|
|
@@ -1388,15 +1437,56 @@ export function toArchiExport(model) {
|
|
|
1388
1437
|
archi: view.archi,
|
|
1389
1438
|
children: fromArchimateView(view),
|
|
1390
1439
|
}));
|
|
1440
|
+
const archiElements = [...(model.archi?.elements ?? [])];
|
|
1441
|
+
const elements = model.elements().map(el => {
|
|
1442
|
+
if (!el.specialization) return el;
|
|
1443
|
+
const spec = ArchimateSpecializationRegistry.resolve(el.specialization);
|
|
1444
|
+
if (!spec) return el;
|
|
1445
|
+
const specId = spec.id.startsWith('id-') ? spec.id : `id-prof-${spec.id}`;
|
|
1446
|
+
const existing = archiElements.find(e => e.tag === 'profile' && (e.attributes?.id === specId || e.attributes?.name === spec.name));
|
|
1447
|
+
if (!existing) {
|
|
1448
|
+
archiElements.push({
|
|
1449
|
+
tag: 'profile',
|
|
1450
|
+
attributes: {
|
|
1451
|
+
id: specId,
|
|
1452
|
+
name: spec.name,
|
|
1453
|
+
conceptType: _toPascalType(el.type),
|
|
1454
|
+
...(spec.iconUrl && { imagePath: spec.iconUrl }),
|
|
1455
|
+
},
|
|
1456
|
+
});
|
|
1457
|
+
} else if (spec.iconUrl && !existing.attributes?.imagePath) {
|
|
1458
|
+
existing.attributes = { ...existing.attributes, imagePath: spec.iconUrl };
|
|
1459
|
+
}
|
|
1460
|
+
if (!el.archi?.attributes?.profiles) {
|
|
1461
|
+
return {
|
|
1462
|
+
...el,
|
|
1463
|
+
archi: {
|
|
1464
|
+
...(el.archi ?? {}),
|
|
1465
|
+
attributes: {
|
|
1466
|
+
...(el.archi?.attributes ?? {}),
|
|
1467
|
+
profiles: specId,
|
|
1468
|
+
},
|
|
1469
|
+
},
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
return el;
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
const archi = (model.archi || archiElements.length) ? {
|
|
1476
|
+
...(model.archi ?? {}),
|
|
1477
|
+
attributes: { ...(model.archi?.attributes ?? {}) },
|
|
1478
|
+
elements: archiElements,
|
|
1479
|
+
} : null;
|
|
1480
|
+
|
|
1391
1481
|
return [{
|
|
1392
1482
|
id: model.id || 'model-1',
|
|
1393
1483
|
name: model.name || 'ArchiMate Model',
|
|
1394
1484
|
version: '5.0.0',
|
|
1395
1485
|
documentation: model.documentation,
|
|
1396
1486
|
properties: model.properties,
|
|
1397
|
-
archi
|
|
1487
|
+
archi,
|
|
1398
1488
|
emptyViewFolders: model.emptyViewFolders ?? [],
|
|
1399
|
-
elements
|
|
1489
|
+
elements,
|
|
1400
1490
|
relationships: model.relationships(),
|
|
1401
1491
|
}, views];
|
|
1402
1492
|
}
|
|
@@ -1510,6 +1600,20 @@ export function toArchiXML(model, views) {
|
|
|
1510
1600
|
if (original && !el.archi?.attributes?.profiles) {
|
|
1511
1601
|
profile = typeProfileId(original);
|
|
1512
1602
|
typeProfiles.set(profile, { name: typeDisplayName(original), conceptType: _toPascalType(type) });
|
|
1603
|
+
} else if (el.specialization && !el.archi?.attributes?.profiles) {
|
|
1604
|
+
const spec = ArchimateSpecializationRegistry.resolve(el.specialization);
|
|
1605
|
+
if (spec) {
|
|
1606
|
+
const specId = spec.id.startsWith('id-') ? spec.id : ('id-prof-' + spec.id);
|
|
1607
|
+
profile = specId;
|
|
1608
|
+
const existing = (model.archi?.elements ?? []).find(e => e.tag === 'profile' && (e.attributes?.id === specId || e.attributes?.name === spec.name));
|
|
1609
|
+
if (!existing && !typeProfiles.has(specId)) {
|
|
1610
|
+
typeProfiles.set(specId, {
|
|
1611
|
+
name: spec.name,
|
|
1612
|
+
conceptType: _toPascalType(type),
|
|
1613
|
+
...(spec.iconUrl && { imagePath: spec.iconUrl }),
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1513
1617
|
}
|
|
1514
1618
|
byFolder.get(folder.type).push({ el, type, original, profile });
|
|
1515
1619
|
}
|
|
@@ -1540,8 +1644,8 @@ export function toArchiXML(model, views) {
|
|
|
1540
1644
|
|
|
1541
1645
|
// What else the model carried (profiles, ...), its own properties, then its documentation (Archi's "purpose").
|
|
1542
1646
|
lines.push(..._extraElementLines(model.archi?.elements, ' '));
|
|
1543
|
-
for (const [id, { name, conceptType }] of typeProfiles) {
|
|
1544
|
-
lines.push(` <profile name="${_escapeAttr(name)}" id="${id}" conceptType="${conceptType}"/>`);
|
|
1647
|
+
for (const [id, { name, conceptType, imagePath }] of typeProfiles) {
|
|
1648
|
+
lines.push(` <profile name="${_escapeAttr(name)}" id="${id}" conceptType="${conceptType}"${imagePath ? ` imagePath="${_escapeAttr(imagePath)}"` : ''}/>`);
|
|
1545
1649
|
}
|
|
1546
1650
|
lines.push(..._propertyLines(model.properties, ' '));
|
|
1547
1651
|
if (model.documentation) lines.push(` <purpose>${_escapeXml(model.documentation)}</purpose>`);
|
package/src/archimateArchive.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { parseArchiModel, toArchiXML } from './archimateArchiFormat.js';
|
|
1
|
+
import { parseArchiModel, toArchiXML, toPascalType } from './archimateArchiFormat.js';
|
|
2
|
+
import { ArchimateSpecializationRegistry } from './archimateSpecializations.js';
|
|
2
3
|
import { ZipArchive } from './zip/ZipArchive.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -95,15 +96,37 @@ export function toArchiFile(model, views) {
|
|
|
95
96
|
_eachVisual(copies, (visual) => registerPicture(visual));
|
|
96
97
|
|
|
97
98
|
let modelForXml = model;
|
|
98
|
-
|
|
99
|
+
let archiElements = [...(model?.archi?.elements || [])];
|
|
100
|
+
for (const el of model?.elements || []) {
|
|
101
|
+
if (el.specialization) {
|
|
102
|
+
const spec = ArchimateSpecializationRegistry.resolve(el.specialization);
|
|
103
|
+
if (spec?.iconUrl?.startsWith('data:')) {
|
|
104
|
+
const specId = spec.id.startsWith('id-') ? spec.id : ('id-prof-' + spec.id);
|
|
105
|
+
if (!archiElements.some(e => e.tag === 'profile' && (e.attributes?.id === specId || e.attributes?.name === spec.name))) {
|
|
106
|
+
archiElements.push({
|
|
107
|
+
tag: 'profile',
|
|
108
|
+
attributes: {
|
|
109
|
+
id: specId,
|
|
110
|
+
name: spec.name,
|
|
111
|
+
conceptType: toPascalType(el.type),
|
|
112
|
+
imagePath: spec.iconUrl,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (archiElements.some(e => e.attributes?.imagePath?.startsWith('data:'))) {
|
|
99
120
|
modelForXml = {
|
|
100
121
|
...model,
|
|
101
122
|
archi: {
|
|
102
|
-
...model
|
|
103
|
-
|
|
123
|
+
...(model?.archi ?? {}),
|
|
124
|
+
attributes: { ...(model?.archi?.attributes ?? {}) },
|
|
125
|
+
elements: archiElements.map(e => {
|
|
104
126
|
if (!e.attributes?.imagePath?.startsWith('data:')) return e;
|
|
105
127
|
const clonedAttr = { ...e.attributes };
|
|
106
128
|
registerPicture(clonedAttr);
|
|
129
|
+
delete clonedAttr.archiImagePath;
|
|
107
130
|
return { ...e, attributes: clonedAttr };
|
|
108
131
|
}),
|
|
109
132
|
},
|
|
@@ -144,12 +144,20 @@ function _organizationPaths(root) {
|
|
|
144
144
|
return { paths, emptyViewFolders };
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
function _asPropertyPairs(pairs) {
|
|
148
|
+
const list = pairs.map(p => ({ ...p }));
|
|
149
|
+
for (const { key, value } of pairs) {
|
|
150
|
+
if (!(key in list)) list[key] = value;
|
|
151
|
+
}
|
|
152
|
+
return list;
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
/** An element as parseArchiModel gives it (type: Archi's class name). @private */
|
|
148
156
|
function _parseElement(el, propertyPairs) {
|
|
149
157
|
const xsiType = _xsiType(el);
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
|
|
158
|
+
const ownType = propertyPairs.find(p => p.key === EXCHANGE_TYPE_PROPERTY)?.value;
|
|
159
|
+
const filteredPairs = propertyPairs.filter(p => p.key !== EXCHANGE_TYPE_PROPERTY);
|
|
160
|
+
const properties = _asPropertyPairs(filteredPairs);
|
|
153
161
|
const attributes = {};
|
|
154
162
|
const elements = [];
|
|
155
163
|
if (JUNCTION_TYPES[xsiType]) attributes.type = 'or';
|
|
@@ -180,7 +188,7 @@ function _parseRelationship(el, propertyPairs) {
|
|
|
180
188
|
target: el.getAttribute('target'),
|
|
181
189
|
...(el.getAttribute('isDirected') === 'true' && { directed: true }),
|
|
182
190
|
documentation: _text(el, 'documentation'),
|
|
183
|
-
properties:
|
|
191
|
+
properties: _asPropertyPairs(propertyPairs),
|
|
184
192
|
...(Object.keys(attributes).length && { archi: { attributes, elements: [] } }),
|
|
185
193
|
};
|
|
186
194
|
}
|
|
@@ -99,6 +99,12 @@ export class ArchimateSpecializationRegistry {
|
|
|
99
99
|
/** Register or replace a specialization. */
|
|
100
100
|
static register(spec) {
|
|
101
101
|
const s = new ArchimateSpecialization(spec);
|
|
102
|
+
const existingByName = this.list().find(e => e.name === s.name && e.id !== s.id);
|
|
103
|
+
if (existingByName) {
|
|
104
|
+
if (s.iconUrl && !existingByName.iconUrl) existingByName.iconUrl = s.iconUrl;
|
|
105
|
+
if (s.icon && !existingByName.icon) existingByName.icon = s.icon;
|
|
106
|
+
if (s.color && !existingByName.color) existingByName.color = s.color;
|
|
107
|
+
}
|
|
102
108
|
this._store.set(s.id, s);
|
|
103
109
|
return s;
|
|
104
110
|
}
|
|
@@ -116,7 +122,11 @@ export class ArchimateSpecializationRegistry {
|
|
|
116
122
|
/** Look up by id or by exact name. */
|
|
117
123
|
static resolve(idOrName) {
|
|
118
124
|
if (!idOrName) return undefined;
|
|
119
|
-
|
|
125
|
+
const byId = this._store.get(idOrName);
|
|
126
|
+
if (byId) return byId;
|
|
127
|
+
const matches = this.list().filter(s => s.name === idOrName);
|
|
128
|
+
if (!matches.length) return undefined;
|
|
129
|
+
return matches.find(s => s.iconUrl || s.icon) || matches[0];
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
/** All registered specializations. */
|
package/src/chartCatalog.js
CHANGED
|
@@ -91,12 +91,13 @@ export const CAMERA_MODE = {
|
|
|
91
91
|
export const CHART_CATALOG = [
|
|
92
92
|
{
|
|
93
93
|
key: 'bar', label: 'Bar', method: 'barChart', category: '2d',
|
|
94
|
-
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.
|
|
94
|
+
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.GROUPED_FIELD, camera: CAMERA_MODE.NONE,
|
|
95
95
|
axes: [
|
|
96
96
|
{ role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
|
|
97
97
|
{ role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y_LENGTH, required: true },
|
|
98
|
+
{ role: 'group', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: false },
|
|
98
99
|
],
|
|
99
|
-
dataShape: 'Array<{label, value}>',
|
|
100
|
+
dataShape: 'Array<{label, value}> — optional `colorCol` (field name or compiled formula) colors bars by group',
|
|
100
101
|
streamShape: 'flat',
|
|
101
102
|
},
|
|
102
103
|
{
|
|
@@ -609,14 +610,15 @@ export const CHART_CATALOG = [
|
|
|
609
610
|
},
|
|
610
611
|
{
|
|
611
612
|
key: 'lollipop', label: 'Lollipop', method: 'lollipopChart', category: '2d',
|
|
612
|
-
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.
|
|
613
|
+
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.GROUPED_FIELD, camera: CAMERA_MODE.NONE,
|
|
613
614
|
seriesCapable: true,
|
|
614
615
|
axes: [
|
|
615
616
|
{ role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
|
|
616
617
|
{ role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
|
|
617
618
|
{ role: 'series', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: false },
|
|
619
|
+
{ role: 'group', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: false },
|
|
618
620
|
],
|
|
619
|
-
dataShape: 'Array<{label, value}> or multi-series Array<{name, values:Array<{label,value}>}> — stem-and-dot alternative to bars',
|
|
621
|
+
dataShape: 'Array<{label, value}> or multi-series Array<{name, values:Array<{label,value}>}> — stem-and-dot alternative to bars; `colorCol` supported on 1D data',
|
|
620
622
|
streamShape: 'flat',
|
|
621
623
|
},
|
|
622
624
|
{
|
|
@@ -33,7 +33,8 @@ import { ExchangeFileFormat } from './archimate/ExchangeFileFormat.js';
|
|
|
33
33
|
import { ArchimateCsvFileFormat } from './archimate/ArchimateCsvFileFormat.js';
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* ArchimateDiagramEditor —
|
|
36
|
+
* ArchimateDiagramEditor — legacy GraphDiagram-based ArchiMate editor extending DiagramEditor.
|
|
37
|
+
* @deprecated Use ArchimateCanvasEditor (or ArchimateCanvasPart) for ArchiMate editing. RenderEngine.archimateDiagram() remains for read-only charts.
|
|
37
38
|
*
|
|
38
39
|
* Encapsulates: ArchiMate model, tree navigator, diagram tabs, rendering, controls wiring.
|
|
39
40
|
* Inherits: common rendering, drag/drop, toolbar, tree, filtering from DiagramEditor.
|
|
@@ -140,11 +141,6 @@ export class ArchimateDiagramEditor extends DiagramEditor {
|
|
|
140
141
|
|
|
141
142
|
// Enable tree drag-drop (delayed to allow DOM to be ready)
|
|
142
143
|
setTimeout(() => this.enableTreeDragDrop(), 100);
|
|
143
|
-
|
|
144
|
-
// Wire controls if not using auto-toolbar
|
|
145
|
-
if (!this.features) {
|
|
146
|
-
this.wireControls();
|
|
147
|
-
}
|
|
148
144
|
}
|
|
149
145
|
|
|
150
146
|
/**
|
|
@@ -1365,19 +1361,10 @@ export class ArchimateDiagramEditor extends DiagramEditor {
|
|
|
1365
1361
|
}
|
|
1366
1362
|
|
|
1367
1363
|
/**
|
|
1368
|
-
* Get a control value
|
|
1364
|
+
* Get a control value.
|
|
1369
1365
|
*/
|
|
1370
1366
|
getOption(controlId, defaultValue) {
|
|
1371
|
-
|
|
1372
|
-
// Hosts that bring their own controls (wireControls) keep them in the DOM.
|
|
1373
|
-
try {
|
|
1374
|
-
const el = this.document.getElementById(controlId);
|
|
1375
|
-
if (!el) return defaultValue;
|
|
1376
|
-
if (el.type === 'checkbox') return el.checked;
|
|
1377
|
-
return el.value || defaultValue;
|
|
1378
|
-
} catch {
|
|
1379
|
-
return defaultValue;
|
|
1380
|
-
}
|
|
1367
|
+
return super.getOption(controlId, defaultValue);
|
|
1381
1368
|
}
|
|
1382
1369
|
|
|
1383
1370
|
/** Save specializations to localStorage. */
|
|
@@ -1571,98 +1558,9 @@ export class ArchimateDiagramEditor extends DiagramEditor {
|
|
|
1571
1558
|
}
|
|
1572
1559
|
|
|
1573
1560
|
/**
|
|
1574
|
-
*
|
|
1575
|
-
* Triggers render on change. Optional: provide custom handler functions.
|
|
1561
|
+
* @deprecated Legacy control wiring. Controls are now auto-rendered into the toolbar via `features`.
|
|
1576
1562
|
*/
|
|
1577
|
-
wireControls(
|
|
1578
|
-
|
|
1579
|
-
onVersionChange,
|
|
1580
|
-
onViewpointChange,
|
|
1581
|
-
onViewpointModeChange,
|
|
1582
|
-
onDistinctShapesChange,
|
|
1583
|
-
onShowDerivedChange,
|
|
1584
|
-
onManageViewpoints,
|
|
1585
|
-
onManageSpecializations,
|
|
1586
|
-
populateViewpointSelect,
|
|
1587
|
-
} = options;
|
|
1588
|
-
|
|
1589
|
-
// Version selector
|
|
1590
|
-
const versionEl = this.document.getElementById('archimate-version');
|
|
1591
|
-
if (versionEl) {
|
|
1592
|
-
versionEl.addEventListener('change', () => {
|
|
1593
|
-
if (onVersionChange) onVersionChange();
|
|
1594
|
-
this.render();
|
|
1595
|
-
});
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
// Viewpoint selector and mode
|
|
1599
|
-
const viewpointEl = this.document.getElementById('archimate-viewpoint');
|
|
1600
|
-
const viewpointModeEl = this.document.getElementById('archimate-viewpoint-mode');
|
|
1601
|
-
if (viewpointEl) {
|
|
1602
|
-
viewpointEl.addEventListener('change', () => {
|
|
1603
|
-
if (onViewpointChange) onViewpointChange();
|
|
1604
|
-
this.render();
|
|
1605
|
-
});
|
|
1606
|
-
}
|
|
1607
|
-
if (viewpointModeEl) {
|
|
1608
|
-
viewpointModeEl.addEventListener('change', () => {
|
|
1609
|
-
if (onViewpointModeChange) onViewpointModeChange();
|
|
1610
|
-
this.render();
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
// Distinct shapes checkbox
|
|
1615
|
-
const distinctShapesEl = this.document.getElementById('archimate-distinct-shapes');
|
|
1616
|
-
if (distinctShapesEl) {
|
|
1617
|
-
distinctShapesEl.addEventListener('change', () => {
|
|
1618
|
-
if (onDistinctShapesChange) onDistinctShapesChange();
|
|
1619
|
-
this.render();
|
|
1620
|
-
});
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
// Show derived relationships checkbox
|
|
1624
|
-
const showDerivedEl = this.document.getElementById('archimate-show-derived');
|
|
1625
|
-
if (showDerivedEl) {
|
|
1626
|
-
showDerivedEl.addEventListener('change', () => {
|
|
1627
|
-
if (onShowDerivedChange) onShowDerivedChange();
|
|
1628
|
-
this.render();
|
|
1629
|
-
});
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
// Manage viewpoints dialog
|
|
1633
|
-
const manageVpEl = this.document.getElementById('archimate-manage-viewpoints');
|
|
1634
|
-
if (manageVpEl && onManageViewpoints) {
|
|
1635
|
-
manageVpEl.addEventListener('click', onManageViewpoints);
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
// Manage specializations dialog
|
|
1639
|
-
const manageSpecEl = this.document.getElementById('archimate-manage-specializations');
|
|
1640
|
-
if (manageSpecEl && onManageSpecializations) {
|
|
1641
|
-
manageSpecEl.addEventListener('click', onManageSpecializations);
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
// Settings dialog
|
|
1645
|
-
const settingsEl = this.document.getElementById('archimate-settings');
|
|
1646
|
-
if (settingsEl) {
|
|
1647
|
-
settingsEl.addEventListener('click', () => {
|
|
1648
|
-
this.workspace.showGlobalSettingsDialog(this.document, this.settingsManager, 'default');
|
|
1649
|
-
});
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
// Filter controls
|
|
1653
|
-
const filterInput = this.document.getElementById('archimate-filter');
|
|
1654
|
-
const filterClearEl = this.document.getElementById('archimate-filter-clear');
|
|
1655
|
-
if (filterInput && this.currentEngine) {
|
|
1656
|
-
const applyFilter = () => {
|
|
1657
|
-
this.currentEngine.filterGraphNodes(filterInput.value);
|
|
1658
|
-
};
|
|
1659
|
-
filterInput.addEventListener('input', applyFilter);
|
|
1660
|
-
if (filterClearEl) {
|
|
1661
|
-
filterClearEl.addEventListener('click', () => {
|
|
1662
|
-
filterInput.value = '';
|
|
1663
|
-
applyFilter();
|
|
1664
|
-
});
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1563
|
+
wireControls(_options = {}) {
|
|
1564
|
+
// No-op: toolbar and controls are managed via features and MenuBar
|
|
1667
1565
|
}
|
|
1668
1566
|
}
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
EllipseTemplate, DiamondTemplate, TriangleTemplate, PentagonTemplate, HexagonTemplate,
|
|
13
13
|
OctagonTemplate, StarTemplate, ParallelogramTemplate, TrapezoidTemplate, ManualInputTemplate,
|
|
14
14
|
CylinderTemplate, DocumentTemplate, NoteTemplate, CloudTemplate, ActorTemplate,
|
|
15
|
-
PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate,
|
|
15
|
+
PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate,
|
|
16
|
+
C4PersonTemplate, C4SystemTemplate, C4ContainerTemplate, C4ComponentTemplate, C4BoundaryTemplate,
|
|
16
17
|
} from './templates/index.js';
|
|
17
18
|
|
|
18
19
|
const C = {
|
|
@@ -202,6 +203,24 @@ export const DIAGRAM_TYPES = {
|
|
|
202
203
|
],
|
|
203
204
|
},
|
|
204
205
|
|
|
206
|
+
'c4-architecture': {
|
|
207
|
+
name: 'C4 Architecture',
|
|
208
|
+
group: 'Architecture',
|
|
209
|
+
description: 'Hierarchical software architecture: Person, System, Container, Component, and Boundaries',
|
|
210
|
+
templates: [
|
|
211
|
+
shape('c4-person', 'Person', C4PersonTemplate, { fillColor: '#08427b', textColor: '#ffffff' }, 'A human user, actor, persona, or stakeholder interacting with systems'),
|
|
212
|
+
shape('c4-system', 'Software System', C4SystemTemplate, { fillColor: '#1168bd', textColor: '#ffffff' }, 'The software system in focus delivering business value'),
|
|
213
|
+
shape('c4-system-external', 'External System', C4SystemTemplate, { external: true, fillColor: '#999999', textColor: '#ffffff' }, 'An external software system outside the boundary of change'),
|
|
214
|
+
shape('c4-container', 'Container', C4ContainerTemplate, { fillColor: '#438dd5', textColor: '#ffffff' }, 'An application, web service, mobile app, or microservice'),
|
|
215
|
+
shape('c4-database', 'Database Container', C4ContainerTemplate, { containerType: 'database', fillColor: '#438dd5', textColor: '#ffffff' }, 'A database, persistence store, cache, or datastore container'),
|
|
216
|
+
shape('c4-component', 'Component', C4ComponentTemplate, { fillColor: '#85bbf0', textColor: '#000000' }, 'A modular component, controller, or service within a container'),
|
|
217
|
+
shape('c4-system-boundary', 'System Boundary', C4BoundaryTemplate, { boundaryType: 'System', zIndex: -1 }, 'Boundary box enclosing containers belonging to a Software System'),
|
|
218
|
+
shape('c4-enterprise-boundary', 'Enterprise Boundary', C4BoundaryTemplate, { boundaryType: 'Enterprise', zIndex: -2 }, 'Boundary box enclosing all systems belonging to an enterprise'),
|
|
219
|
+
shape('c4-container-boundary', 'Container Boundary', C4BoundaryTemplate, { boundaryType: 'Container', zIndex: -1 }, 'Boundary box enclosing components belonging to a Container'),
|
|
220
|
+
note,
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
|
|
205
224
|
'er-diagram': {
|
|
206
225
|
name: 'Entity-Relationship',
|
|
207
226
|
group: 'Data',
|
|
@@ -2,26 +2,33 @@ import { ViewPropertiesDialog } from './ViewPropertiesDialog.js';
|
|
|
2
2
|
import { inheritableFields, readInheritableFields } from './SettingsCascade.js';
|
|
3
3
|
import { EDGE_STYLE_PROPERTIES } from './edgeStyle/EdgeStyleSchema.js';
|
|
4
4
|
import { SetStyleCommand } from './edgeStyle/SetStyleCommand.js';
|
|
5
|
+
import { CompositeCommand } from './CommandHistory.js';
|
|
6
|
+
|
|
7
|
+
const TITLE_POSITIONS = [['none', 'None'], ['top', 'Top'], ['bottom', 'Bottom']];
|
|
8
|
+
const WATERMARK_ROTATIONS = [['0°', 'Horizontal (0°)'], ['-45°', 'Diagonal (-45°)'], ['-90°', 'Vertical (-90°)']];
|
|
9
|
+
const WATERMARK_REPEATS = [['none', 'Center (single)'], ['repeat', 'Repeated grid'], ['tile', 'Tiled pattern']];
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* DiagramViewPropertiesDialog — a template-editor diagram's properties plus
|
|
8
|
-
* its line-style defaults
|
|
9
|
-
* (diagram → model → tool)
|
|
10
|
-
*
|
|
11
|
-
* DiagramEditorPart.
|
|
13
|
+
* its line-style defaults and visual appearance (background, title band, watermark):
|
|
14
|
+
* the diagram level of the line-style cascade (diagram → model → tool) and
|
|
15
|
+
* the diagram appearance settings. Changes are undoable on the diagram's history.
|
|
16
|
+
* Used by DiagramEditorPart.
|
|
12
17
|
*/
|
|
13
18
|
export class DiagramViewPropertiesDialog extends ViewPropertiesDialog {
|
|
14
19
|
/**
|
|
15
20
|
* @param {Object} options - As ViewPropertiesDialog, plus:
|
|
16
21
|
* @param {EdgeStyleCascade} options.cascade - The diagram's line-style levels, the diagram's own keyed `levelKey`
|
|
17
22
|
* @param {string} [options.levelKey] - Which level is this diagram's
|
|
18
|
-
* @param {() => void} [options.onStyleChange] - After the style changes
|
|
23
|
+
* @param {() => void} [options.onStyleChange] - After the line style changes
|
|
24
|
+
* @param {() => void} [options.onSettingsChange] - After the appearance settings change
|
|
19
25
|
*/
|
|
20
|
-
constructor({ cascade, levelKey = 'view', onStyleChange = () => {}, ...rest }) {
|
|
26
|
+
constructor({ cascade, levelKey = 'view', onStyleChange = () => {}, onSettingsChange = () => {}, ...rest }) {
|
|
21
27
|
super(rest);
|
|
22
28
|
this.cascade = cascade;
|
|
23
29
|
this.levelKey = levelKey;
|
|
24
30
|
this.onStyleChange = onStyleChange;
|
|
31
|
+
this.onSettingsChange = onSettingsChange;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
/** The diagram's own line style (its level's style object). */
|
|
@@ -30,17 +37,54 @@ export class DiagramViewPropertiesDialog extends ViewPropertiesDialog {
|
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
sections() {
|
|
40
|
+
const ownSettings = this.view.viewSettings ?? {};
|
|
33
41
|
return [
|
|
34
42
|
{ type: 'heading', label: 'Line style' },
|
|
35
43
|
...inheritableFields(EDGE_STYLE_PROPERTIES, this.style, this.cascade.inheritedBy(this.levelKey)),
|
|
44
|
+
{ type: 'heading', label: 'Appearance' },
|
|
45
|
+
{ key: 'background', label: 'Background', type: 'color', value: ownSettings.background || '#ffffff' },
|
|
46
|
+
{ key: 'backgroundImage', label: 'Background image', type: 'text', value: ownSettings.backgroundImage ?? '', placeholder: 'URL (optional)' },
|
|
47
|
+
{ key: 'bandTitle', label: 'Title band', type: 'text', value: ownSettings.title ?? '', placeholder: 'Text shown above or below the diagram' },
|
|
48
|
+
{ key: 'titlePosition', label: 'Title position', type: 'select', value: ownSettings.titlePosition || 'none', choices: TITLE_POSITIONS },
|
|
49
|
+
{ key: 'watermark', label: 'Watermark', type: 'text', value: ownSettings.watermark ?? '', placeholder: 'CONFIDENTIAL, DRAFT, ...' },
|
|
50
|
+
{ key: 'watermarkRotation', label: 'Watermark rotation', type: 'select', value: ownSettings.watermarkRotation || '-45°', choices: WATERMARK_ROTATIONS },
|
|
51
|
+
{ key: 'watermarkOpacity', label: 'Watermark opacity', type: 'number', min: 0, max: 100, step: 5, value: ownSettings.watermarkOpacity ?? 20 },
|
|
52
|
+
{ key: 'watermarkRepeat', label: 'Watermark repeat', type: 'select', value: ownSettings.watermarkRepeat || 'none', choices: WATERMARK_REPEATS },
|
|
36
53
|
];
|
|
37
54
|
}
|
|
38
55
|
|
|
39
56
|
applySections(values) {
|
|
57
|
+
// Line style
|
|
40
58
|
const patch = readInheritableFields(EDGE_STYLE_PROPERTIES, values);
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.
|
|
59
|
+
const styleChanged = Object.entries(patch).some(([k, v]) => this.style[k] !== v);
|
|
60
|
+
|
|
61
|
+
// Appearance
|
|
62
|
+
const ownSettings = this.view.viewSettings ?? (this.view.viewSettings = {});
|
|
63
|
+
const appearancePatch = {};
|
|
64
|
+
if ('background' in values) appearancePatch.background = values.background;
|
|
65
|
+
if ('backgroundImage' in values) appearancePatch.backgroundImage = values.backgroundImage?.trim() || undefined;
|
|
66
|
+
if ('bandTitle' in values) appearancePatch.title = values.bandTitle?.trim() || undefined;
|
|
67
|
+
if ('titlePosition' in values) appearancePatch.titlePosition = values.titlePosition && values.titlePosition !== 'none' ? values.titlePosition : undefined;
|
|
68
|
+
if ('watermark' in values) appearancePatch.watermark = values.watermark?.trim() || undefined;
|
|
69
|
+
if ('watermarkRotation' in values) appearancePatch.watermarkRotation = values.watermarkRotation && values.watermarkRotation !== '-45°' ? values.watermarkRotation : undefined;
|
|
70
|
+
if ('watermarkOpacity' in values) appearancePatch.watermarkOpacity = values.watermarkOpacity != null && Number(values.watermarkOpacity) !== 20 ? Number(values.watermarkOpacity) : undefined;
|
|
71
|
+
if ('watermarkRepeat' in values) appearancePatch.watermarkRepeat = values.watermarkRepeat && values.watermarkRepeat !== 'none' ? values.watermarkRepeat : undefined;
|
|
72
|
+
|
|
73
|
+
const appearanceChanged = Object.entries(appearancePatch).some(([k, v]) => ownSettings[k] !== v);
|
|
74
|
+
|
|
75
|
+
if (styleChanged && appearanceChanged) {
|
|
76
|
+
this.view.history.execute(new CompositeCommand('Diagram properties', [
|
|
77
|
+
new SetStyleCommand(this.style, patch, 'Diagram line style'),
|
|
78
|
+
new SetStyleCommand(ownSettings, appearancePatch, 'Diagram appearance'),
|
|
79
|
+
]));
|
|
80
|
+
this.onStyleChange();
|
|
81
|
+
this.onSettingsChange();
|
|
82
|
+
} else if (styleChanged) {
|
|
83
|
+
this.view.history.execute(new SetStyleCommand(this.style, patch, 'Diagram line style'));
|
|
84
|
+
this.onStyleChange();
|
|
85
|
+
} else if (appearanceChanged) {
|
|
86
|
+
this.view.history.execute(new SetStyleCommand(ownSettings, appearancePatch, 'Diagram appearance'));
|
|
87
|
+
this.onSettingsChange();
|
|
88
|
+
}
|
|
45
89
|
}
|
|
46
90
|
}
|
package/src/charting/Edge.js
CHANGED
|
@@ -117,4 +117,26 @@ export class Edge {
|
|
|
117
117
|
clearWaypoints() {
|
|
118
118
|
this.waypoints = [];
|
|
119
119
|
}
|
|
120
|
+
|
|
121
|
+
/** The edge as plain JSON: its endpoints, label, waypoints, side pins and style overrides. */
|
|
122
|
+
toJSON() {
|
|
123
|
+
return {
|
|
124
|
+
id: this.id,
|
|
125
|
+
sourceId: this.sourceId,
|
|
126
|
+
targetId: this.targetId,
|
|
127
|
+
label: this.label,
|
|
128
|
+
waypoints: this.waypoints.map(w => ({ x: w.x, y: w.y })),
|
|
129
|
+
sourceSide: this.sourceSide,
|
|
130
|
+
targetSide: this.targetSide,
|
|
131
|
+
visible: this.visible,
|
|
132
|
+
...this.styleOverrides(),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Reconstruct an Edge from plain JSON. */
|
|
137
|
+
static fromJSON(json) {
|
|
138
|
+
if (!json) throw new TypeError('Edge.fromJSON requires a JSON object');
|
|
139
|
+
const { id, sourceId, targetId, ...rest } = json;
|
|
140
|
+
return new Edge(id, sourceId, targetId, rest);
|
|
141
|
+
}
|
|
120
142
|
}
|