@peter.prib/renderengine 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +4 -0
  2. package/REFERENCE.md +180 -2
  3. package/dist/renderengine-workspace.umd.js +8704 -5310
  4. package/dist/renderengine.umd.js +5675 -3398
  5. package/examples/archimate.html +68 -66
  6. package/examples/data-model.html +223 -13
  7. package/package.json +1 -1
  8. package/src/bian.js +182 -0
  9. package/src/charting/DiagramEditor.js +5 -26
  10. package/src/charting/DiagramTypeRegistry.js +8 -2
  11. package/src/charting/ModelCheckDialog.js +63 -0
  12. package/src/charting/ModelChecker.js +26 -0
  13. package/src/charting/ViewTabStrip.js +94 -81
  14. package/src/charting/datamodel/BianLandscapeReader.js +185 -0
  15. package/src/charting/datamodel/BianSemanticApiReader.js +183 -0
  16. package/src/charting/datamodel/DataModel.js +356 -10
  17. package/src/charting/datamodel/DataModelChecker.js +92 -0
  18. package/src/charting/datamodel/DataModelCompared.js +127 -0
  19. package/src/charting/datamodel/DataModelFileFormat.js +33 -0
  20. package/src/charting/datamodel/DataModelPerspective.js +45 -0
  21. package/src/charting/datamodel/DataType.js +108 -0
  22. package/src/charting/datamodel/EnumerationType.js +51 -0
  23. package/src/charting/datamodel/ServiceDomain.js +177 -0
  24. package/src/charting/datamodel/ServiceLandscape.js +104 -0
  25. package/src/charting/datamodel/StructuredType.js +63 -0
  26. package/src/charting/datamodel/index.js +10 -0
  27. package/src/charting/diagramModel/MergeModelCommand.js +43 -37
  28. package/src/charting/diagramModel/OpenViewList.js +75 -0
  29. package/src/charting/diagramModel/ViewSetChecker.js +22 -34
  30. package/src/charting/diagrams/DataModelRelationshipStrategy.js +12 -4
  31. package/src/charting/diagrams/EdgeGeometry.js +578 -553
  32. package/src/charting/diagrams/GraphDiagram.js +11 -1
  33. package/src/charting/diagrams/NodeGeometry.js +169 -158
  34. package/src/charting/glyphs/PersonGlyph.js +24 -23
  35. package/src/index.js +2666 -2664
  36. package/src/workspace/EditorShell.js +166 -67
  37. package/src/workspace/PanelLayout.js +56 -0
  38. package/src/workspace/layout/TabsNode.js +205 -191
  39. package/src/workspace/parts/ArchimateCanvasPart.js +5 -7
  40. package/src/workspace/parts/DataModelPart.js +339 -29
  41. package/src/workspace/parts/DiagramEditorPart.js +2 -8
  42. package/src/workspace/parts/EditorPart.js +461 -407
  43. package/src/workspace/parts/ModelFile.js +216 -187
  44. package/src/workspace/parts/ModelsPart.js +483 -192
  45. package/src/workspace/storage/BackupFolder.js +145 -0
  46. package/src/workspace/storage/KeyValueStore.js +92 -0
  47. package/src/workspace/storage/ModelLibrary.js +65 -0
  48. package/src/workspace/workArea.js +417 -413
  49. 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,169 @@
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
- /** Size of a `shape:'table'` node — column-count-driven, never affected
33
- * by node.size/sizeDomain. */
34
- static tableNodeSize(node) {
35
- const { width, headerH, rowH, pad } = NodeGeometry.TABLE_NODE;
36
- const cols = node.columns?.length ?? 0;
37
- return { w: node.width || width, h: headerH + cols * rowH + pad };
38
- }
39
-
40
- /** World-space Y of a named column's row within a table node, for edge anchoring. */
41
- static tableColumnY(node, posEntry, size, colName) {
42
- const { headerH, rowH } = NodeGeometry.TABLE_NODE;
43
- const idx = Math.max(0, (node.columns ?? []).findIndex(c => c.name === colName));
44
- return posEntry.y - size.h / 2 + headerH + idx * rowH + rowH / 2;
45
- }
46
-
47
- /** Draws a table-card node's header + one row per column into wrapper `g`. */
48
- static buildTableNodeContent(renderer, g, node) {
49
- const { headerH, rowH } = NodeGeometry.TABLE_NODE;
50
- const { w, h } = NodeGeometry.tableNodeSize(node);
51
- const cols = node.columns ?? [];
52
- const x0 = -w / 2, y0 = -h / 2;
53
- const borderColor = node.selected ? '#4f8ef7' : '#cfd8dc';
54
- const borderWidth = node.selected ? 2 : 1;
55
-
56
- const TYPE_COLORS = { NUMBER: '#74b9ff', TEXT: '#a29bfe', TIMESTAMP: '#55efc4', BOOLEAN: '#ffeaa7', JSON: '#fd79a8', UNKNOWN: '#dfe6e9' };
57
- g.appendChild(renderer.rect(x0, y0, w, h, '#ffffff', { color: borderColor, width: borderWidth }));
58
- g.appendChild(renderer.rect(x0, y0, w, headerH, '#1976d2', null));
59
- g.appendChild(renderer.text(LabelHelper.resolveNodeLabel(node), x0 + 8, y0 + headerH / 2, {
60
- fill: '#fff', textAnchor: 'start', verticalAlign: 'middle', fontSize: '11px', fontWeight: 'bold', 'pointer-events': 'none',
61
- }));
62
- if (node.rowCount != null) {
63
- g.appendChild(renderer.text(String(node.rowCount), x0 + w - 8, y0 + headerH / 2, {
64
- fill: '#cfe3fb', textAnchor: 'end', verticalAlign: 'middle', fontSize: '9px', 'pointer-events': 'none',
65
- }));
66
- }
67
-
68
- cols.forEach((col, i) => {
69
- const rowY = y0 + headerH + i * rowH;
70
- g.appendChild(renderer.circle(x0 + 12, rowY + rowH / 2, 3.5, TYPE_COLORS[col.typeGroup] ?? TYPE_COLORS.UNKNOWN, null));
71
- g.appendChild(renderer.text(col.name, x0 + 20, rowY + rowH / 2, {
72
- fill: '#333', textAnchor: 'start', verticalAlign: 'middle', fontSize: '10px', 'pointer-events': 'none',
73
- }));
74
- let badgeX = x0 + w - 8;
75
- if (col.isFK) {
76
- g.appendChild(renderer.text('FK', badgeX, rowY + rowH / 2, {
77
- fill: '#e67e22', textAnchor: 'end', verticalAlign: 'middle', fontSize: '8px', fontWeight: 'bold', 'pointer-events': 'none',
78
- }));
79
- badgeX -= 18;
80
- }
81
- if (col.isPK) {
82
- g.appendChild(renderer.text('PK', badgeX, rowY + rowH / 2, {
83
- fill: '#00b894', textAnchor: 'end', verticalAlign: 'middle', fontSize: '8px', fontWeight: 'bold', 'pointer-events': 'none',
84
- }));
85
- }
86
- });
87
- }
88
-
89
- /** Pure. The point at a given corner of a `size` (`{w,h}`) box centred on
90
- * `pos` — shared by the resize handle's screen positioning and a node's
91
- * icon-badge placement. */
92
- static cornerPoint(pos, size, corner) {
93
- return {
94
- x: pos.x + (corner.includes('right') ? size.w / 2 : -size.w / 2),
95
- y: pos.y + (corner.includes('bottom') ? size.h / 2 : -size.h / 2),
96
- };
97
- }
98
-
99
- /** Pure. Generic 3-tier corner resolution — 'top-left'|'top-right'|
100
- * 'bottom-left'|'bottom-right': a per-node `node[nodeKey]` override,
101
- * then a per-shape entry in `byShape` (keyed by `node.shape`, default
102
- * shape 'circle'), then `defaultCorner`. */
103
- static resolveNodeCorner(node, nodeKey, byShape, defaultCorner) {
104
- if (node?.[nodeKey]) return node[nodeKey];
105
- const shapeCorner = byShape?.[node?.shape || 'circle'];
106
- return shapeCorner || defaultCorner;
107
- }
108
-
109
- /** Pure. Which corner a node's resize handle sits at/drags from. */
110
- static nodeResizeCorner(node, resizeCornerByShape, defaultCorner = 'top-right') {
111
- return NodeGeometry.resolveNodeCorner(node, 'resizeCorner', resizeCornerByShape, defaultCorner);
112
- }
113
-
114
- /** Pure. Which corner a node's icon badge sits at when `iconPosition`
115
- * is `'corner'`. */
116
- static nodeIconCorner(node, iconCornerByShape, defaultCorner = 'top-right') {
117
- return NodeGeometry.resolveNodeCorner(node, 'iconCorner', iconCornerByShape, defaultCorner);
118
- }
119
-
120
- /** Pure. What dragging a node's resize handle to `mousePos` should set on
121
- * the node. */
122
- static resizedNodeValue(node, corner, center, mousePos) {
123
- const signX = corner.includes('right') ? 1 : -1;
124
- const signY = corner.includes('bottom') ? 1 : -1;
125
- if (node?.shape === 'table') {
126
- const MIN_TABLE_WIDTH = 80;
127
- return { width: Math.max(MIN_TABLE_WIDTH, Math.round(Math.abs(mousePos.x - center.x) * 2)) };
128
- }
129
- if (node?.shape === 'rect') {
130
- const MIN_RECT_SIZE = 24;
131
- const width = Math.max(MIN_RECT_SIZE, Math.round((mousePos.x - center.x) * signX * 2));
132
- const height = Math.max(MIN_RECT_SIZE, Math.round((mousePos.y - center.y) * signY * 2));
133
- return { width, height };
134
- }
135
- const MIN_RADIUS = 4;
136
- const projected = ((mousePos.x - center.x) * signX + (mousePos.y - center.y) * signY) / 2;
137
- return { radius: Math.max(MIN_RADIUS, Math.round(projected)) };
138
- }
139
-
140
- /** Pure. SVG path `d` for a `w`x`h` rectangle centred on the origin with
141
- * all four corners cut at 45° (an octagon) — see nodeShapeEl's
142
- * 'chamfered' cornerStyle. `chamfer` (the cut's size) defaults to a
143
- * fraction of the box's smaller dimension so it scales sensibly with
144
- * the box rather than needing its own per-node configuration. */
145
- static chamferedRectPath(w, h, chamfer) {
146
- return Glyph.chamferedRectPath(w, h, chamfer);
147
- }
148
-
149
- /** Build the SVG element for a node's shape. Returns `{el, suppressInner}`
150
- * where `suppressInner` tells the caller not to draw a centred icon or
151
- * initials on top of busy distinct shapes. The shapes themselves are
152
- * Glyphs (src/charting/glyphs), shared with the editor's templates. */
153
- static nodeShapeEl(renderer, shape, R, color, size, cornerStyle, strokeColor = '#fff', strokeWidth = 2, iconUrl = null, icon = null) {
154
- const glyph = glyphFor(shape);
155
- const ctx = Glyph.context({ R, size, color, cornerStyle, strokeColor, strokeWidth, iconUrl, icon });
156
- return { el: drawGlyph(renderer, glyph.parts(ctx)), suppressInner: glyph.constructor.ownsInterior };
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
+ /** Draws a table-card node's header + one row per column into wrapper `g`. */
59
+ static buildTableNodeContent(renderer, g, node) {
60
+ const { headerH, rowH } = NodeGeometry.TABLE_NODE;
61
+ const { w, h } = NodeGeometry.tableNodeSize(node);
62
+ const cols = node.columns ?? [];
63
+ const x0 = -w / 2, y0 = -h / 2;
64
+ const borderColor = node.selected ? '#4f8ef7' : '#cfd8dc';
65
+ const borderWidth = node.selected ? 2 : 1;
66
+
67
+ const TYPE_COLORS = { NUMBER: '#74b9ff', TEXT: '#a29bfe', TIMESTAMP: '#55efc4', BOOLEAN: '#ffeaa7', JSON: '#fd79a8', UNKNOWN: '#dfe6e9' };
68
+ g.appendChild(renderer.rect(x0, y0, w, h, '#ffffff', { color: borderColor, width: borderWidth }));
69
+ g.appendChild(renderer.rect(x0, y0, w, headerH, '#1976d2', null));
70
+ g.appendChild(renderer.text(LabelHelper.resolveNodeLabel(node), x0 + 8, y0 + headerH / 2, {
71
+ fill: '#fff', textAnchor: 'start', verticalAlign: 'middle', fontSize: '11px', fontWeight: 'bold', 'pointer-events': 'none',
72
+ }));
73
+ if (node.rowCount != null) {
74
+ g.appendChild(renderer.text(String(node.rowCount), x0 + w - 8, y0 + headerH / 2, {
75
+ fill: '#cfe3fb', textAnchor: 'end', verticalAlign: 'middle', fontSize: '9px', 'pointer-events': 'none',
76
+ }));
77
+ }
78
+
79
+ cols.forEach((col, i) => {
80
+ const rowY = y0 + headerH + i * rowH;
81
+ g.appendChild(renderer.circle(x0 + 12, rowY + rowH / 2, 3.5, TYPE_COLORS[col.typeGroup] ?? TYPE_COLORS.UNKNOWN, null));
82
+ g.appendChild(renderer.text(col.name, x0 + 20, rowY + rowH / 2, {
83
+ fill: '#333', textAnchor: 'start', verticalAlign: 'middle', fontSize: '10px', 'pointer-events': 'none',
84
+ }));
85
+ let badgeX = x0 + w - 8;
86
+ if (col.isFK) {
87
+ g.appendChild(renderer.text('FK', badgeX, rowY + rowH / 2, {
88
+ fill: '#e67e22', textAnchor: 'end', verticalAlign: 'middle', fontSize: '8px', fontWeight: 'bold', 'pointer-events': 'none',
89
+ }));
90
+ badgeX -= 18;
91
+ }
92
+ if (col.isPK) {
93
+ g.appendChild(renderer.text('PK', badgeX, rowY + rowH / 2, {
94
+ fill: '#00b894', textAnchor: 'end', verticalAlign: 'middle', fontSize: '8px', fontWeight: 'bold', 'pointer-events': 'none',
95
+ }));
96
+ }
97
+ });
98
+ }
99
+
100
+ /** Pure. The point at a given corner of a `size` (`{w,h}`) box centred on
101
+ * `pos` — shared by the resize handle's screen positioning and a node's
102
+ * icon-badge placement. */
103
+ static cornerPoint(pos, size, corner) {
104
+ return {
105
+ x: pos.x + (corner.includes('right') ? size.w / 2 : -size.w / 2),
106
+ y: pos.y + (corner.includes('bottom') ? size.h / 2 : -size.h / 2),
107
+ };
108
+ }
109
+
110
+ /** Pure. Generic 3-tier corner resolution — 'top-left'|'top-right'|
111
+ * 'bottom-left'|'bottom-right': a per-node `node[nodeKey]` override,
112
+ * then a per-shape entry in `byShape` (keyed by `node.shape`, default
113
+ * shape 'circle'), then `defaultCorner`. */
114
+ static resolveNodeCorner(node, nodeKey, byShape, defaultCorner) {
115
+ if (node?.[nodeKey]) return node[nodeKey];
116
+ const shapeCorner = byShape?.[node?.shape || 'circle'];
117
+ return shapeCorner || defaultCorner;
118
+ }
119
+
120
+ /** Pure. Which corner a node's resize handle sits at/drags from. */
121
+ static nodeResizeCorner(node, resizeCornerByShape, defaultCorner = 'top-right') {
122
+ return NodeGeometry.resolveNodeCorner(node, 'resizeCorner', resizeCornerByShape, defaultCorner);
123
+ }
124
+
125
+ /** Pure. Which corner a node's icon badge sits at when `iconPosition`
126
+ * is `'corner'`. */
127
+ static nodeIconCorner(node, iconCornerByShape, defaultCorner = 'top-right') {
128
+ return NodeGeometry.resolveNodeCorner(node, 'iconCorner', iconCornerByShape, defaultCorner);
129
+ }
130
+
131
+ /** Pure. What dragging a node's resize handle to `mousePos` should set on
132
+ * the node. */
133
+ static resizedNodeValue(node, corner, center, mousePos) {
134
+ const signX = corner.includes('right') ? 1 : -1;
135
+ const signY = corner.includes('bottom') ? 1 : -1;
136
+ if (node?.shape === 'table') {
137
+ const MIN_TABLE_WIDTH = 80;
138
+ return { width: Math.max(MIN_TABLE_WIDTH, Math.round(Math.abs(mousePos.x - center.x) * 2)) };
139
+ }
140
+ if (node?.shape === 'rect') {
141
+ const MIN_RECT_SIZE = 24;
142
+ const width = Math.max(MIN_RECT_SIZE, Math.round((mousePos.x - center.x) * signX * 2));
143
+ const height = Math.max(MIN_RECT_SIZE, Math.round((mousePos.y - center.y) * signY * 2));
144
+ return { width, height };
145
+ }
146
+ const MIN_RADIUS = 4;
147
+ const projected = ((mousePos.x - center.x) * signX + (mousePos.y - center.y) * signY) / 2;
148
+ return { radius: Math.max(MIN_RADIUS, Math.round(projected)) };
149
+ }
150
+
151
+ /** Pure. SVG path `d` for a `w`x`h` rectangle centred on the origin with
152
+ * all four corners cut at 45° (an octagon) — see nodeShapeEl's
153
+ * 'chamfered' cornerStyle. `chamfer` (the cut's size) defaults to a
154
+ * fraction of the box's smaller dimension so it scales sensibly with
155
+ * the box rather than needing its own per-node configuration. */
156
+ static chamferedRectPath(w, h, chamfer) {
157
+ return Glyph.chamferedRectPath(w, h, chamfer);
158
+ }
159
+
160
+ /** Build the SVG element for a node's shape. Returns `{el, suppressInner}`
161
+ * where `suppressInner` tells the caller not to draw a centred icon or
162
+ * initials on top of busy distinct shapes. The shapes themselves are
163
+ * Glyphs (src/charting/glyphs), shared with the editor's templates. */
164
+ static nodeShapeEl(renderer, shape, R, color, size, cornerStyle, strokeColor = '#fff', strokeWidth = 2, iconUrl = null, icon = null) {
165
+ const glyph = glyphFor(shape);
166
+ const ctx = Glyph.context({ R, size, color, cornerStyle, strokeColor, strokeWidth, iconUrl, icon });
167
+ return { el: drawGlyph(renderer, glyph.parts(ctx)), suppressInner: glyph.constructor.ownsInterior };
168
+ }
169
+ }
@@ -1,23 +1,24 @@
1
- import { Glyph } from './Glyph.js';
2
-
3
- /** A disc with a white head-and-shoulders silhouette. */
4
- export class PersonGlyph extends Glyph {
5
- static key = 'person';
6
- static ownsInterior = true;
7
-
8
- parts(ctx) {
9
- const G = Glyph, st = (k) => this.stroke(ctx, k);
10
- const r = ctx.circleR;
11
- const headR = r * 0.30;
12
- const headY = -(r * 0.28);
13
- const neckY = headY + headR + r * 0.05;
14
- const bodyW = r * 0.55;
15
- const bodyH = r * 0.88;
16
- const bodyPath = `M ${-bodyW},${bodyH} Q ${-bodyW},${neckY} 0,${neckY} Q ${bodyW},${neckY} ${bodyW},${bodyH} Z`;
17
- return G.group([
18
- G.circle(0, 0, r, ctx.color, st()),
19
- G.circle(0, headY, headR, '#fff', null, { opacity: 0.92 }),
20
- G.path(bodyPath, '#fff', null, { opacity: 0.92 }),
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
+ }