@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
|
@@ -24,6 +24,16 @@ import {
|
|
|
24
24
|
const EDGE_HANDLE_ATTR = 'data-edge-handle';
|
|
25
25
|
|
|
26
26
|
export class GraphDiagram extends Diagram {
|
|
27
|
+
/**
|
|
28
|
+
* Pure. Whether a node without a picture shows centred text: its icon text,
|
|
29
|
+
* or (standing in for a name drawn below the shape) its initials. A node
|
|
30
|
+
* whose name is written inside it needs no initials.
|
|
31
|
+
*/
|
|
32
|
+
static showsInitials(node, defaultLabelPosition = 'below') {
|
|
33
|
+
if (node.icon) return true;
|
|
34
|
+
return (node.labelPosition || (node.shape === 'note' ? 'inside' : defaultLabelPosition)) !== 'inside';
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
static diagramType = 'graph';
|
|
28
38
|
|
|
29
39
|
constructor(renderer, nodes, edges, options = {}, engine = null, folders = []) {
|
|
@@ -522,7 +532,7 @@ export class GraphDiagram extends Diagram {
|
|
|
522
532
|
if (node.iconUrl) {
|
|
523
533
|
const iconSize = nodeR * 1.3;
|
|
524
534
|
g.appendChild(this.renderer.image(node.iconUrl, -iconSize / 2, -iconSize / 2, iconSize, iconSize, { 'pointer-events': 'none' }));
|
|
525
|
-
} else {
|
|
535
|
+
} else if (GraphDiagram.showsInitials(node, defaultLabelPosition)) {
|
|
526
536
|
const centerText = node.icon || resolvedLabel.slice(0, 2).toUpperCase() || (node.id ?? '').slice(0, 2).toUpperCase();
|
|
527
537
|
const fontSize = node.icon ? Math.max(12, Math.round(nodeR * 1.1)) : 10;
|
|
528
538
|
g.appendChild(this.renderer.text(centerText, 0, 0, { fill: '#fff', textAnchor: 'middle', verticalAlign: 'middle', fontSize: `${fontSize}px`, fontWeight: node.icon ? 'normal' : 'bold', 'pointer-events': 'none' }));
|
|
@@ -1,158 +1,173 @@
|
|
|
1
|
-
import { Glyph, glyphFor, drawGlyph } from '../glyphs/index.js';
|
|
2
|
-
import { GraphMetrics } from './GraphMetrics.js';
|
|
3
|
-
import { LabelHelper } from './LabelHelper.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* NodeGeometry — pure, stateless helpers for graph node sizing, corners,
|
|
7
|
-
* resize-handle math, and table-node geometry.
|
|
8
|
-
*
|
|
9
|
-
* These used to live on RenderEngine; they are collected here so diagram
|
|
10
|
-
* classes can compute node geometry without going through the engine façade.
|
|
11
|
-
*/
|
|
12
|
-
export class NodeGeometry {
|
|
13
|
-
static TABLE_NODE = { width: 170, headerH: 26, rowH: 18, pad: 6 };
|
|
14
|
-
|
|
15
|
-
/** Bounding box a node occupies, centred on its (x,y) — {w:2R,h:2R} for
|
|
16
|
-
* the plain default (sized by GraphMetrics.effectiveNodeRadius when a
|
|
17
|
-
* `sizeDomain` is supplied), the real card size for a `shape:'table'`
|
|
18
|
-
* node (see NodeGeometry.tableNodeSize), or, for any OTHER shape, an
|
|
19
|
-
* explicit `node.width`/`node.height` override for a wider-than-tall box. */
|
|
20
|
-
static nodeSize(node, R, sizeDomain, sizeOptions) {
|
|
21
|
-
if (node?.shape === 'table') return NodeGeometry.tableNodeSize(node);
|
|
22
|
-
const radius = GraphMetrics ? GraphMetrics.effectiveNodeRadius(node, R, sizeDomain, sizeOptions) : R;
|
|
23
|
-
if (Number.isFinite(node?.width) || Number.isFinite(node?.height)) {
|
|
24
|
-
return {
|
|
25
|
-
w: Number.isFinite(node.width) ? node.width : radius * 2,
|
|
26
|
-
h: Number.isFinite(node.height) ? node.height : radius * 2,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
return { w: radius * 2, h: radius * 2 };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Pure.
|
|
115
|
-
*
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
1
|
+
import { Glyph, glyphFor, drawGlyph } from '../glyphs/index.js';
|
|
2
|
+
import { GraphMetrics } from './GraphMetrics.js';
|
|
3
|
+
import { LabelHelper } from './LabelHelper.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* NodeGeometry — pure, stateless helpers for graph node sizing, corners,
|
|
7
|
+
* resize-handle math, and table-node geometry.
|
|
8
|
+
*
|
|
9
|
+
* These used to live on RenderEngine; they are collected here so diagram
|
|
10
|
+
* classes can compute node geometry without going through the engine façade.
|
|
11
|
+
*/
|
|
12
|
+
export class NodeGeometry {
|
|
13
|
+
static TABLE_NODE = { width: 170, headerH: 26, rowH: 18, pad: 6 };
|
|
14
|
+
|
|
15
|
+
/** Bounding box a node occupies, centred on its (x,y) — {w:2R,h:2R} for
|
|
16
|
+
* the plain default (sized by GraphMetrics.effectiveNodeRadius when a
|
|
17
|
+
* `sizeDomain` is supplied), the real card size for a `shape:'table'`
|
|
18
|
+
* node (see NodeGeometry.tableNodeSize), or, for any OTHER shape, an
|
|
19
|
+
* explicit `node.width`/`node.height` override for a wider-than-tall box. */
|
|
20
|
+
static nodeSize(node, R, sizeDomain, sizeOptions) {
|
|
21
|
+
if (node?.shape === 'table') return NodeGeometry.tableNodeSize(node);
|
|
22
|
+
const radius = GraphMetrics ? GraphMetrics.effectiveNodeRadius(node, R, sizeDomain, sizeOptions) : R;
|
|
23
|
+
if (Number.isFinite(node?.width) || Number.isFinite(node?.height)) {
|
|
24
|
+
return {
|
|
25
|
+
w: Number.isFinite(node.width) ? node.width : radius * 2,
|
|
26
|
+
h: Number.isFinite(node.height) ? node.height : radius * 2,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { w: radius * 2, h: radius * 2 };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A shape's visible outline for a node of `size`, as a closed polygon centred
|
|
34
|
+
* on (0, 0) (its glyph's `outline`: a curve sampled, a box its box), or null
|
|
35
|
+
* for a shape no glyph draws.
|
|
36
|
+
*/
|
|
37
|
+
static outline(shape, size) {
|
|
38
|
+
const glyph = glyphFor(shape);
|
|
39
|
+
if (!glyph || glyph.constructor.key !== shape) return null; // glyphFor falls back to a circle for one it lacks
|
|
40
|
+
return glyph.outline(Glyph.context({ R: Math.max(size.w, size.h) / 2, size }));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Size of a `shape:'table'` node — column-count-driven, never affected
|
|
44
|
+
* by node.size/sizeDomain. */
|
|
45
|
+
static tableNodeSize(node) {
|
|
46
|
+
const { width, headerH, rowH, pad } = NodeGeometry.TABLE_NODE;
|
|
47
|
+
const cols = node.columns?.length ?? 0;
|
|
48
|
+
return { w: node.width || width, h: headerH + cols * rowH + pad };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** World-space Y of a named column's row within a table node, for edge anchoring. */
|
|
52
|
+
static tableColumnY(node, posEntry, size, colName) {
|
|
53
|
+
const { headerH, rowH } = NodeGeometry.TABLE_NODE;
|
|
54
|
+
const idx = Math.max(0, (node.columns ?? []).findIndex(c => c.name === colName));
|
|
55
|
+
return posEntry.y - size.h / 2 + headerH + idx * rowH + rowH / 2;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Draws a table-card node's header + one row per column into wrapper `g`;
|
|
60
|
+
* the node's own `color` tints the card's body (a lens, a marked control
|
|
61
|
+
* record), the header staying as it is so its name reads.
|
|
62
|
+
*/
|
|
63
|
+
static buildTableNodeContent(renderer, g, node) {
|
|
64
|
+
const { headerH, rowH } = NodeGeometry.TABLE_NODE;
|
|
65
|
+
const { w, h } = NodeGeometry.tableNodeSize(node);
|
|
66
|
+
const cols = node.columns ?? [];
|
|
67
|
+
const x0 = -w / 2, y0 = -h / 2;
|
|
68
|
+
const borderColor = node.selected ? '#4f8ef7' : '#cfd8dc';
|
|
69
|
+
const borderWidth = node.selected ? 2 : 1;
|
|
70
|
+
|
|
71
|
+
const TYPE_COLORS = { NUMBER: '#74b9ff', TEXT: '#a29bfe', TIMESTAMP: '#55efc4', BOOLEAN: '#ffeaa7', JSON: '#fd79a8', UNKNOWN: '#dfe6e9' };
|
|
72
|
+
g.appendChild(renderer.rect(x0, y0, w, h, node.color || '#ffffff', { color: borderColor, width: borderWidth }));
|
|
73
|
+
g.appendChild(renderer.rect(x0, y0, w, headerH, '#1976d2', null));
|
|
74
|
+
g.appendChild(renderer.text(LabelHelper.resolveNodeLabel(node), x0 + 8, y0 + headerH / 2, {
|
|
75
|
+
fill: '#fff', textAnchor: 'start', verticalAlign: 'middle', fontSize: '11px', fontWeight: 'bold', 'pointer-events': 'none',
|
|
76
|
+
}));
|
|
77
|
+
if (node.rowCount != null) {
|
|
78
|
+
g.appendChild(renderer.text(String(node.rowCount), x0 + w - 8, y0 + headerH / 2, {
|
|
79
|
+
fill: '#cfe3fb', textAnchor: 'end', verticalAlign: 'middle', fontSize: '9px', 'pointer-events': 'none',
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
cols.forEach((col, i) => {
|
|
84
|
+
const rowY = y0 + headerH + i * rowH;
|
|
85
|
+
g.appendChild(renderer.circle(x0 + 12, rowY + rowH / 2, 3.5, TYPE_COLORS[col.typeGroup] ?? TYPE_COLORS.UNKNOWN, null));
|
|
86
|
+
g.appendChild(renderer.text(col.name, x0 + 20, rowY + rowH / 2, {
|
|
87
|
+
fill: '#333', textAnchor: 'start', verticalAlign: 'middle', fontSize: '10px', 'pointer-events': 'none',
|
|
88
|
+
}));
|
|
89
|
+
let badgeX = x0 + w - 8;
|
|
90
|
+
if (col.isFK) {
|
|
91
|
+
g.appendChild(renderer.text('FK', badgeX, rowY + rowH / 2, {
|
|
92
|
+
fill: '#e67e22', textAnchor: 'end', verticalAlign: 'middle', fontSize: '8px', fontWeight: 'bold', 'pointer-events': 'none',
|
|
93
|
+
}));
|
|
94
|
+
badgeX -= 18;
|
|
95
|
+
}
|
|
96
|
+
if (col.isPK) {
|
|
97
|
+
g.appendChild(renderer.text('PK', badgeX, rowY + rowH / 2, {
|
|
98
|
+
fill: '#00b894', textAnchor: 'end', verticalAlign: 'middle', fontSize: '8px', fontWeight: 'bold', 'pointer-events': 'none',
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Pure. The point at a given corner of a `size` (`{w,h}`) box centred on
|
|
105
|
+
* `pos` — shared by the resize handle's screen positioning and a node's
|
|
106
|
+
* icon-badge placement. */
|
|
107
|
+
static cornerPoint(pos, size, corner) {
|
|
108
|
+
return {
|
|
109
|
+
x: pos.x + (corner.includes('right') ? size.w / 2 : -size.w / 2),
|
|
110
|
+
y: pos.y + (corner.includes('bottom') ? size.h / 2 : -size.h / 2),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Pure. Generic 3-tier corner resolution — 'top-left'|'top-right'|
|
|
115
|
+
* 'bottom-left'|'bottom-right': a per-node `node[nodeKey]` override,
|
|
116
|
+
* then a per-shape entry in `byShape` (keyed by `node.shape`, default
|
|
117
|
+
* shape 'circle'), then `defaultCorner`. */
|
|
118
|
+
static resolveNodeCorner(node, nodeKey, byShape, defaultCorner) {
|
|
119
|
+
if (node?.[nodeKey]) return node[nodeKey];
|
|
120
|
+
const shapeCorner = byShape?.[node?.shape || 'circle'];
|
|
121
|
+
return shapeCorner || defaultCorner;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Pure. Which corner a node's resize handle sits at/drags from. */
|
|
125
|
+
static nodeResizeCorner(node, resizeCornerByShape, defaultCorner = 'top-right') {
|
|
126
|
+
return NodeGeometry.resolveNodeCorner(node, 'resizeCorner', resizeCornerByShape, defaultCorner);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Pure. Which corner a node's icon badge sits at when `iconPosition`
|
|
130
|
+
* is `'corner'`. */
|
|
131
|
+
static nodeIconCorner(node, iconCornerByShape, defaultCorner = 'top-right') {
|
|
132
|
+
return NodeGeometry.resolveNodeCorner(node, 'iconCorner', iconCornerByShape, defaultCorner);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Pure. What dragging a node's resize handle to `mousePos` should set on
|
|
136
|
+
* the node. */
|
|
137
|
+
static resizedNodeValue(node, corner, center, mousePos) {
|
|
138
|
+
const signX = corner.includes('right') ? 1 : -1;
|
|
139
|
+
const signY = corner.includes('bottom') ? 1 : -1;
|
|
140
|
+
if (node?.shape === 'table') {
|
|
141
|
+
const MIN_TABLE_WIDTH = 80;
|
|
142
|
+
return { width: Math.max(MIN_TABLE_WIDTH, Math.round(Math.abs(mousePos.x - center.x) * 2)) };
|
|
143
|
+
}
|
|
144
|
+
if (node?.shape === 'rect') {
|
|
145
|
+
const MIN_RECT_SIZE = 24;
|
|
146
|
+
const width = Math.max(MIN_RECT_SIZE, Math.round((mousePos.x - center.x) * signX * 2));
|
|
147
|
+
const height = Math.max(MIN_RECT_SIZE, Math.round((mousePos.y - center.y) * signY * 2));
|
|
148
|
+
return { width, height };
|
|
149
|
+
}
|
|
150
|
+
const MIN_RADIUS = 4;
|
|
151
|
+
const projected = ((mousePos.x - center.x) * signX + (mousePos.y - center.y) * signY) / 2;
|
|
152
|
+
return { radius: Math.max(MIN_RADIUS, Math.round(projected)) };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Pure. SVG path `d` for a `w`x`h` rectangle centred on the origin with
|
|
156
|
+
* all four corners cut at 45° (an octagon) — see nodeShapeEl's
|
|
157
|
+
* 'chamfered' cornerStyle. `chamfer` (the cut's size) defaults to a
|
|
158
|
+
* fraction of the box's smaller dimension so it scales sensibly with
|
|
159
|
+
* the box rather than needing its own per-node configuration. */
|
|
160
|
+
static chamferedRectPath(w, h, chamfer) {
|
|
161
|
+
return Glyph.chamferedRectPath(w, h, chamfer);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Build the SVG element for a node's shape. Returns `{el, suppressInner}`
|
|
165
|
+
* where `suppressInner` tells the caller not to draw a centred icon or
|
|
166
|
+
* initials on top of busy distinct shapes. The shapes themselves are
|
|
167
|
+
* Glyphs (src/charting/glyphs), shared with the editor's templates. */
|
|
168
|
+
static nodeShapeEl(renderer, shape, R, color, size, cornerStyle, strokeColor = '#fff', strokeWidth = 2, iconUrl = null, icon = null) {
|
|
169
|
+
const glyph = glyphFor(shape);
|
|
170
|
+
const ctx = Glyph.context({ R, size, color, cornerStyle, strokeColor, strokeWidth, iconUrl, icon });
|
|
171
|
+
return { el: drawGlyph(renderer, glyph.parts(ctx)), suppressInner: glyph.constructor.ownsInterior };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { Glyph } from './Glyph.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
static
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
G.circle(0,
|
|
20
|
-
G.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
1
|
+
import { Glyph } from './Glyph.js';
|
|
2
|
+
import { CircleGlyph } from './CircleGlyph.js';
|
|
3
|
+
|
|
4
|
+
/** A disc with a white head-and-shoulders silhouette: a circle, its outline the circle's. */
|
|
5
|
+
export class PersonGlyph extends CircleGlyph {
|
|
6
|
+
static key = 'person';
|
|
7
|
+
static ownsInterior = true;
|
|
8
|
+
|
|
9
|
+
parts(ctx) {
|
|
10
|
+
const G = Glyph, st = (k) => this.stroke(ctx, k);
|
|
11
|
+
const r = ctx.circleR;
|
|
12
|
+
const headR = r * 0.30;
|
|
13
|
+
const headY = -(r * 0.28);
|
|
14
|
+
const neckY = headY + headR + r * 0.05;
|
|
15
|
+
const bodyW = r * 0.55;
|
|
16
|
+
const bodyH = r * 0.88;
|
|
17
|
+
const bodyPath = `M ${-bodyW},${bodyH} Q ${-bodyW},${neckY} 0,${neckY} Q ${bodyW},${neckY} ${bodyW},${bodyH} Z`;
|
|
18
|
+
return G.group([
|
|
19
|
+
G.circle(0, 0, r, ctx.color, st()),
|
|
20
|
+
G.circle(0, headY, headR, '#fff', null, { opacity: 0.92 }),
|
|
21
|
+
G.path(bodyPath, '#fff', null, { opacity: 0.92 }),
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -35,4 +35,10 @@ export class ToolbarSelect extends ToolbarItem {
|
|
|
35
35
|
],
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/** Show the value as it is now (it may have been chosen other than through this select). */
|
|
40
|
+
refresh(node) {
|
|
41
|
+
const select = [...(node?.children ?? [])].find(c => c.tagName === 'SELECT');
|
|
42
|
+
if (select) select.value = this.value();
|
|
43
|
+
}
|
|
38
44
|
}
|