@peter.prib/renderengine 0.2.6 → 0.2.10
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 +70 -9
- package/REFERENCE.md +303 -2
- package/dist/renderengine-workspace.umd.js +8851 -3305
- package/dist/renderengine.umd.js +5621 -1382
- package/examples/archimate.html +68 -66
- package/examples/data-model.html +251 -19
- package/package.json +1 -1
- package/src/bian.js +182 -0
- package/src/charting/DiagramEditor.js +5 -26
- package/src/charting/DiagramTypeRegistry.js +8 -2
- package/src/charting/FormDialog.js +398 -385
- package/src/charting/ModelCheckDialog.js +63 -0
- package/src/charting/ModelChecker.js +26 -0
- package/src/charting/RowsField.js +127 -0
- package/src/charting/ViewTabStrip.js +94 -81
- package/src/charting/datamodel/BianLandscapeReader.js +185 -0
- package/src/charting/datamodel/BianSemanticApiReader.js +183 -0
- package/src/charting/datamodel/ClassificationLens.js +33 -0
- package/src/charting/datamodel/ClassificationScheme.js +98 -0
- package/src/charting/datamodel/DataModel.js +574 -12
- package/src/charting/datamodel/DataModelChecker.js +338 -0
- package/src/charting/datamodel/DataModelCompared.js +127 -0
- package/src/charting/datamodel/DataModelContext.js +40 -10
- package/src/charting/datamodel/DataModelEditCommand.js +47 -0
- package/src/charting/datamodel/DataModelFileFormat.js +33 -0
- package/src/charting/datamodel/DataModelPerspective.js +55 -0
- package/src/charting/datamodel/DataType.js +108 -0
- package/src/charting/datamodel/EnumerationType.js +64 -0
- package/src/charting/datamodel/ServiceDomain.js +186 -0
- package/src/charting/datamodel/ServiceLandscape.js +104 -0
- package/src/charting/datamodel/SqlDdl.js +80 -0
- package/src/charting/datamodel/SqlDdlFileFormat.js +23 -0
- package/src/charting/datamodel/StructuredType.js +63 -0
- package/src/charting/datamodel/index.js +21 -5
- package/src/charting/datamodel/naming/CamelCaseStyle.js +13 -0
- package/src/charting/datamodel/naming/LowerWordsStyle.js +13 -0
- package/src/charting/datamodel/naming/NameStyle.js +57 -0
- package/src/charting/datamodel/naming/NamingStandard.js +132 -0
- package/src/charting/datamodel/naming/PascalCaseStyle.js +13 -0
- package/src/charting/datamodel/naming/SnakeCaseStyle.js +13 -0
- package/src/charting/datamodel/naming/TitleCaseStyle.js +25 -0
- package/src/charting/datamodel/naming/UpperSnakeStyle.js +13 -0
- package/src/charting/datamodel/naming/index.js +8 -0
- package/src/charting/datamodel/scorecard/CompletenessCategory.js +30 -0
- package/src/charting/datamodel/scorecard/ConsistencyCategory.js +33 -0
- package/src/charting/datamodel/scorecard/DefinitionsCategory.js +33 -0
- package/src/charting/datamodel/scorecard/ModelScorecard.js +113 -0
- package/src/charting/datamodel/scorecard/ReadabilityCategory.js +37 -0
- package/src/charting/datamodel/scorecard/ScorecardCategory.js +46 -0
- package/src/charting/datamodel/scorecard/StandardsCategory.js +29 -0
- package/src/charting/datamodel/scorecard/StructureCategory.js +32 -0
- package/src/charting/datamodel/scorecard/index.js +8 -0
- package/src/charting/diagramModel/MergeModelCommand.js +43 -37
- package/src/charting/diagramModel/OpenViewList.js +75 -0
- package/src/charting/diagramModel/ViewSetChecker.js +22 -34
- package/src/charting/diagrams/DataModelDiagram.js +4 -11
- package/src/charting/diagrams/DataModelRelationshipStrategy.js +19 -5
- package/src/charting/diagrams/EdgeGeometry.js +578 -553
- package/src/charting/diagrams/GraphDiagram.js +11 -1
- package/src/charting/diagrams/NodeGeometry.js +173 -158
- package/src/charting/glyphs/PersonGlyph.js +24 -23
- package/src/charting/toolbar/ToolbarSelect.js +6 -0
- package/src/datamodel.js +402 -4
- package/src/index.js +6 -4
- package/src/workspace/EditorShell.js +166 -67
- package/src/workspace/PanelLayout.js +56 -0
- package/src/workspace/layout/TabsNode.js +205 -191
- package/src/workspace/parts/ArchimateCanvasPart.js +5 -7
- package/src/workspace/parts/DataModelPart.js +534 -33
- package/src/workspace/parts/DiagramEditorPart.js +2 -8
- package/src/workspace/parts/EditorPart.js +461 -407
- package/src/workspace/parts/ModelFile.js +216 -187
- package/src/workspace/parts/ModelsPart.js +483 -192
- package/src/workspace/storage/BackupFolder.js +145 -0
- package/src/workspace/storage/KeyValueStore.js +92 -0
- package/src/workspace/storage/ModelLibrary.js +65 -0
- package/src/workspace/workArea.js +417 -413
- package/src/yaml/YamlReader.js +372 -0
package/src/datamodel.js
CHANGED
|
@@ -35,7 +35,19 @@
|
|
|
35
35
|
* the same calibration archimateDiagram's own icons use.
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
/**
|
|
38
|
+
/** Text from a dialog field, trimmed. */
|
|
39
|
+
const text = (v) => String(v ?? '').trim();
|
|
40
|
+
/** Categories as written ('PII, PCI') as a list. */
|
|
41
|
+
const list = (v) => (Array.isArray(v) ? v : String(v ?? '').split(/[,;]/)).map(x => String(x).trim()).filter(Boolean);
|
|
42
|
+
/** Who answers for an entity (DMBOK2's stewardship), in its dialog's order. */
|
|
43
|
+
const STEWARDS = Object.freeze([['owner', 'Business owner'], ['steward', 'Data steward'], ['custodian', 'Technical custodian']]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* One data-modelling abstraction level (see DataModelNotation.levels()): what
|
|
47
|
+
* it is, and what its entities and relationships hold, as the dialogs that
|
|
48
|
+
* edit them show it. A level's entity has a name and a definition; a level
|
|
49
|
+
* whose entities have attributes (AttributedLevel) adds them as a table.
|
|
50
|
+
*/
|
|
39
51
|
export class DataModelLevel {
|
|
40
52
|
constructor(name, description) {
|
|
41
53
|
this.name = name;
|
|
@@ -46,6 +58,195 @@ export class DataModelLevel {
|
|
|
46
58
|
get label() {
|
|
47
59
|
return this.name.charAt(0).toUpperCase() + this.name.slice(1);
|
|
48
60
|
}
|
|
61
|
+
|
|
62
|
+
/** What an entity is called at this level ('Concept', 'Entity', 'Table'). */
|
|
63
|
+
get entityNoun() {
|
|
64
|
+
return 'Entity';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* An entity's dialog fields (FormDialog): its name and definition, who
|
|
69
|
+
* answers for it (business owner, data steward, technical custodian), and
|
|
70
|
+
* how it is classified (a sensitivity of the model's scheme, categories).
|
|
71
|
+
* @param {Object|null} entity - null for a new one
|
|
72
|
+
* @param {Object} [context] - What the fields may offer: `typeNames` (the model's data types),
|
|
73
|
+
* `sensitivities` and `categories` (its classification scheme's)
|
|
74
|
+
*/
|
|
75
|
+
entityFields(entity, context = {}) {
|
|
76
|
+
const { sensitivities = [], categories = [] } = context;
|
|
77
|
+
return [
|
|
78
|
+
{ key: 'name', label: 'Name', type: 'text', value: entity?.name ?? '' },
|
|
79
|
+
{ key: 'description', label: 'Definition', type: 'textarea', rows: 3, value: entity?.description ?? '', placeholder: 'What it is, in the business\'s words' },
|
|
80
|
+
{ type: 'heading', label: 'Stewardship' },
|
|
81
|
+
...STEWARDS.map(([key, label]) => ({ key: `stewardship.${key}`, label, type: 'text', value: entity?.stewardship?.[key] ?? '' })),
|
|
82
|
+
{ type: 'heading', label: 'Classification' },
|
|
83
|
+
{ key: 'sensitivity', label: 'Sensitivity', type: 'select', value: entity?.sensitivity ?? '', choices: [['', 'Unclassified'], ...sensitivities.map(s => [s, s])] },
|
|
84
|
+
{ key: 'categories', label: 'Categories', type: 'text', value: list(entity?.categories).join(', '), placeholder: categories.join(', ') },
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** What those fields say, laid over the entity (what they do not show kept; an empty one taken away). */
|
|
89
|
+
entitySpecFrom(values, entity = null) {
|
|
90
|
+
const out = { ...(entity ?? {}), name: text(values.name) };
|
|
91
|
+
const keep = (key, value) => { if (value === '' || (Array.isArray(value) && !value.length)) delete out[key]; else out[key] = value; };
|
|
92
|
+
keep('description', text(values.description));
|
|
93
|
+
const stewardship = Object.fromEntries(STEWARDS.map(([key]) => [key, text(values[`stewardship.${key}`])]).filter(([, v]) => v));
|
|
94
|
+
keep('stewardship', Object.keys(stewardship).length ? stewardship : '');
|
|
95
|
+
keep('sensitivity', text(values.sensitivity));
|
|
96
|
+
keep('categories', list(values.categories));
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A relationship's dialog fields: its two ends (entities at this level),
|
|
102
|
+
* its name, each end's cardinality, whether it is identifying, a definition.
|
|
103
|
+
* @param {Array<Object>} entities - The level's entities
|
|
104
|
+
* @param {Object|null} relationship - null for a new one
|
|
105
|
+
*/
|
|
106
|
+
relationshipFields(entities, relationship = null) {
|
|
107
|
+
const ends = entities.map(e => [e.id, e.name]);
|
|
108
|
+
const cardinalities = DataModelNotation.cardinalities().map(c => [c, c]);
|
|
109
|
+
return [
|
|
110
|
+
{
|
|
111
|
+
key: 'kind', label: 'Kind', type: 'select', value: RelationshipKind.of(relationship).kind,
|
|
112
|
+
choices: RelationshipKind.all().map(k => [k.kind, k.label]),
|
|
113
|
+
hint: 'A generalization: From is a subtype of To; its cardinalities are not used',
|
|
114
|
+
},
|
|
115
|
+
{ key: 'source', label: 'From', type: 'select', value: relationship?.source ?? ends[0]?.[0] ?? '', choices: ends },
|
|
116
|
+
{ key: 'sourceCardinality', label: 'Its cardinality', type: 'select', value: relationship?.sourceCardinality ?? '1', choices: cardinalities },
|
|
117
|
+
{ key: 'name', label: 'Name', type: 'text', value: relationship?.name ?? '', placeholder: 'has, places, is for…' },
|
|
118
|
+
{ key: 'target', label: 'To', type: 'select', value: relationship?.target ?? ends[1]?.[0] ?? ends[0]?.[0] ?? '', choices: ends },
|
|
119
|
+
{ key: 'targetCardinality', label: 'Its cardinality', type: 'select', value: relationship?.targetCardinality ?? '0..*', choices: cardinalities },
|
|
120
|
+
{ key: 'identifying', label: 'Identifying', type: 'checkbox', value: relationship ? relationship.identifying !== false : true },
|
|
121
|
+
{ key: 'complete', label: 'Complete (generalization)', type: 'checkbox', value: !!relationship?.complete },
|
|
122
|
+
{ key: 'disjoint', label: 'Disjoint (generalization)', type: 'checkbox', value: relationship ? relationship.disjoint !== false : true },
|
|
123
|
+
{ key: 'description', label: 'Definition', type: 'textarea', rows: 2, value: relationship?.description ?? '' },
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** What those fields say, laid over the relationship. */
|
|
128
|
+
relationshipSpecFrom(values, relationship = null) {
|
|
129
|
+
const out = {
|
|
130
|
+
...(relationship ?? {}),
|
|
131
|
+
source: values.source, target: values.target, name: text(values.name),
|
|
132
|
+
sourceCardinality: values.sourceCardinality, targetCardinality: values.targetCardinality,
|
|
133
|
+
};
|
|
134
|
+
if (values.identifying) delete out.identifying; else out.identifying = false;
|
|
135
|
+
if (text(values.description)) out.description = text(values.description); else delete out.description;
|
|
136
|
+
const kind = RelationshipKind.KINDS.get(values.kind) ?? RelationshipKind.of(relationship);
|
|
137
|
+
delete out.complete; delete out.disjoint; delete out.kind;
|
|
138
|
+
if (kind.kind !== AssociationKind.kind) out.kind = kind.kind;
|
|
139
|
+
if (!kind.hasCardinality) {
|
|
140
|
+
delete out.sourceCardinality; delete out.targetCardinality; delete out.identifying;
|
|
141
|
+
out.complete = !!values.complete;
|
|
142
|
+
out.disjoint = values.disjoint !== false;
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A level whose entities have attributes (logical attributes, physical
|
|
150
|
+
* columns): its entity dialog adds them as a table (a FormDialog `rows`
|
|
151
|
+
* field), a row each: the attribute's name, type, keys, whether it is
|
|
152
|
+
* required, what a foreign key references, its definition.
|
|
153
|
+
*/
|
|
154
|
+
export class AttributedLevel extends DataModelLevel {
|
|
155
|
+
/** What its attributes are called ('Attributes', 'Columns'). */
|
|
156
|
+
get attributeNoun() {
|
|
157
|
+
return 'Attributes';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The types its type column suggests. @param {Object} context */
|
|
161
|
+
typeSuggestions(context) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The attribute table's columns. */
|
|
166
|
+
attributeColumns(context = {}) {
|
|
167
|
+
return [
|
|
168
|
+
{ key: 'name', label: 'Name' },
|
|
169
|
+
{ key: 'type', label: 'Type', suggestions: this.typeSuggestions(context), width: '130px' },
|
|
170
|
+
{ key: 'isPK', label: 'PK', type: 'checkbox' },
|
|
171
|
+
{ key: 'isFK', label: 'FK', type: 'checkbox' },
|
|
172
|
+
{ key: 'isUnique', label: 'Unique', type: 'checkbox' },
|
|
173
|
+
{ key: 'required', label: 'Required', type: 'checkbox' },
|
|
174
|
+
{ key: 'references', label: 'References', placeholder: 'entity.attribute', width: '140px' },
|
|
175
|
+
{ key: 'description', label: 'Definition' },
|
|
176
|
+
{ key: 'sensitivity', label: 'Sensitivity', type: 'select', choices: [['', '—'], ...(context.sensitivities ?? []).map(x => [x, x])] },
|
|
177
|
+
{ key: 'categories', label: 'Categories', placeholder: (context.categories ?? []).slice(0, 2).join(', '), width: '90px' },
|
|
178
|
+
{ key: 'measure', label: 'Measure', type: 'select', choices: [['', '—'], ...MEASURES.map(m => [m, m])] },
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
entityFields(entity, context = {}) {
|
|
183
|
+
return [
|
|
184
|
+
...super.entityFields(entity, context),
|
|
185
|
+
{ type: 'heading', label: 'Dimensional' },
|
|
186
|
+
{ key: 'role', label: 'Role', type: 'select', value: EntityRole.of(entity).role, choices: EntityRole.all().map(r => [r.role, r.label]) },
|
|
187
|
+
{ key: 'grain', label: 'Grain (a fact)', type: 'text', value: entity?.grain ?? '', placeholder: 'One row per account per day' },
|
|
188
|
+
{ key: 'scdType', label: 'History (a dimension)', type: 'select', value: entity?.scdType ?? '', choices: [['', '—'], ...DimensionRole.SCD_TYPES] },
|
|
189
|
+
{ key: 'conformed', label: 'Conformed (a dimension)', type: 'checkbox', value: !!entity?.conformed },
|
|
190
|
+
{
|
|
191
|
+
key: 'attributes', label: this.attributeNoun, type: 'rows', columns: this.attributeColumns(context),
|
|
192
|
+
// Categories shown as written, a list read back (entitySpecFrom).
|
|
193
|
+
value: (entity?.attributes ?? []).map(a => (Array.isArray(a.categories) ? { ...a, categories: a.categories.join(', ') } : a)),
|
|
194
|
+
addLabel: this.attributeNoun.replace(/s$/, '').toLowerCase(),
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
entitySpecFrom(values, entity = null) {
|
|
200
|
+
// An attribute's categories as a list, as an entity's are.
|
|
201
|
+
const attributes = (values.attributes ?? []).filter(a => text(a.name)).map(a => {
|
|
202
|
+
if (a.categories === undefined) return a;
|
|
203
|
+
const categories = list(a.categories);
|
|
204
|
+
const { categories: _, ...rest } = a;
|
|
205
|
+
return categories.length ? { ...rest, categories } : rest;
|
|
206
|
+
});
|
|
207
|
+
const out = { ...super.entitySpecFrom(values, entity), attributes };
|
|
208
|
+
// Its role, and only what that role has (a fact's grain, a dimension's history).
|
|
209
|
+
const role = EntityRole.ROLES.get(values.role ?? EntityRole.of(entity).role) ?? EntityRole.of(null);
|
|
210
|
+
for (const key of ['role', 'grain', 'scdType', 'conformed']) delete out[key];
|
|
211
|
+
if (role.role) out.role = role.role;
|
|
212
|
+
if (role.keys.includes('grain') && text(values.grain)) out.grain = text(values.grain);
|
|
213
|
+
if (role.keys.includes('scdType') && values.scdType !== '' && values.scdType != null) out.scdType = String(values.scdType);
|
|
214
|
+
if (role.keys.includes('conformed') && values.conformed) out.conformed = true;
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The logical level: entities and their attributes, typed by the model's data types or plainly. */
|
|
220
|
+
export class LogicalLevel extends AttributedLevel {
|
|
221
|
+
static PLAIN_TYPES = Object.freeze(['Text', 'Number', 'Decimal', 'Amount', 'Date', 'Date/Time', 'Boolean', 'Identifier', 'Code']);
|
|
222
|
+
|
|
223
|
+
typeSuggestions({ typeNames = [] } = {}) {
|
|
224
|
+
return [...typeNames, ...LogicalLevel.PLAIN_TYPES];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The physical level: tables and their columns, typed as a database types them. */
|
|
229
|
+
export class PhysicalLevel extends AttributedLevel {
|
|
230
|
+
static SQL_TYPES = Object.freeze(['VARCHAR(40)', 'TEXT', 'INT', 'BIGINT', 'DECIMAL(18,2)', 'DATE', 'TIMESTAMP', 'BOOLEAN', 'CHAR(3)', 'JSONB']);
|
|
231
|
+
|
|
232
|
+
get entityNoun() {
|
|
233
|
+
return 'Table';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
get attributeNoun() {
|
|
237
|
+
return 'Columns';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
typeSuggestions() {
|
|
241
|
+
return PhysicalLevel.SQL_TYPES;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The conceptual level: concepts with a name and a definition, no attributes. */
|
|
246
|
+
export class ConceptualLevel extends DataModelLevel {
|
|
247
|
+
get entityNoun() {
|
|
248
|
+
return 'Concept';
|
|
249
|
+
}
|
|
49
250
|
}
|
|
50
251
|
|
|
51
252
|
/** The canonical cardinality vocabulary every NotationStyle renders
|
|
@@ -91,9 +292,9 @@ export class NotationStyle {
|
|
|
91
292
|
}
|
|
92
293
|
|
|
93
294
|
const LEVELS = {
|
|
94
|
-
conceptual: new
|
|
95
|
-
logical: new
|
|
96
|
-
physical: new
|
|
295
|
+
conceptual: new ConceptualLevel('conceptual', 'Entities and their relationships only — no attributes, no keys, no types.'),
|
|
296
|
+
logical: new LogicalLevel('logical', 'Attributes and keys, still database-agnostic — no column types or engine specifics.'),
|
|
297
|
+
physical: new PhysicalLevel('physical', 'Real column types, keys, and constraints as they exist in an actual database.'),
|
|
97
298
|
};
|
|
98
299
|
|
|
99
300
|
const NOTATIONS = {
|
|
@@ -120,6 +321,197 @@ const NOTATIONS = {
|
|
|
120
321
|
}),
|
|
121
322
|
};
|
|
122
323
|
|
|
324
|
+
/**
|
|
325
|
+
* RelationshipKind — what a relationship between two entities is: an
|
|
326
|
+
* association (`AssociationKind`, a cardinality at each end, a Chen diamond)
|
|
327
|
+
* or a generalization (`GeneralizationKind`, the source a subtype of the
|
|
328
|
+
* target). A relationship's `kind` names one (none: an association); each
|
|
329
|
+
* draws its own edge in a notation, says whether a notation draws it as a node
|
|
330
|
+
* of its own, and whether it has cardinalities.
|
|
331
|
+
*/
|
|
332
|
+
export class RelationshipKind {
|
|
333
|
+
static kind = '';
|
|
334
|
+
static label = '';
|
|
335
|
+
|
|
336
|
+
/** Relationship kinds by their `kind`. */
|
|
337
|
+
static KINDS = new Map();
|
|
338
|
+
|
|
339
|
+
static register(KindClass) {
|
|
340
|
+
RelationshipKind.KINDS.set(KindClass.kind, new KindClass());
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** A relationship's kind (an association when it names none, or one not known). */
|
|
344
|
+
static of(relationship) {
|
|
345
|
+
return RelationshipKind.KINDS.get(relationship?.kind) ?? RelationshipKind.KINDS.get('association');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Every kind, in the order registered. */
|
|
349
|
+
static all() {
|
|
350
|
+
return [...RelationshipKind.KINDS.values()];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
get kind() { return this.constructor.kind; }
|
|
354
|
+
get label() { return this.constructor.label; }
|
|
355
|
+
|
|
356
|
+
/** Whether it has a cardinality at each end. */
|
|
357
|
+
get hasCardinality() {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Whether a notation draws it as a node of its own (Chen's diamond). */
|
|
362
|
+
drawnAsNode(notation) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** The graphDiagram edge it is in a notation. @abstract */
|
|
367
|
+
edge(relationship, notation) {
|
|
368
|
+
throw new Error(`${this.constructor.name} must implement edge()`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** An association: a cardinality at each end, drawn as each notation draws one (a Chen diamond). */
|
|
373
|
+
export class AssociationKind extends RelationshipKind {
|
|
374
|
+
static kind = 'association';
|
|
375
|
+
static label = 'Association';
|
|
376
|
+
|
|
377
|
+
drawnAsNode(notation) {
|
|
378
|
+
return !!notation.relationshipAsNode;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
edge(relationship, notation) {
|
|
382
|
+
return {
|
|
383
|
+
...relationship,
|
|
384
|
+
label: relationship.name,
|
|
385
|
+
lineStyle: relationship.identifying === false ? 'dashed' : 'solid',
|
|
386
|
+
arrowheadStart: notation.cardinalityArrowhead(relationship.sourceCardinality) || 'none',
|
|
387
|
+
arrowhead: notation.cardinalityArrowhead(relationship.targetCardinality) || 'none',
|
|
388
|
+
sourceLabel: notation.cardinalityLabel(relationship.sourceCardinality),
|
|
389
|
+
targetLabel: notation.cardinalityLabel(relationship.targetCardinality),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* A generalization (DMBOK2's subtypes and supertypes): the source a subtype
|
|
396
|
+
* of the target. `complete` (every instance of the supertype is one of its
|
|
397
|
+
* subtypes) and `disjoint` (an instance is one subtype at most) are said on
|
|
398
|
+
* it. Drawn in every notation as a line to the supertype ending in a hollow
|
|
399
|
+
* triangle, its constraints its label (Chen's an ISA one); no cardinalities.
|
|
400
|
+
*/
|
|
401
|
+
export class GeneralizationKind extends RelationshipKind {
|
|
402
|
+
static kind = 'generalization';
|
|
403
|
+
static label = 'Generalization (subtype of)';
|
|
404
|
+
|
|
405
|
+
get hasCardinality() {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Its constraints in words: '{complete, disjoint}'. */
|
|
410
|
+
static constraints(relationship) {
|
|
411
|
+
return `{${relationship.complete ? 'complete' : 'incomplete'}, ${relationship.disjoint === false ? 'overlapping' : 'disjoint'}}`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
edge(relationship, notation) {
|
|
415
|
+
const { sourceCardinality, targetCardinality, ...rest } = relationship;
|
|
416
|
+
const words = GeneralizationKind.constraints(relationship);
|
|
417
|
+
return {
|
|
418
|
+
...rest,
|
|
419
|
+
label: notation.relationshipAsNode ? `ISA ${words}` : words,
|
|
420
|
+
lineStyle: 'solid', arrowhead: 'triangle-hollow', arrowheadStart: 'none',
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
RelationshipKind.register(AssociationKind);
|
|
426
|
+
RelationshipKind.register(GeneralizationKind);
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* EntityRole — what an entity is in a dimensional scheme (DMBOK2's data
|
|
430
|
+
* warehousing and BI, after Kimball): a plain entity (`EntityRole`, none), a
|
|
431
|
+
* fact (`FactRole`: what is measured, at a stated `grain`, its attributes
|
|
432
|
+
* `measure`s) or a dimension (`DimensionRole`: what facts are looked at by,
|
|
433
|
+
* its `scdType` how it keeps history, `conformed` when facts share it). An
|
|
434
|
+
* entity's `role` names one; each says how it is drawn in the star view.
|
|
435
|
+
*/
|
|
436
|
+
export class EntityRole {
|
|
437
|
+
static role = '';
|
|
438
|
+
static label = 'None';
|
|
439
|
+
/** The colour its entities are drawn in, in the star view. */
|
|
440
|
+
static color = null;
|
|
441
|
+
|
|
442
|
+
/** Entity roles by their `role` ('' the plain one). */
|
|
443
|
+
static ROLES = new Map();
|
|
444
|
+
|
|
445
|
+
static register(RoleClass) {
|
|
446
|
+
EntityRole.ROLES.set(RoleClass.role, new RoleClass());
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** An entity's role (the plain one when it names none, or one not known). */
|
|
450
|
+
static of(entity) {
|
|
451
|
+
return EntityRole.ROLES.get(entity?.role ?? '') ?? EntityRole.ROLES.get('');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
static all() {
|
|
455
|
+
return [...EntityRole.ROLES.values()];
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
get role() { return this.constructor.role; }
|
|
459
|
+
get label() { return this.constructor.label; }
|
|
460
|
+
get color() { return this.constructor.color; }
|
|
461
|
+
|
|
462
|
+
/** What is said of an entity in this role, for a hover: `{}` here. */
|
|
463
|
+
details(entity) {
|
|
464
|
+
return {};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** The keys this role adds to an entity (FactRole's grain...): none here. */
|
|
468
|
+
get keys() {
|
|
469
|
+
return [];
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** A fact: measured, at a grain. */
|
|
474
|
+
export class FactRole extends EntityRole {
|
|
475
|
+
static role = 'fact';
|
|
476
|
+
static label = 'Fact';
|
|
477
|
+
static color = '#fde68a';
|
|
478
|
+
|
|
479
|
+
details(entity) {
|
|
480
|
+
return { role: 'fact', ...(entity.grain && { grain: entity.grain }) };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
get keys() {
|
|
484
|
+
return ['grain'];
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** A dimension: what facts are looked at by. */
|
|
489
|
+
export class DimensionRole extends EntityRole {
|
|
490
|
+
static role = 'dimension';
|
|
491
|
+
static label = 'Dimension';
|
|
492
|
+
static color = '#bfdbfe';
|
|
493
|
+
/** Slowly changing dimension types: how a dimension keeps history. */
|
|
494
|
+
static SCD_TYPES = Object.freeze([['0', 'Type 0: never changes'], ['1', 'Type 1: overwritten'], ['2', 'Type 2: a row per version'], ['3', 'Type 3: the previous value kept']]);
|
|
495
|
+
|
|
496
|
+
details(entity) {
|
|
497
|
+
return {
|
|
498
|
+
role: entity.conformed ? 'conformed dimension' : 'dimension',
|
|
499
|
+
...(entity.scdType !== undefined && entity.scdType !== '' && { history: DimensionRole.SCD_TYPES.find(([t]) => t === String(entity.scdType))?.[1] ?? `type ${entity.scdType}` }),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
get keys() {
|
|
504
|
+
return ['scdType', 'conformed'];
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
EntityRole.register(EntityRole);
|
|
509
|
+
EntityRole.register(FactRole);
|
|
510
|
+
EntityRole.register(DimensionRole);
|
|
511
|
+
|
|
512
|
+
/** How a fact's measure adds up across its dimensions. */
|
|
513
|
+
const MEASURES = Object.freeze(['additive', 'semi-additive', 'non-additive']);
|
|
514
|
+
|
|
123
515
|
export const DataModelNotation = {
|
|
124
516
|
levels() { return Object.values(LEVELS); },
|
|
125
517
|
levelNames() { return Object.keys(LEVELS); },
|
|
@@ -129,4 +521,10 @@ export const DataModelNotation = {
|
|
|
129
521
|
notation(name) { return NOTATIONS[name] || NOTATIONS.crowsfoot; },
|
|
130
522
|
isCardinality(value) { return CARDINALITIES.has(value); },
|
|
131
523
|
cardinalities() { return [...CARDINALITIES]; },
|
|
524
|
+
/** A relationship's kind (RelationshipKind). */
|
|
525
|
+
relationshipKind(relationship) { return RelationshipKind.of(relationship); },
|
|
526
|
+
/** An entity's role in a dimensional scheme (EntityRole). */
|
|
527
|
+
entityRole(entity) { return EntityRole.of(entity); },
|
|
528
|
+
/** How a fact's measures add up: additive, semi-additive, non-additive. */
|
|
529
|
+
measures() { return [...MEASURES]; },
|
|
132
530
|
};
|
package/src/index.js
CHANGED
|
@@ -164,8 +164,9 @@ import { buildModelTreePanel } from './charting/modelTree/ModelTreePanel.js';
|
|
|
164
164
|
import { buildAnalysisPanel, buildAnalysisData } from './charting/modelTree/AnalysisPanel.js';
|
|
165
165
|
import { buildTypeCategoryTree, buildPropertyTree } from './charting/modelTree/buildFilterTrees.js';
|
|
166
166
|
import { DiagramTabs } from './charting/modelTree/DiagramTabs.js';
|
|
167
|
-
import { DataModelNotation, DataModelLevel, NotationStyle } from './datamodel.js';
|
|
168
|
-
import { DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping } from './charting/datamodel/index.js';
|
|
167
|
+
import { DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole } from './datamodel.js';
|
|
168
|
+
import { DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping, ServiceDomain, DataModelFileFormat, ServiceLandscape, BianLandscapeReader, BianSemanticApiReader, DataType, EnumerationType, StructuredType, NamingStandard, NameStyle, ClassificationScheme, ClassificationLens, SqlDdl, SqlDdlFileFormat, ModelScorecard, ScorecardCategory, MeasuredCategory } from './charting/datamodel/index.js';
|
|
169
|
+
import { BianNotation, BianFunctionalPattern, BianActionTerm } from './bian.js';
|
|
169
170
|
import {
|
|
170
171
|
ARCHI_VISUAL_OBJECTS, ARCHI_VIEW_TYPES,
|
|
171
172
|
parseXML, parseArchiModel, toRenderEngineModel, toArchimateModel, toArchiXML, fromArchimateView, toArchiExport,
|
|
@@ -221,8 +222,9 @@ export {
|
|
|
221
222
|
ModelTree, TreeModeRegistry,
|
|
222
223
|
RadialLayout, DerivedRelationships, EdgeGeometry,
|
|
223
224
|
buildModelTreePanel, buildAnalysisPanel, buildAnalysisData, buildTypeCategoryTree, buildPropertyTree, DiagramTabs,
|
|
224
|
-
DataModelNotation, DataModelLevel, NotationStyle,
|
|
225
|
-
DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping,
|
|
225
|
+
DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole,
|
|
226
|
+
DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping, ServiceDomain, DataModelFileFormat, ServiceLandscape, BianLandscapeReader, BianSemanticApiReader, DataType, EnumerationType, StructuredType, NamingStandard, NameStyle, ClassificationScheme, ClassificationLens, SqlDdl, SqlDdlFileFormat, ModelScorecard, ScorecardCategory, MeasuredCategory,
|
|
227
|
+
BianNotation, BianFunctionalPattern, BianActionTerm,
|
|
226
228
|
exportElementsCSV, exportRelationshipsCSV, exportModelCSV,
|
|
227
229
|
importElementsCSV, importRelationshipsCSV, importModelCSV,
|
|
228
230
|
parseArchiModel, toArchimateModel, fromArchimateView, toArchiExport,
|