@diagrammo/dgmo 0.47.0 → 0.48.0
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/auto.js +1 -1
- package/dist/auto.mjs +1 -1
- package/dist/element.js +1 -1
- package/dist/element.mjs +1 -1
- package/package.json +19 -70
- package/dist/advanced.cjs +0 -71648
- package/dist/advanced.d.cts +0 -5719
- package/dist/auto.cjs +0 -70648
- package/dist/auto.d.cts +0 -54
- package/dist/block.cjs +0 -70208
- package/dist/block.d.cts +0 -115
- package/dist/editor.cjs +0 -526
- package/dist/editor.d.cts +0 -26
- package/dist/element.cjs +0 -70530
- package/dist/element.d.cts +0 -62
- package/dist/highlight.cjs +0 -852
- package/dist/highlight.d.cts +0 -49
- package/dist/index.cjs +0 -70491
- package/dist/index.d.cts +0 -160
- package/dist/pert.cjs +0 -325
- package/dist/pert.d.cts +0 -559
- package/dist/themes-1-CuKpeH.d.cts +0 -908
- package/src/advanced.ts +0 -873
- package/src/arc/parser.ts +0 -15
- package/src/arc/renderer.ts +0 -633
- package/src/auto/index.ts +0 -621
- package/src/auto/shared.ts +0 -370
- package/src/auto/styles.ts +0 -73
- package/src/block/layout.ts +0 -198
- package/src/block/parser.ts +0 -427
- package/src/block/renderer.ts +0 -484
- package/src/block/types.ts +0 -67
- package/src/boxes-and-lines/collapse.ts +0 -96
- package/src/boxes-and-lines/focus.ts +0 -257
- package/src/boxes-and-lines/label-placement.ts +0 -339
- package/src/boxes-and-lines/layout-grouped.ts +0 -912
- package/src/boxes-and-lines/layout-layered.ts +0 -722
- package/src/boxes-and-lines/layout-search.ts +0 -1678
- package/src/boxes-and-lines/layout.ts +0 -398
- package/src/boxes-and-lines/parser.ts +0 -1181
- package/src/boxes-and-lines/renderer.ts +0 -1465
- package/src/boxes-and-lines/types.ts +0 -66
- package/src/c4/layout.ts +0 -2441
- package/src/c4/parser.ts +0 -1158
- package/src/c4/renderer.ts +0 -2008
- package/src/c4/types.ts +0 -89
- package/src/chart-type-registry.ts +0 -529
- package/src/chart-types.ts +0 -225
- package/src/chart.ts +0 -685
- package/src/charts-d3/bar.ts +0 -338
- package/src/charts-d3/chord.ts +0 -140
- package/src/charts-d3/function.ts +0 -147
- package/src/charts-d3/funnel.ts +0 -104
- package/src/charts-d3/heatmap.ts +0 -145
- package/src/charts-d3/index.ts +0 -337
- package/src/charts-d3/interactions.ts +0 -477
- package/src/charts-d3/line.ts +0 -344
- package/src/charts-d3/mount.ts +0 -151
- package/src/charts-d3/pie.ts +0 -117
- package/src/charts-d3/polar.ts +0 -89
- package/src/charts-d3/radar.ts +0 -0
- package/src/charts-d3/sankey.ts +0 -0
- package/src/charts-d3/scatter.ts +0 -185
- package/src/charts-d3/shared.ts +0 -208
- package/src/class/layout.ts +0 -326
- package/src/class/parser.ts +0 -635
- package/src/class/renderer.ts +0 -793
- package/src/class/types.ts +0 -58
- package/src/cli-banner.ts +0 -106
- package/src/cli.ts +0 -1080
- package/src/colors-diagnostics.ts +0 -35
- package/src/colors.ts +0 -379
- package/src/completion-types.ts +0 -27
- package/src/completion.ts +0 -2668
- package/src/cycle/layout.ts +0 -1026
- package/src/cycle/parser.ts +0 -387
- package/src/cycle/renderer.ts +0 -653
- package/src/cycle/types.ts +0 -101
- package/src/d3.ts +0 -1434
- package/src/data-chart-parser.ts +0 -1007
- package/src/dgmo-router.ts +0 -296
- package/src/diagnostics-registry.ts +0 -108
- package/src/diagnostics.ts +0 -0
- package/src/dimensions.ts +0 -20
- package/src/directives-registry.ts +0 -396
- package/src/editor/dgmo.grammar +0 -76
- package/src/editor/dgmo.grammar.d.ts +0 -2
- package/src/editor/dgmo.grammar.js +0 -18
- package/src/editor/dgmo.grammar.terms.d.ts +0 -5
- package/src/editor/dgmo.grammar.terms.js +0 -35
- package/src/editor/highlight-api.ts +0 -596
- package/src/editor/highlight.ts +0 -36
- package/src/editor/index.ts +0 -27
- package/src/editor/keywords.ts +0 -128
- package/src/editor/tokens.ts +0 -30
- package/src/element/index.ts +0 -432
- package/src/embed/css.ts +0 -312
- package/src/embed/escape.ts +0 -15
- package/src/embed/index.ts +0 -280
- package/src/er/classify.ts +0 -228
- package/src/er/layout.ts +0 -530
- package/src/er/parser.ts +0 -750
- package/src/er/renderer.ts +0 -725
- package/src/er/types.ts +0 -52
- package/src/error-card.ts +0 -269
- package/src/event-line/diagnostics.ts +0 -117
- package/src/event-line/parser.ts +0 -643
- package/src/event-line/renderer.ts +0 -1742
- package/src/event-line/types.ts +0 -74
- package/src/fonts.ts +0 -3
- package/src/gantt/calculator.ts +0 -979
- package/src/gantt/parser.ts +0 -1641
- package/src/gantt/renderer.ts +0 -3787
- package/src/gantt/resolver.ts +0 -172
- package/src/gantt/types.ts +0 -206
- package/src/graph/diagnostics.ts +0 -64
- package/src/graph/edge-spline.ts +0 -29
- package/src/graph/flowchart-parser.ts +0 -759
- package/src/graph/flowchart-renderer.ts +0 -817
- package/src/graph/layout.ts +0 -472
- package/src/graph/notes.ts +0 -21
- package/src/graph/state-collapse.ts +0 -78
- package/src/graph/state-parser.ts +0 -574
- package/src/graph/state-renderer.ts +0 -653
- package/src/graph/types.ts +0 -65
- package/src/index.ts +0 -261
- package/src/infra/compute.ts +0 -1385
- package/src/infra/layout.ts +0 -888
- package/src/infra/parser.ts +0 -1234
- package/src/infra/renderer.ts +0 -2534
- package/src/infra/roles.ts +0 -75
- package/src/infra/types.ts +0 -216
- package/src/infra/validation.ts +0 -201
- package/src/journey-map/layout.ts +0 -490
- package/src/journey-map/parser.ts +0 -557
- package/src/journey-map/renderer.ts +0 -1580
- package/src/journey-map/types.ts +0 -47
- package/src/kanban/mutations.ts +0 -175
- package/src/kanban/parser.ts +0 -509
- package/src/kanban/renderer.ts +0 -1449
- package/src/kanban/types.ts +0 -39
- package/src/label-layout.ts +0 -368
- package/src/map/colorize.ts +0 -54
- package/src/map/completion.ts +0 -316
- package/src/map/context-labels.ts +0 -609
- package/src/map/data/PROVENANCE.json +0 -1
- package/src/map/data/README.md +0 -40
- package/src/map/data/airport-collisions.json +0 -1
- package/src/map/data/airports.json +0 -1
- package/src/map/data/gazetteer.json +0 -1
- package/src/map/data/lakes.json +0 -1
- package/src/map/data/mountain-ranges.json +0 -1
- package/src/map/data/na-lakes.json +0 -1
- package/src/map/data/na-land.json +0 -1
- package/src/map/data/region-names.json +0 -1
- package/src/map/data/rivers.json +0 -1
- package/src/map/data/types.ts +0 -121
- package/src/map/data/us-states.json +0 -1
- package/src/map/data/water-bodies.json +0 -1
- package/src/map/data/world-coarse.json +0 -1
- package/src/map/data/world-detail.json +0 -1
- package/src/map/diagnostics.ts +0 -134
- package/src/map/dimensions.ts +0 -150
- package/src/map/geo-query.ts +0 -295
- package/src/map/geo.ts +0 -438
- package/src/map/invert.ts +0 -111
- package/src/map/layout.ts +0 -5069
- package/src/map/legend-band.ts +0 -127
- package/src/map/load-data.ts +0 -186
- package/src/map/parser.ts +0 -783
- package/src/map/renderer.ts +0 -1445
- package/src/map/resolved-types.ts +0 -160
- package/src/map/resolver.ts +0 -1146
- package/src/map/types.ts +0 -194
- package/src/mindmap/collapse.ts +0 -67
- package/src/mindmap/layout.ts +0 -649
- package/src/mindmap/parser.ts +0 -430
- package/src/mindmap/renderer.ts +0 -637
- package/src/mindmap/text-wrap.ts +0 -218
- package/src/mindmap/types.ts +0 -56
- package/src/org/collapse.ts +0 -151
- package/src/org/layout.ts +0 -1262
- package/src/org/parser.ts +0 -519
- package/src/org/renderer.ts +0 -866
- package/src/org/resolver.ts +0 -636
- package/src/palettes/atlas.ts +0 -77
- package/src/palettes/blueprint.ts +0 -73
- package/src/palettes/catppuccin.ts +0 -84
- package/src/palettes/color-utils.ts +0 -443
- package/src/palettes/index.ts +0 -67
- package/src/palettes/nord.ts +0 -67
- package/src/palettes/registry.ts +0 -114
- package/src/palettes/slate.ts +0 -73
- package/src/palettes/tidewater.ts +0 -73
- package/src/palettes/tokyo-night.ts +0 -86
- package/src/palettes/types.ts +0 -88
- package/src/pert/analyzer.ts +0 -1183
- package/src/pert/diagnostics.ts +0 -110
- package/src/pert/index.ts +0 -43
- package/src/pert/internal.ts +0 -252
- package/src/pert/layout.ts +0 -971
- package/src/pert/monte-carlo.ts +0 -446
- package/src/pert/parser.ts +0 -1606
- package/src/pert/renderer.ts +0 -4009
- package/src/pert/share-normalize.ts +0 -62
- package/src/pert/types.ts +0 -604
- package/src/pyramid/parser.ts +0 -185
- package/src/pyramid/renderer.ts +0 -768
- package/src/pyramid/types.ts +0 -28
- package/src/quadrant/parser.ts +0 -18
- package/src/quadrant/renderer.ts +0 -638
- package/src/raci/diagnostics.ts +0 -213
- package/src/raci/index.ts +0 -23
- package/src/raci/mutations.ts +0 -321
- package/src/raci/parser.ts +0 -699
- package/src/raci/renderer.ts +0 -1627
- package/src/raci/types.ts +0 -82
- package/src/raci/variants.ts +0 -320
- package/src/render.ts +0 -268
- package/src/ring/parser.ts +0 -212
- package/src/ring/renderer.ts +0 -490
- package/src/ring/types.ts +0 -27
- package/src/sequence/collapse.ts +0 -174
- package/src/sequence/parser.ts +0 -1596
- package/src/sequence/participant-inference.ts +0 -152
- package/src/sequence/renderer.ts +0 -3168
- package/src/sequence/tag-resolution.ts +0 -164
- package/src/sharing.ts +0 -211
- package/src/sitemap/collapse.ts +0 -100
- package/src/sitemap/layout.ts +0 -925
- package/src/sitemap/parser.ts +0 -755
- package/src/sitemap/renderer.ts +0 -828
- package/src/sitemap/types.ts +0 -42
- package/src/slope/parser.ts +0 -18
- package/src/slope/renderer.ts +0 -378
- package/src/swimlane/diagnostics.ts +0 -94
- package/src/swimlane/layout.ts +0 -597
- package/src/swimlane/parser.ts +0 -613
- package/src/swimlane/renderer.ts +0 -681
- package/src/swimlane/types.ts +0 -151
- package/src/tech-radar/interactive.ts +0 -1129
- package/src/tech-radar/layout.ts +0 -197
- package/src/tech-radar/parser.ts +0 -436
- package/src/tech-radar/renderer.ts +0 -1239
- package/src/tech-radar/shared.ts +0 -151
- package/src/tech-radar/types.ts +0 -86
- package/src/themes.ts +0 -22
- package/src/timeline/parser.ts +0 -472
- package/src/timeline/renderer.ts +0 -3024
- package/src/timeline/types.ts +0 -33
- package/src/timeline/viz-parser.ts +0 -21
- package/src/treemap/diagnostics.ts +0 -62
- package/src/treemap/layout-radial.ts +0 -233
- package/src/treemap/layout.ts +0 -189
- package/src/treemap/parser.ts +0 -463
- package/src/treemap/renderer-radial.ts +0 -438
- package/src/treemap/renderer.ts +0 -572
- package/src/treemap/treemap-shared.ts +0 -309
- package/src/treemap/types.ts +0 -60
- package/src/utils/arrow-markers.ts +0 -51
- package/src/utils/arrows-diagnostics.ts +0 -62
- package/src/utils/arrows.ts +0 -227
- package/src/utils/brand.ts +0 -61
- package/src/utils/card.ts +0 -183
- package/src/utils/collapse-engine/tree-edges.ts +0 -115
- package/src/utils/collapse-engine/tree.ts +0 -117
- package/src/utils/d3-helpers.ts +0 -219
- package/src/utils/d3-types.ts +0 -9
- package/src/utils/description-helpers.ts +0 -38
- package/src/utils/duration.ts +0 -312
- package/src/utils/export-container.ts +0 -41
- package/src/utils/expr-eval.ts +0 -27
- package/src/utils/fit-canvas.ts +0 -64
- package/src/utils/hover-styles.ts +0 -673
- package/src/utils/inline-markdown.ts +0 -96
- package/src/utils/legend-constants.ts +0 -48
- package/src/utils/legend-d3.ts +0 -618
- package/src/utils/legend-integration.ts +0 -107
- package/src/utils/legend-layout.ts +0 -776
- package/src/utils/legend-svg.ts +0 -214
- package/src/utils/legend-types.ts +0 -272
- package/src/utils/name-normalize.ts +0 -163
- package/src/utils/note-box/constants.ts +0 -25
- package/src/utils/note-box/index.ts +0 -11
- package/src/utils/note-box/metrics.ts +0 -90
- package/src/utils/note-box/svg.ts +0 -331
- package/src/utils/notes/bounds.ts +0 -30
- package/src/utils/notes/build.ts +0 -131
- package/src/utils/notes/index.ts +0 -18
- package/src/utils/notes/model.ts +0 -19
- package/src/utils/notes/parse.ts +0 -131
- package/src/utils/notes/place.ts +0 -177
- package/src/utils/notes/resolve.ts +0 -88
- package/src/utils/number-format.ts +0 -36
- package/src/utils/parsing.ts +0 -953
- package/src/utils/reserved-key-registry.ts +0 -239
- package/src/utils/safe-href.ts +0 -38
- package/src/utils/scaling.ts +0 -97
- package/src/utils/svg-embed.ts +0 -251
- package/src/utils/tag-groups.ts +0 -607
- package/src/utils/text-measure.ts +0 -122
- package/src/utils/time-ticks.ts +0 -253
- package/src/utils/title-constants.ts +0 -24
- package/src/utils/visual-conventions.ts +0 -61
- package/src/utils/wrapped-desc.ts +0 -87
- package/src/venn/parser.ts +0 -18
- package/src/venn/renderer.ts +0 -1047
- package/src/version-control/diagnostics.ts +0 -67
- package/src/version-control/parser.ts +0 -544
- package/src/version-control/renderer.ts +0 -724
- package/src/version-control/types.ts +0 -100
- package/src/visualizations/parse.ts +0 -1560
- package/src/visualizations/types.ts +0 -262
- package/src/wireframe/layout.ts +0 -479
- package/src/wireframe/parser.ts +0 -1114
- package/src/wireframe/renderer.ts +0 -1318
- package/src/wireframe/types.ts +0 -110
- package/src/wordcloud/parser.ts +0 -18
- package/src/wordcloud/renderer.ts +0 -357
|
@@ -1,477 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Framework-agnostic interaction adapter for hand-rendered data-chart SVGs.
|
|
3
|
-
//
|
|
4
|
-
// Replaces the ECharts-instance interaction model (dispatchAction /
|
|
5
|
-
// convertToPixel / setOption graphic) with plain DOM events over the semantic
|
|
6
|
-
// SVG the D3 renderers emit. No ECharts, no d3, no framework — just an
|
|
7
|
-
// SVGSVGElement + the `data-*` attributes the renderers wrote.
|
|
8
|
-
//
|
|
9
|
-
// Interaction model (NO tooltips): hovering a figure draws dotted leader lines
|
|
10
|
-
// to the axis/axes associated with it, prints that figure's value ON the axis
|
|
11
|
-
// (axis-pointer style), and dims the other figures. Charts without axes
|
|
12
|
-
// (pie/funnel/sankey/…) just emphasize the hovered figure and dim the rest —
|
|
13
|
-
// their values already live in static labels.
|
|
14
|
-
//
|
|
15
|
-
// Three auto-detected modes:
|
|
16
|
-
// • crosshair — line/area (`.dgmo-plot-rect` + `.dgmo-pt`): snap a vertical
|
|
17
|
-
// leader to the nearest x, project the nearest series point to the y-axis.
|
|
18
|
-
// • projection — points carrying `data-axval-x`/`data-axval-y` (scatter):
|
|
19
|
-
// dotted leaders to BOTH axes + on-axis value pills.
|
|
20
|
-
// • emphasis — any other `.dgmo-datum`: emphasize + dim siblings.
|
|
21
|
-
// Plus universal click-to-source via `data-line-number`.
|
|
22
|
-
// ============================================================
|
|
23
|
-
|
|
24
|
-
export interface DataChartInteractionOpts {
|
|
25
|
-
onNavigate?: (line: number) => void;
|
|
26
|
-
mutedColor?: string;
|
|
27
|
-
surface?: string;
|
|
28
|
-
text?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface DataChartController {
|
|
32
|
-
/** Remove all listeners + overlays. */
|
|
33
|
-
destroy: () => void;
|
|
34
|
-
/** Emphasize the chart element(s) on the given source line (editor cursor
|
|
35
|
-
* sync); pass null to clear. No-op while the pointer is actively hovering. */
|
|
36
|
-
highlight: (line: number | null) => void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const NS = 'http://www.w3.org/2000/svg';
|
|
40
|
-
const STYLE_ID = 'dgmo-chart-interactions-style';
|
|
41
|
-
|
|
42
|
-
function ensureStyle(svg: SVGSVGElement, muted: string): void {
|
|
43
|
-
const doc = svg.ownerDocument;
|
|
44
|
-
if (doc.getElementById(STYLE_ID)) return;
|
|
45
|
-
const style = doc.createElementNS(NS, 'style');
|
|
46
|
-
style.id = STYLE_ID;
|
|
47
|
-
style.textContent = `
|
|
48
|
-
.dgmo-series,.dgmo-datum,.dgmo-tick{transition:opacity .12s ease}
|
|
49
|
-
.dgmo-series.dgmo-dim,.dgmo-datum.dgmo-dim{opacity:.18}
|
|
50
|
-
.dgmo-tick.dgmo-faded{opacity:.22}
|
|
51
|
-
.dgmo-datum{cursor:pointer}
|
|
52
|
-
.dgmo-axline{stroke:${muted};stroke-width:1;stroke-dasharray:4 4;pointer-events:none}
|
|
53
|
-
`;
|
|
54
|
-
svg.insertBefore(style, svg.firstChild);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function toUserSpace(
|
|
58
|
-
svg: SVGSVGElement,
|
|
59
|
-
e: MouseEvent
|
|
60
|
-
): { x: number; y: number } {
|
|
61
|
-
const ctm = svg.getScreenCTM?.();
|
|
62
|
-
if (!ctm) return { x: 0, y: 0 };
|
|
63
|
-
const p = svg.createSVGPoint();
|
|
64
|
-
p.x = e.clientX;
|
|
65
|
-
p.y = e.clientY;
|
|
66
|
-
const u = p.matrixTransform(ctm.inverse());
|
|
67
|
-
return { x: u.x, y: u.y };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function walkUpForLine(target: Element | null, root: Element): number | null {
|
|
71
|
-
let el: Element | null = target;
|
|
72
|
-
while (el && el !== root.parentElement) {
|
|
73
|
-
const a = el.getAttribute('data-line-number');
|
|
74
|
-
if (a) return parseInt(a, 10);
|
|
75
|
-
el = el.parentElement;
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
interface Plot {
|
|
81
|
-
left: number;
|
|
82
|
-
top: number;
|
|
83
|
-
right: number;
|
|
84
|
-
bottom: number;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function attachDataChartInteractions(
|
|
88
|
-
svg: SVGSVGElement,
|
|
89
|
-
opts: DataChartInteractionOpts = {}
|
|
90
|
-
): DataChartController {
|
|
91
|
-
const muted = opts.mutedColor ?? '#94a3b8';
|
|
92
|
-
const text = opts.text ?? '#1f2933';
|
|
93
|
-
ensureStyle(svg, muted);
|
|
94
|
-
const doc = svg.ownerDocument;
|
|
95
|
-
|
|
96
|
-
const plotRect = svg.querySelector<SVGRectElement>('.dgmo-plot-rect');
|
|
97
|
-
const plot: Plot | null = plotRect
|
|
98
|
-
? {
|
|
99
|
-
left: parseFloat(plotRect.getAttribute('x') ?? '0'),
|
|
100
|
-
top: parseFloat(plotRect.getAttribute('y') ?? '0'),
|
|
101
|
-
right:
|
|
102
|
-
parseFloat(plotRect.getAttribute('x') ?? '0') +
|
|
103
|
-
parseFloat(plotRect.getAttribute('width') ?? '0'),
|
|
104
|
-
bottom:
|
|
105
|
-
parseFloat(plotRect.getAttribute('y') ?? '0') +
|
|
106
|
-
parseFloat(plotRect.getAttribute('height') ?? '0'),
|
|
107
|
-
}
|
|
108
|
-
: null;
|
|
109
|
-
|
|
110
|
-
const seriesGroups = Array.from(
|
|
111
|
-
svg.querySelectorAll<SVGGElement>('.dgmo-series')
|
|
112
|
-
);
|
|
113
|
-
const datums = Array.from(svg.querySelectorAll<SVGElement>('.dgmo-datum'));
|
|
114
|
-
const circles = Array.from(
|
|
115
|
-
svg.querySelectorAll<SVGCircleElement>('.dgmo-pt')
|
|
116
|
-
);
|
|
117
|
-
const ticks = Array.from(svg.querySelectorAll<SVGTextElement>('.dgmo-tick'));
|
|
118
|
-
const fadeTicks = () => ticks.forEach((t) => t.classList.add('dgmo-faded'));
|
|
119
|
-
const unfadeTicks = () =>
|
|
120
|
-
ticks.forEach((t) => t.classList.remove('dgmo-faded'));
|
|
121
|
-
|
|
122
|
-
// ── transient overlay (leader lines + on-axis value pills) ────────────
|
|
123
|
-
let overlay: SVGGElement | null = null;
|
|
124
|
-
const getOverlay = (): SVGGElement => {
|
|
125
|
-
if (!overlay) {
|
|
126
|
-
overlay = doc.createElementNS(NS, 'g');
|
|
127
|
-
overlay.setAttribute('class', 'dgmo-overlay');
|
|
128
|
-
overlay.setAttribute('pointer-events', 'none');
|
|
129
|
-
}
|
|
130
|
-
svg.appendChild(overlay); // keep on top
|
|
131
|
-
return overlay;
|
|
132
|
-
};
|
|
133
|
-
const clearOverlay = () => {
|
|
134
|
-
if (overlay)
|
|
135
|
-
while (overlay.firstChild) overlay.removeChild(overlay.firstChild);
|
|
136
|
-
unfadeTicks();
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const dottedLine = (x1: number, y1: number, x2: number, y2: number) => {
|
|
140
|
-
const l = doc.createElementNS(NS, 'line');
|
|
141
|
-
l.setAttribute('class', 'dgmo-axline');
|
|
142
|
-
l.setAttribute('x1', String(x1));
|
|
143
|
-
l.setAttribute('y1', String(y1));
|
|
144
|
-
l.setAttribute('x2', String(x2));
|
|
145
|
-
l.setAttribute('y2', String(y2));
|
|
146
|
-
getOverlay().appendChild(l);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// The active value drawn ON an axis — styled as an emphasized tick (chart
|
|
150
|
-
// text color, a little bigger + bold), NOT a tooltip chip. Sits where the
|
|
151
|
-
// axis tick label would; the other ticks fade so this one reads as active.
|
|
152
|
-
// side 'x' → below x-axis (centered, at the x-tick row); side 'y' → left of
|
|
153
|
-
// the y-axis (right-aligned, at the y-tick column).
|
|
154
|
-
const axisValue = (
|
|
155
|
-
cx: number,
|
|
156
|
-
cy: number,
|
|
157
|
-
label: string,
|
|
158
|
-
side: 'x' | 'y'
|
|
159
|
-
) => {
|
|
160
|
-
const t = doc.createElementNS(NS, 'text');
|
|
161
|
-
t.setAttribute('font-size', '15');
|
|
162
|
-
t.setAttribute('font-weight', '700');
|
|
163
|
-
t.setAttribute('font-family', 'Inter, system-ui, sans-serif');
|
|
164
|
-
t.setAttribute('fill', text);
|
|
165
|
-
t.textContent = label;
|
|
166
|
-
if (side === 'x') {
|
|
167
|
-
t.setAttribute('text-anchor', 'middle');
|
|
168
|
-
t.setAttribute('x', String(cx));
|
|
169
|
-
t.setAttribute('y', String(cy + 19));
|
|
170
|
-
} else {
|
|
171
|
-
t.setAttribute('text-anchor', 'end');
|
|
172
|
-
t.setAttribute('x', String(cx - 10));
|
|
173
|
-
t.setAttribute('y', String(cy + 5));
|
|
174
|
-
}
|
|
175
|
-
getOverlay().appendChild(t);
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const dimDatumsExcept = (el: Element | null) => {
|
|
179
|
-
for (const d of datums) d.classList.toggle('dgmo-dim', d !== el);
|
|
180
|
-
};
|
|
181
|
-
const dimByKey = (el: SVGElement) => {
|
|
182
|
-
const key = el.getAttribute('data-emph-key');
|
|
183
|
-
for (const d of datums) {
|
|
184
|
-
const dk = d.getAttribute('data-emph-key');
|
|
185
|
-
if (key) d.classList.toggle('dgmo-dim', dk !== key);
|
|
186
|
-
else d.classList.toggle('dgmo-dim', d !== el);
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
const clearDim = () => {
|
|
190
|
-
for (const d of datums) d.classList.remove('dgmo-dim');
|
|
191
|
-
for (const g of seriesGroups) g.classList.remove('dgmo-dim');
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// ── crosshair (line / area) ───────────────────────────────────────────
|
|
195
|
-
const crosshairOn = !!plot && circles.length > 0;
|
|
196
|
-
let activePt: { el: SVGCircleElement; r: number } | null = null;
|
|
197
|
-
const restorePt = () => {
|
|
198
|
-
if (activePt) activePt.el.setAttribute('r', String(activePt.r));
|
|
199
|
-
activePt = null;
|
|
200
|
-
};
|
|
201
|
-
let slots: { cx: number; pts: SVGCircleElement[] }[] = [];
|
|
202
|
-
if (crosshairOn) {
|
|
203
|
-
const byX = new Map<number, SVGCircleElement[]>();
|
|
204
|
-
for (const el of circles) {
|
|
205
|
-
const xi = parseInt(el.getAttribute('data-x-index') ?? '0', 10);
|
|
206
|
-
(byX.get(xi) ?? byX.set(xi, []).get(xi)!).push(el);
|
|
207
|
-
}
|
|
208
|
-
slots = [...byX.values()]
|
|
209
|
-
.map((pts) => ({
|
|
210
|
-
cx: parseFloat(pts[0]!.getAttribute('cx') ?? '0'),
|
|
211
|
-
pts,
|
|
212
|
-
}))
|
|
213
|
-
.sort((a, b) => a.cx - b.cx);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const drawCrosshair = (x: number, y: number): void => {
|
|
217
|
-
if (!plot || slots.length === 0) return;
|
|
218
|
-
let slot = slots[0]!;
|
|
219
|
-
let best = Infinity;
|
|
220
|
-
for (const s of slots) {
|
|
221
|
-
const d = Math.abs(s.cx - x);
|
|
222
|
-
if (d < best) {
|
|
223
|
-
best = d;
|
|
224
|
-
slot = s;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
let nearest = slot.pts[0]!;
|
|
228
|
-
let bestY = Infinity;
|
|
229
|
-
for (const p of slot.pts) {
|
|
230
|
-
const cy = parseFloat(p.getAttribute('cy') ?? '0');
|
|
231
|
-
const d = Math.abs(cy - y);
|
|
232
|
-
if (d < bestY) {
|
|
233
|
-
bestY = d;
|
|
234
|
-
nearest = p;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
clearOverlay();
|
|
238
|
-
// vertical leader at the category x + x-axis value
|
|
239
|
-
dottedLine(slot.cx, plot.top, slot.cx, plot.bottom);
|
|
240
|
-
axisValue(
|
|
241
|
-
slot.cx,
|
|
242
|
-
plot.bottom,
|
|
243
|
-
nearest.getAttribute('data-x-label') ?? '',
|
|
244
|
-
'x'
|
|
245
|
-
);
|
|
246
|
-
// horizontal leader from nearest point to the y-axis + y-axis value
|
|
247
|
-
const ny = parseFloat(nearest.getAttribute('cy') ?? '0');
|
|
248
|
-
dottedLine(plot.left, ny, slot.cx, ny);
|
|
249
|
-
axisValue(plot.left, ny, nearest.getAttribute('data-value') ?? '', 'y');
|
|
250
|
-
fadeTicks();
|
|
251
|
-
// emphasize nearest point + its series, dim the others
|
|
252
|
-
restorePt();
|
|
253
|
-
const base = parseFloat(nearest.getAttribute('r') ?? '3.5');
|
|
254
|
-
activePt = { el: nearest, r: base };
|
|
255
|
-
nearest.setAttribute('r', String(base + 3));
|
|
256
|
-
const ns = nearest.getAttribute('data-series-name');
|
|
257
|
-
for (const g of seriesGroups)
|
|
258
|
-
g.classList.toggle(
|
|
259
|
-
'dgmo-dim',
|
|
260
|
-
seriesGroups.length > 1 && g.getAttribute('data-series-name') !== ns
|
|
261
|
-
);
|
|
262
|
-
};
|
|
263
|
-
const clearCrosshair = (): void => {
|
|
264
|
-
clearOverlay();
|
|
265
|
-
restorePt();
|
|
266
|
-
for (const g of seriesGroups) g.classList.remove('dgmo-dim');
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
// ── element hover (projection for points w/ axis values, else emphasis) ─
|
|
270
|
-
let curDatum: Element | null = null;
|
|
271
|
-
const clearDatum = () => {
|
|
272
|
-
clearOverlay();
|
|
273
|
-
clearDim();
|
|
274
|
-
curDatum = null;
|
|
275
|
-
};
|
|
276
|
-
const enterDatum = (el: SVGElement) => {
|
|
277
|
-
const axX = el.getAttribute('data-axval-x');
|
|
278
|
-
const axY = el.getAttribute('data-axval-y');
|
|
279
|
-
if (plot && (axX !== null || axY !== null)) {
|
|
280
|
-
// projection mode (scatter): leaders to both axes + on-axis pills
|
|
281
|
-
const cx = parseFloat(el.getAttribute('cx') ?? '0');
|
|
282
|
-
const cy = parseFloat(el.getAttribute('cy') ?? '0');
|
|
283
|
-
clearOverlay();
|
|
284
|
-
if (axX !== null) {
|
|
285
|
-
dottedLine(cx, cy, cx, plot.bottom);
|
|
286
|
-
axisValue(cx, plot.bottom, axX, 'x');
|
|
287
|
-
}
|
|
288
|
-
if (axY !== null) {
|
|
289
|
-
dottedLine(plot.left, cy, cx, cy);
|
|
290
|
-
axisValue(plot.left, cy, axY, 'y');
|
|
291
|
-
}
|
|
292
|
-
fadeTicks();
|
|
293
|
-
dimDatumsExcept(el);
|
|
294
|
-
} else {
|
|
295
|
-
dimByKey(el);
|
|
296
|
-
}
|
|
297
|
-
curDatum = el;
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
let crosshairActive = false;
|
|
301
|
-
const onSvgMove = (e: MouseEvent) => {
|
|
302
|
-
const el = (e.target as Element).closest?.(
|
|
303
|
-
'.dgmo-datum'
|
|
304
|
-
) as SVGElement | null;
|
|
305
|
-
if (el) {
|
|
306
|
-
if (el !== curDatum) {
|
|
307
|
-
clearDatum();
|
|
308
|
-
enterDatum(el);
|
|
309
|
-
}
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
if (curDatum) clearDatum();
|
|
313
|
-
// Crosshair fires anywhere inside the plot region — over the area fill, the
|
|
314
|
-
// line, or the dots, not just empty space (the whole point of the change).
|
|
315
|
-
if (crosshairOn && plot) {
|
|
316
|
-
const ctm = svg.getScreenCTM?.();
|
|
317
|
-
const { x, y } = toUserSpace(svg, e);
|
|
318
|
-
const inside =
|
|
319
|
-
!ctm ||
|
|
320
|
-
(x >= plot.left - 2 &&
|
|
321
|
-
x <= plot.right + 2 &&
|
|
322
|
-
y >= plot.top - 2 &&
|
|
323
|
-
y <= plot.bottom + 2);
|
|
324
|
-
if (inside) {
|
|
325
|
-
drawCrosshair(x, y);
|
|
326
|
-
crosshairActive = true;
|
|
327
|
-
} else if (crosshairActive) {
|
|
328
|
-
clearCrosshair();
|
|
329
|
-
crosshairActive = false;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
const onLeave = () => {
|
|
334
|
-
clearDatum();
|
|
335
|
-
if (crosshairActive) {
|
|
336
|
-
clearCrosshair();
|
|
337
|
-
crosshairActive = false;
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
const onClick = (e: MouseEvent) => {
|
|
341
|
-
const line = walkUpForLine(e.target as Element, svg);
|
|
342
|
-
if (line !== null && opts.onNavigate) opts.onNavigate(line);
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
// ── cursor-driven highlight (editor cursor → chart element) ───────────
|
|
346
|
-
let pinned: { el: SVGCircleElement; r: number }[] = [];
|
|
347
|
-
const clearHighlight = () => {
|
|
348
|
-
for (const p of pinned) p.el.setAttribute('r', String(p.r));
|
|
349
|
-
pinned = [];
|
|
350
|
-
for (const d of datums) d.classList.remove('dgmo-dim');
|
|
351
|
-
for (const g of seriesGroups) g.classList.remove('dgmo-dim');
|
|
352
|
-
};
|
|
353
|
-
const highlightLine = (line: number | null) => {
|
|
354
|
-
if (curDatum || crosshairActive) return; // hover wins
|
|
355
|
-
clearHighlight();
|
|
356
|
-
if (line == null) return;
|
|
357
|
-
const ls = String(line);
|
|
358
|
-
const md = datums.filter((d) => d.getAttribute('data-line-number') === ls);
|
|
359
|
-
const ms = seriesGroups.filter(
|
|
360
|
-
(g) => g.getAttribute('data-line-number') === ls
|
|
361
|
-
);
|
|
362
|
-
const mp = circles.filter((c) => c.getAttribute('data-line-number') === ls);
|
|
363
|
-
if (!md.length && !ms.length && !mp.length) return;
|
|
364
|
-
if (md.length)
|
|
365
|
-
for (const d of datums) d.classList.toggle('dgmo-dim', !md.includes(d));
|
|
366
|
-
if (ms.length)
|
|
367
|
-
for (const g of seriesGroups)
|
|
368
|
-
g.classList.toggle('dgmo-dim', !ms.includes(g));
|
|
369
|
-
for (const c of mp) {
|
|
370
|
-
const r = parseFloat(c.getAttribute('r') ?? '3.5');
|
|
371
|
-
pinned.push({ el: c, r });
|
|
372
|
-
c.setAttribute('r', String(r + 3));
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
if (datums.length > 0 || crosshairOn)
|
|
377
|
-
svg.addEventListener('mousemove', onSvgMove);
|
|
378
|
-
svg.addEventListener('mouseleave', onLeave);
|
|
379
|
-
svg.addEventListener('click', onClick);
|
|
380
|
-
|
|
381
|
-
// ── legend hover → emphasize the hovered series, dim the rest ──────────
|
|
382
|
-
// The injected series legend (utils/legend-svg.ts) tags each entry with
|
|
383
|
-
// data-series-name matching the series groups' data-series-name. Hovering an
|
|
384
|
-
// entry dims every OTHER series (group + its datums); leaving restores. Only
|
|
385
|
-
// series-tagged elements are touched, so charts without series groups are
|
|
386
|
-
// untouched. Dimming the g.dgmo-series fades its polygon, points, and labels.
|
|
387
|
-
const legendEntries = Array.from(
|
|
388
|
-
svg.querySelectorAll<SVGGElement>('.chart-legend [data-legend-entry]')
|
|
389
|
-
);
|
|
390
|
-
const emphasizeSeries = (name: string | null) => {
|
|
391
|
-
for (const g of seriesGroups) {
|
|
392
|
-
const sn = g.getAttribute('data-series-name');
|
|
393
|
-
if (sn != null)
|
|
394
|
-
g.classList.toggle('dgmo-dim', name != null && sn !== name);
|
|
395
|
-
}
|
|
396
|
-
for (const d of datums) {
|
|
397
|
-
const sn = d.getAttribute('data-series-name');
|
|
398
|
-
if (sn != null)
|
|
399
|
-
d.classList.toggle('dgmo-dim', name != null && sn !== name);
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
const legendListeners: Array<[SVGGElement, string, EventListener]> = [];
|
|
403
|
-
if (legendEntries.length > 0 && seriesGroups.length > 1) {
|
|
404
|
-
for (const entry of legendEntries) {
|
|
405
|
-
const name = entry.getAttribute('data-series-name');
|
|
406
|
-
if (name == null) continue;
|
|
407
|
-
const onEnter: EventListener = () => {
|
|
408
|
-
// The pointer is on the legend (outside the plot interior), so any
|
|
409
|
-
// lingering crosshair/datum hover must yield — clear it first, else a
|
|
410
|
-
// stale flag from the plot→legend move would suppress the emphasis.
|
|
411
|
-
clearDatum();
|
|
412
|
-
if (crosshairActive) {
|
|
413
|
-
clearCrosshair();
|
|
414
|
-
crosshairActive = false;
|
|
415
|
-
}
|
|
416
|
-
emphasizeSeries(name);
|
|
417
|
-
};
|
|
418
|
-
const onExit: EventListener = () => emphasizeSeries(null);
|
|
419
|
-
entry.addEventListener('mouseenter', onEnter);
|
|
420
|
-
entry.addEventListener('mouseleave', onExit);
|
|
421
|
-
legendListeners.push([entry, 'mouseenter', onEnter]);
|
|
422
|
-
legendListeners.push([entry, 'mouseleave', onExit]);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// ── axis-legend hover → emphasize the series on that axis ──────────────
|
|
427
|
-
// Dual-axis charts (line.ts) tag each axis strip (title + ticks) with
|
|
428
|
-
// data-axis-legend="left"|"right" and each series group with data-axis.
|
|
429
|
-
// Hovering an axis dims every series NOT on it, so the colour-tinted axis
|
|
430
|
-
// reads as a legend for its line(s). Same dim mechanism as series legends.
|
|
431
|
-
const emphasizeAxis = (axis: string | null) => {
|
|
432
|
-
for (const g of seriesGroups) {
|
|
433
|
-
const a = g.getAttribute('data-axis');
|
|
434
|
-
if (a != null) g.classList.toggle('dgmo-dim', axis != null && a !== axis);
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
const axisLegends = Array.from(
|
|
438
|
-
svg.querySelectorAll<SVGElement>('[data-axis-legend]')
|
|
439
|
-
);
|
|
440
|
-
if (axisLegends.length > 0 && seriesGroups.length > 1) {
|
|
441
|
-
for (const el of axisLegends) {
|
|
442
|
-
const axis = el.getAttribute('data-axis-legend');
|
|
443
|
-
if (axis == null) continue;
|
|
444
|
-
const onEnter: EventListener = () => {
|
|
445
|
-
// Axis strip is outside the plot interior — clear any lingering plot
|
|
446
|
-
// hover (stale crosshair/datum from the move that landed here would
|
|
447
|
-
// otherwise suppress the emphasis) before dimming the other axis.
|
|
448
|
-
clearDatum();
|
|
449
|
-
if (crosshairActive) {
|
|
450
|
-
clearCrosshair();
|
|
451
|
-
crosshairActive = false;
|
|
452
|
-
}
|
|
453
|
-
emphasizeAxis(axis);
|
|
454
|
-
};
|
|
455
|
-
const onExit: EventListener = () => emphasizeAxis(null);
|
|
456
|
-
el.addEventListener('mouseenter', onEnter);
|
|
457
|
-
el.addEventListener('mouseleave', onExit);
|
|
458
|
-
legendListeners.push([el as SVGGElement, 'mouseenter', onEnter]);
|
|
459
|
-
legendListeners.push([el as SVGGElement, 'mouseleave', onExit]);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
return {
|
|
464
|
-
destroy: () => {
|
|
465
|
-
svg.removeEventListener('mousemove', onSvgMove);
|
|
466
|
-
svg.removeEventListener('mouseleave', onLeave);
|
|
467
|
-
svg.removeEventListener('click', onClick);
|
|
468
|
-
for (const [el, ev, fn] of legendListeners)
|
|
469
|
-
el.removeEventListener(ev, fn);
|
|
470
|
-
if (overlay) overlay.remove();
|
|
471
|
-
restorePt();
|
|
472
|
-
clearHighlight();
|
|
473
|
-
clearDim();
|
|
474
|
-
},
|
|
475
|
-
highlight: highlightLine,
|
|
476
|
-
};
|
|
477
|
-
}
|