@diagrammo/dgmo 0.8.10 → 0.8.11
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/cli.cjs +245 -672
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +2 -3
- package/dist/editor.d.ts +2 -3
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +306 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +304 -77
- package/dist/index.js.map +1 -1
- package/package.json +14 -17
- package/src/boxes-and-lines/renderer.ts +1 -1
- package/src/c4/layout.ts +31 -10
- package/src/d3.ts +80 -31
- package/src/echarts.ts +56 -57
- package/src/editor/index.ts +1 -2
- package/src/gantt/resolver.ts +19 -14
- package/src/index.ts +2 -0
- package/src/kanban/renderer.ts +10 -7
- package/src/label-layout.ts +286 -0
- package/src/sequence/parser.ts +4 -0
- package/src/sequence/renderer.ts +16 -3
- package/src/utils/arrows.ts +1 -1
- package/src/utils/legend-layout.ts +1 -5
- package/src/utils/legend-svg.ts +2 -2
- package/src/utils/legend-types.ts +0 -3
- package/src/utils/parsing.ts +1 -1
- package/src/utils/tag-groups.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diagrammo/dgmo",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.11",
|
|
4
4
|
"description": "DGMO diagram markup language — parser, renderer, and color system",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -74,10 +74,10 @@
|
|
|
74
74
|
"prepare": "husky"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@dagrejs/dagre": "^
|
|
77
|
+
"@dagrejs/dagre": "^3.0.0",
|
|
78
78
|
"@resvg/resvg-js": "^2.6.2",
|
|
79
79
|
"d3-array": "^3.2.4",
|
|
80
|
-
"d3-cloud": "^1.2.
|
|
80
|
+
"d3-cloud": "^1.2.9",
|
|
81
81
|
"d3-hierarchy": "^3.1.2",
|
|
82
82
|
"d3-scale": "^4.0.2",
|
|
83
83
|
"d3-selection": "^3.0.0",
|
|
@@ -85,32 +85,30 @@
|
|
|
85
85
|
"echarts": "^6.0.0",
|
|
86
86
|
"lz-string": "^1.5.0"
|
|
87
87
|
},
|
|
88
|
-
"optionalDependencies": {
|
|
89
|
-
"jsdom": "^28.1.0"
|
|
90
|
-
},
|
|
91
88
|
"devDependencies": {
|
|
92
89
|
"@codemirror/language": "^6.12.3",
|
|
93
|
-
"@codemirror/state": "^6.6.0",
|
|
94
90
|
"@eslint/js": "^10.0.1",
|
|
95
91
|
"@lezer/generator": "^1.8.0",
|
|
96
|
-
"@types/d3-array": "^3.2.
|
|
92
|
+
"@types/d3-array": "^3.2.2",
|
|
97
93
|
"@types/d3-cloud": "^1.2.9",
|
|
98
94
|
"@types/d3-hierarchy": "^3.1.7",
|
|
99
|
-
"@types/d3-scale": "^4.0.
|
|
95
|
+
"@types/d3-scale": "^4.0.9",
|
|
100
96
|
"@types/d3-selection": "^3.0.11",
|
|
101
|
-
"@types/d3-shape": "^3.1.
|
|
102
|
-
"@types/jsdom": "^28.0.
|
|
97
|
+
"@types/d3-shape": "^3.1.8",
|
|
98
|
+
"@types/jsdom": "^28.0.1",
|
|
103
99
|
"cspell": "^9.7.0",
|
|
104
|
-
"
|
|
100
|
+
"esbuild": "^0.28.0",
|
|
101
|
+
"eslint": "^10.2.0",
|
|
105
102
|
"husky": "^9.1.7",
|
|
106
103
|
"jscpd": "^4.0.8",
|
|
107
|
-
"
|
|
104
|
+
"jsdom": "^29.0.1",
|
|
105
|
+
"knip": "^6.3.0",
|
|
108
106
|
"lint-staged": "^16.4.0",
|
|
109
107
|
"prettier": "^3.8.1",
|
|
110
108
|
"tsup": "^8.5.1",
|
|
111
|
-
"typescript": "^
|
|
112
|
-
"typescript-eslint": "^8.
|
|
113
|
-
"vitest": "^4.
|
|
109
|
+
"typescript": "^6.0.2",
|
|
110
|
+
"typescript-eslint": "^8.58.0",
|
|
111
|
+
"vitest": "^4.1.2"
|
|
114
112
|
},
|
|
115
113
|
"lint-staged": {
|
|
116
114
|
"*.ts": [
|
|
@@ -120,7 +118,6 @@
|
|
|
120
118
|
},
|
|
121
119
|
"peerDependencies": {
|
|
122
120
|
"@codemirror/language": "^6.12.3",
|
|
123
|
-
"@codemirror/state": "^6.6.0",
|
|
124
121
|
"@lezer/common": "^1.5.1",
|
|
125
122
|
"@lezer/highlight": "^1.2.3",
|
|
126
123
|
"@lezer/lr": "^1.4.8"
|
|
@@ -292,7 +292,7 @@ function resolveEdgeLabelOverlaps(
|
|
|
292
292
|
|
|
293
293
|
// ── Main render function ───────────────────────────────────
|
|
294
294
|
|
|
295
|
-
|
|
295
|
+
interface BLRenderOptions {
|
|
296
296
|
onClickItem?: (lineNumber: number) => void;
|
|
297
297
|
exportDims?: { width?: number; height?: number };
|
|
298
298
|
activeTagGroup?: string | null;
|
package/src/c4/layout.ts
CHANGED
|
@@ -18,6 +18,27 @@ import {
|
|
|
18
18
|
measureLegendText,
|
|
19
19
|
} from '../utils/legend-constants';
|
|
20
20
|
|
|
21
|
+
/** dagre node label shape after layout(). */
|
|
22
|
+
interface DagreNodeLabel {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** dagre edge label shape after layout(). */
|
|
31
|
+
interface DagreEdgeLabel {
|
|
32
|
+
points: { x: number; y: number }[];
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
const gNode = (g: any, name: string): DagreNodeLabel => g.node(name);
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
const gEdge = (g: any, v: string, w: string): DagreEdgeLabel | undefined =>
|
|
40
|
+
g.edge(v, w);
|
|
41
|
+
|
|
21
42
|
// ============================================================
|
|
22
43
|
// Types
|
|
23
44
|
// ============================================================
|
|
@@ -213,7 +234,7 @@ function computeEdgePenalty(
|
|
|
213
234
|
* closer to their neighbors, producing cleaner visual layouts.
|
|
214
235
|
*/
|
|
215
236
|
function reduceCrossings(
|
|
216
|
-
g: dagre.graphlib.Graph
|
|
237
|
+
g: InstanceType<typeof dagre.graphlib.Graph>,
|
|
217
238
|
edgeList: { source: string; target: string }[],
|
|
218
239
|
nodeGroupMap?: Map<string, string>
|
|
219
240
|
): void {
|
|
@@ -229,7 +250,7 @@ function reduceCrossings(
|
|
|
229
250
|
// Build geometry map for edge-node collision scoring
|
|
230
251
|
const nodeGeometry = new Map<string, NodeGeometry>();
|
|
231
252
|
for (const name of g.nodes()) {
|
|
232
|
-
const pos = g
|
|
253
|
+
const pos = gNode(g, name);
|
|
233
254
|
if (pos)
|
|
234
255
|
nodeGeometry.set(name, {
|
|
235
256
|
y: pos.y,
|
|
@@ -241,7 +262,7 @@ function reduceCrossings(
|
|
|
241
262
|
// Group nodes by rank
|
|
242
263
|
const rankMap = new Map<number, string[]>();
|
|
243
264
|
for (const name of g.nodes()) {
|
|
244
|
-
const pos = g
|
|
265
|
+
const pos = gNode(g, name);
|
|
245
266
|
if (!pos) continue;
|
|
246
267
|
const rankY = Math.round(pos.y);
|
|
247
268
|
if (!rankMap.has(rankY)) rankMap.set(rankY, []);
|
|
@@ -250,7 +271,7 @@ function reduceCrossings(
|
|
|
250
271
|
|
|
251
272
|
// Sort each rank by current x position
|
|
252
273
|
for (const [, rankNodes] of rankMap) {
|
|
253
|
-
rankNodes.sort((a, b) => g
|
|
274
|
+
rankNodes.sort((a, b) => gNode(g, a).x - gNode(g, b).x);
|
|
254
275
|
}
|
|
255
276
|
|
|
256
277
|
let anyMoved = false;
|
|
@@ -285,13 +306,13 @@ function reduceCrossings(
|
|
|
285
306
|
|
|
286
307
|
// Collect the x-slots for this partition (sorted)
|
|
287
308
|
const xSlots = partition
|
|
288
|
-
.map((name) => g
|
|
309
|
+
.map((name) => gNode(g, name).x)
|
|
289
310
|
.sort((a, b) => a - b);
|
|
290
311
|
|
|
291
312
|
// Build position map snapshot
|
|
292
313
|
const basePositions = new Map<string, number>();
|
|
293
314
|
for (const name of g.nodes()) {
|
|
294
|
-
const pos = g
|
|
315
|
+
const pos = gNode(g, name);
|
|
295
316
|
if (pos) basePositions.set(name, pos.x);
|
|
296
317
|
}
|
|
297
318
|
|
|
@@ -379,7 +400,7 @@ function reduceCrossings(
|
|
|
379
400
|
// Apply best permutation if it differs from current
|
|
380
401
|
if (bestPerm.some((name, i) => name !== partition[i])) {
|
|
381
402
|
for (let i = 0; i < bestPerm.length; i++) {
|
|
382
|
-
g
|
|
403
|
+
gNode(g, bestPerm[i]!).x = xSlots[i]!;
|
|
383
404
|
// Update in the original rankNodes too
|
|
384
405
|
const rankIdx = rankNodes.indexOf(partition[i]!);
|
|
385
406
|
if (rankIdx >= 0) rankNodes[rankIdx] = bestPerm[i]!;
|
|
@@ -392,10 +413,10 @@ function reduceCrossings(
|
|
|
392
413
|
// Recompute edge waypoints if any positions changed
|
|
393
414
|
if (anyMoved) {
|
|
394
415
|
for (const edge of edgeList) {
|
|
395
|
-
const edgeData = g
|
|
416
|
+
const edgeData = gEdge(g, edge.source, edge.target);
|
|
396
417
|
if (!edgeData) continue;
|
|
397
|
-
const srcPos = g
|
|
398
|
-
const tgtPos = g
|
|
418
|
+
const srcPos = gNode(g, edge.source);
|
|
419
|
+
const tgtPos = gNode(g, edge.target);
|
|
399
420
|
if (!srcPos || !tgtPos) continue;
|
|
400
421
|
|
|
401
422
|
const srcBottom = { x: srcPos.x, y: srcPos.y + srcPos.height / 2 };
|
package/src/d3.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as d3Array from 'd3-array';
|
|
|
5
5
|
import cloud from 'd3-cloud';
|
|
6
6
|
import { FONT_FAMILY } from './fonts';
|
|
7
7
|
import { injectBranding } from './branding';
|
|
8
|
+
import { computeQuadrantPointLabels, type LabelRect } from './label-layout';
|
|
8
9
|
|
|
9
10
|
// ============================================================
|
|
10
11
|
// Types
|
|
@@ -19,22 +20,22 @@ export type VisualizationType =
|
|
|
19
20
|
| 'quadrant'
|
|
20
21
|
| 'sequence';
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
interface D3DataItem {
|
|
23
24
|
label: string;
|
|
24
25
|
values: number[];
|
|
25
26
|
color: string | null;
|
|
26
27
|
lineNumber: number;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
interface WordCloudWord {
|
|
30
31
|
text: string;
|
|
31
32
|
weight: number;
|
|
32
33
|
lineNumber: number;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
type WordCloudRotate = 'none' | 'mixed' | 'angled';
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
interface WordCloudOptions {
|
|
38
39
|
rotate: WordCloudRotate;
|
|
39
40
|
max: number;
|
|
40
41
|
minSize: number;
|
|
@@ -56,7 +57,7 @@ export interface ArcLink {
|
|
|
56
57
|
lineNumber: number;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
type ArcOrder = 'appearance' | 'name' | 'group' | 'degree';
|
|
60
61
|
|
|
61
62
|
export interface ArcNodeGroup {
|
|
62
63
|
name: string;
|
|
@@ -65,9 +66,9 @@ export interface ArcNodeGroup {
|
|
|
65
66
|
lineNumber: number;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
type TimelineSort = 'time' | 'group' | 'tag';
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
interface TimelineEvent {
|
|
71
72
|
date: string;
|
|
72
73
|
endDate: string | null;
|
|
73
74
|
label: string;
|
|
@@ -77,13 +78,13 @@ export interface TimelineEvent {
|
|
|
77
78
|
uncertain?: boolean;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
interface TimelineGroup {
|
|
81
82
|
name: string;
|
|
82
83
|
color: string | null;
|
|
83
84
|
lineNumber: number;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
interface TimelineEra {
|
|
87
88
|
startDate: string;
|
|
88
89
|
endDate: string;
|
|
89
90
|
label: string;
|
|
@@ -91,40 +92,40 @@ export interface TimelineEra {
|
|
|
91
92
|
lineNumber: number;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
interface TimelineMarker {
|
|
95
96
|
date: string;
|
|
96
97
|
label: string;
|
|
97
98
|
color: string | null;
|
|
98
99
|
lineNumber: number;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
interface VennSet {
|
|
102
103
|
name: string;
|
|
103
104
|
alias: string | null;
|
|
104
105
|
color: string | null;
|
|
105
106
|
lineNumber: number;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
interface VennOverlap {
|
|
109
110
|
sets: string[];
|
|
110
111
|
label: string | null;
|
|
111
112
|
lineNumber: number;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
interface QuadrantLabel {
|
|
115
116
|
text: string;
|
|
116
117
|
color: string | null;
|
|
117
118
|
lineNumber: number;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
interface QuadrantPoint {
|
|
121
122
|
label: string;
|
|
122
123
|
x: number;
|
|
123
124
|
y: number;
|
|
124
125
|
lineNumber: number;
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
interface QuadrantLabels {
|
|
128
129
|
topRight: QuadrantLabel | null;
|
|
129
130
|
topLeft: QuadrantLabel | null;
|
|
130
131
|
bottomLeft: QuadrantLabel | null;
|
|
@@ -4525,8 +4526,7 @@ export function renderTimeline(
|
|
|
4525
4526
|
.attr('dy', '0.35em')
|
|
4526
4527
|
.attr('text-anchor', 'start')
|
|
4527
4528
|
.attr('fill', textColor)
|
|
4528
|
-
.attr('font-size', '
|
|
4529
|
-
.attr('font-weight', '700')
|
|
4529
|
+
.attr('font-size', '13px')
|
|
4530
4530
|
.text(ev.label);
|
|
4531
4531
|
} else {
|
|
4532
4532
|
// Text outside bar - check if it fits on left or must go right
|
|
@@ -4815,8 +4815,7 @@ export function renderTimeline(
|
|
|
4815
4815
|
.attr('dy', '0.35em')
|
|
4816
4816
|
.attr('text-anchor', 'start')
|
|
4817
4817
|
.attr('fill', textColor)
|
|
4818
|
-
.attr('font-size', '
|
|
4819
|
-
.attr('font-weight', '700')
|
|
4818
|
+
.attr('font-size', '13px')
|
|
4820
4819
|
.text(ev.label);
|
|
4821
4820
|
} else {
|
|
4822
4821
|
// Text outside bar - check if it fits on left or must go right
|
|
@@ -5461,7 +5460,7 @@ export function renderVenn(
|
|
|
5461
5460
|
exportDims?: D3ExportDimensions
|
|
5462
5461
|
): void {
|
|
5463
5462
|
const { vennSets, vennOverlaps, title } = parsed;
|
|
5464
|
-
if (vennSets.length < 2) return;
|
|
5463
|
+
if (vennSets.length < 2 || vennSets.length > 3) return;
|
|
5465
5464
|
|
|
5466
5465
|
const init = initD3Chart(container, palette, exportDims);
|
|
5467
5466
|
if (!init) return;
|
|
@@ -5539,7 +5538,9 @@ export function renderVenn(
|
|
|
5539
5538
|
// Suppress WebKit focus ring on interactive SVG elements
|
|
5540
5539
|
svg
|
|
5541
5540
|
.append('style')
|
|
5542
|
-
.text(
|
|
5541
|
+
.text(
|
|
5542
|
+
'circle:focus, circle:focus-visible { outline-solid: none !important; }'
|
|
5543
|
+
);
|
|
5543
5544
|
|
|
5544
5545
|
// Title
|
|
5545
5546
|
renderChartTitle(
|
|
@@ -5870,7 +5871,7 @@ export function renderVenn(
|
|
|
5870
5871
|
.attr('class', 'venn-hit-target')
|
|
5871
5872
|
.attr('data-line-number', String(vennSets[i].lineNumber))
|
|
5872
5873
|
.style('cursor', onClickItem ? 'pointer' : 'default')
|
|
5873
|
-
.style('outline', 'none')
|
|
5874
|
+
.style('outline-solid', 'none')
|
|
5874
5875
|
.on('mouseenter', () => {
|
|
5875
5876
|
showRegionOverlay([i]);
|
|
5876
5877
|
})
|
|
@@ -5926,7 +5927,7 @@ export function renderVenn(
|
|
|
5926
5927
|
.attr('class', 'venn-hit-target')
|
|
5927
5928
|
.attr('data-line-number', declaredOv ? String(declaredOv.lineNumber) : '')
|
|
5928
5929
|
.style('cursor', onClickItem && declaredOv ? 'pointer' : 'default')
|
|
5929
|
-
.style('outline', 'none')
|
|
5930
|
+
.style('outline-solid', 'none')
|
|
5930
5931
|
.on('mouseenter', () => {
|
|
5931
5932
|
showRegionOverlay(idxs);
|
|
5932
5933
|
})
|
|
@@ -6416,40 +6417,88 @@ export function renderQuadrant(
|
|
|
6416
6417
|
return 'bottom-right';
|
|
6417
6418
|
};
|
|
6418
6419
|
|
|
6420
|
+
// Build obstacle rects from quadrant watermark labels for collision avoidance
|
|
6421
|
+
const POINT_RADIUS = 6;
|
|
6422
|
+
const POINT_LABEL_FONT_SIZE = 12;
|
|
6423
|
+
const quadrantLabelObstacles: LabelRect[] = quadrantDefsWithLabel.map((d) => {
|
|
6424
|
+
const layout = labelLayouts.get(d.label!.text)!;
|
|
6425
|
+
const totalW =
|
|
6426
|
+
Math.max(...layout.lines.map((l) => l.length)) *
|
|
6427
|
+
layout.fontSize *
|
|
6428
|
+
CHAR_WIDTH_RATIO;
|
|
6429
|
+
const totalH = layout.lines.length * layout.fontSize * 1.2;
|
|
6430
|
+
return {
|
|
6431
|
+
x: d.labelX - totalW / 2,
|
|
6432
|
+
y: d.labelY - totalH / 2,
|
|
6433
|
+
w: totalW,
|
|
6434
|
+
h: totalH,
|
|
6435
|
+
};
|
|
6436
|
+
});
|
|
6437
|
+
|
|
6438
|
+
// Compute collision-free label positions for all points
|
|
6439
|
+
const pointPixels = quadrantPoints.map((point) => ({
|
|
6440
|
+
label: point.label,
|
|
6441
|
+
cx: xScale(point.x),
|
|
6442
|
+
cy: yScale(point.y),
|
|
6443
|
+
}));
|
|
6444
|
+
|
|
6445
|
+
const placedPointLabels = computeQuadrantPointLabels(
|
|
6446
|
+
pointPixels,
|
|
6447
|
+
{ left: 0, top: 0, right: chartWidth, bottom: chartHeight },
|
|
6448
|
+
quadrantLabelObstacles,
|
|
6449
|
+
POINT_RADIUS,
|
|
6450
|
+
POINT_LABEL_FONT_SIZE
|
|
6451
|
+
);
|
|
6452
|
+
|
|
6419
6453
|
// Draw data points (circles and labels)
|
|
6420
6454
|
const pointsG = chartG.append('g').attr('class', 'points');
|
|
6421
6455
|
|
|
6422
|
-
quadrantPoints.forEach((point) => {
|
|
6456
|
+
quadrantPoints.forEach((point, i) => {
|
|
6423
6457
|
const cx = xScale(point.x);
|
|
6424
6458
|
const cy = yScale(point.y);
|
|
6425
6459
|
const quadrant = getPointQuadrant(point.x, point.y);
|
|
6426
6460
|
const quadDef = quadrantDefs.find((d) => d.position === quadrant);
|
|
6427
6461
|
const pointColor =
|
|
6428
6462
|
quadDef?.label?.color ?? defaultColors[quadDef?.colorIdx ?? 0];
|
|
6463
|
+
const placed = placedPointLabels[i];
|
|
6429
6464
|
|
|
6430
6465
|
const pointG = pointsG
|
|
6431
6466
|
.append('g')
|
|
6432
6467
|
.attr('class', 'point-group')
|
|
6433
6468
|
.attr('data-line-number', String(point.lineNumber));
|
|
6434
6469
|
|
|
6470
|
+
// Connector line (drawn first so it renders behind circle and label)
|
|
6471
|
+
if (placed.connectorLine) {
|
|
6472
|
+
pointG
|
|
6473
|
+
.append('line')
|
|
6474
|
+
.attr('x1', placed.connectorLine.x1)
|
|
6475
|
+
.attr('y1', placed.connectorLine.y1)
|
|
6476
|
+
.attr('x2', placed.connectorLine.x2)
|
|
6477
|
+
.attr('y2', placed.connectorLine.y2)
|
|
6478
|
+
.attr('stroke', pointColor)
|
|
6479
|
+
.attr('stroke-width', 1)
|
|
6480
|
+
.attr('opacity', 0.5);
|
|
6481
|
+
}
|
|
6482
|
+
|
|
6435
6483
|
// Circle with white fill and colored border for visibility on opaque quadrants
|
|
6436
6484
|
pointG
|
|
6437
6485
|
.append('circle')
|
|
6438
6486
|
.attr('cx', cx)
|
|
6439
6487
|
.attr('cy', cy)
|
|
6440
|
-
.attr('r',
|
|
6488
|
+
.attr('r', POINT_RADIUS)
|
|
6441
6489
|
.attr('fill', '#ffffff')
|
|
6442
6490
|
.attr('stroke', pointColor)
|
|
6443
6491
|
.attr('stroke-width', 2);
|
|
6444
6492
|
|
|
6445
|
-
// Label
|
|
6493
|
+
// Label at computed position
|
|
6446
6494
|
pointG
|
|
6447
6495
|
.append('text')
|
|
6448
|
-
.attr('x',
|
|
6449
|
-
.attr('y',
|
|
6450
|
-
.attr('text-anchor',
|
|
6496
|
+
.attr('x', placed.x)
|
|
6497
|
+
.attr('y', placed.y)
|
|
6498
|
+
.attr('text-anchor', placed.anchor)
|
|
6499
|
+
.attr('dominant-baseline', 'central')
|
|
6451
6500
|
.attr('fill', textColor)
|
|
6452
|
-
.attr('font-size',
|
|
6501
|
+
.attr('font-size', `${POINT_LABEL_FONT_SIZE}px`)
|
|
6453
6502
|
.attr('font-weight', '700')
|
|
6454
6503
|
.style('text-shadow', `0 1px 2px ${shadowColor}`)
|
|
6455
6504
|
.text(point.label);
|
|
@@ -6468,7 +6517,7 @@ export function renderQuadrant(
|
|
|
6468
6517
|
})
|
|
6469
6518
|
.on('mouseleave', () => {
|
|
6470
6519
|
hideTooltip(tooltip);
|
|
6471
|
-
pointG.select('circle').attr('r',
|
|
6520
|
+
pointG.select('circle').attr('r', POINT_RADIUS);
|
|
6472
6521
|
})
|
|
6473
6522
|
.on('click', () => {
|
|
6474
6523
|
if (onClickItem && point.lineNumber) onClickItem(point.lineNumber);
|