@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.
Files changed (319) hide show
  1. package/dist/auto.js +1 -1
  2. package/dist/auto.mjs +1 -1
  3. package/dist/element.js +1 -1
  4. package/dist/element.mjs +1 -1
  5. package/package.json +19 -70
  6. package/dist/advanced.cjs +0 -71648
  7. package/dist/advanced.d.cts +0 -5719
  8. package/dist/auto.cjs +0 -70648
  9. package/dist/auto.d.cts +0 -54
  10. package/dist/block.cjs +0 -70208
  11. package/dist/block.d.cts +0 -115
  12. package/dist/editor.cjs +0 -526
  13. package/dist/editor.d.cts +0 -26
  14. package/dist/element.cjs +0 -70530
  15. package/dist/element.d.cts +0 -62
  16. package/dist/highlight.cjs +0 -852
  17. package/dist/highlight.d.cts +0 -49
  18. package/dist/index.cjs +0 -70491
  19. package/dist/index.d.cts +0 -160
  20. package/dist/pert.cjs +0 -325
  21. package/dist/pert.d.cts +0 -559
  22. package/dist/themes-1-CuKpeH.d.cts +0 -908
  23. package/src/advanced.ts +0 -873
  24. package/src/arc/parser.ts +0 -15
  25. package/src/arc/renderer.ts +0 -633
  26. package/src/auto/index.ts +0 -621
  27. package/src/auto/shared.ts +0 -370
  28. package/src/auto/styles.ts +0 -73
  29. package/src/block/layout.ts +0 -198
  30. package/src/block/parser.ts +0 -427
  31. package/src/block/renderer.ts +0 -484
  32. package/src/block/types.ts +0 -67
  33. package/src/boxes-and-lines/collapse.ts +0 -96
  34. package/src/boxes-and-lines/focus.ts +0 -257
  35. package/src/boxes-and-lines/label-placement.ts +0 -339
  36. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  37. package/src/boxes-and-lines/layout-layered.ts +0 -722
  38. package/src/boxes-and-lines/layout-search.ts +0 -1678
  39. package/src/boxes-and-lines/layout.ts +0 -398
  40. package/src/boxes-and-lines/parser.ts +0 -1181
  41. package/src/boxes-and-lines/renderer.ts +0 -1465
  42. package/src/boxes-and-lines/types.ts +0 -66
  43. package/src/c4/layout.ts +0 -2441
  44. package/src/c4/parser.ts +0 -1158
  45. package/src/c4/renderer.ts +0 -2008
  46. package/src/c4/types.ts +0 -89
  47. package/src/chart-type-registry.ts +0 -529
  48. package/src/chart-types.ts +0 -225
  49. package/src/chart.ts +0 -685
  50. package/src/charts-d3/bar.ts +0 -338
  51. package/src/charts-d3/chord.ts +0 -140
  52. package/src/charts-d3/function.ts +0 -147
  53. package/src/charts-d3/funnel.ts +0 -104
  54. package/src/charts-d3/heatmap.ts +0 -145
  55. package/src/charts-d3/index.ts +0 -337
  56. package/src/charts-d3/interactions.ts +0 -477
  57. package/src/charts-d3/line.ts +0 -344
  58. package/src/charts-d3/mount.ts +0 -151
  59. package/src/charts-d3/pie.ts +0 -117
  60. package/src/charts-d3/polar.ts +0 -89
  61. package/src/charts-d3/radar.ts +0 -0
  62. package/src/charts-d3/sankey.ts +0 -0
  63. package/src/charts-d3/scatter.ts +0 -185
  64. package/src/charts-d3/shared.ts +0 -208
  65. package/src/class/layout.ts +0 -326
  66. package/src/class/parser.ts +0 -635
  67. package/src/class/renderer.ts +0 -793
  68. package/src/class/types.ts +0 -58
  69. package/src/cli-banner.ts +0 -106
  70. package/src/cli.ts +0 -1080
  71. package/src/colors-diagnostics.ts +0 -35
  72. package/src/colors.ts +0 -379
  73. package/src/completion-types.ts +0 -27
  74. package/src/completion.ts +0 -2668
  75. package/src/cycle/layout.ts +0 -1026
  76. package/src/cycle/parser.ts +0 -387
  77. package/src/cycle/renderer.ts +0 -653
  78. package/src/cycle/types.ts +0 -101
  79. package/src/d3.ts +0 -1434
  80. package/src/data-chart-parser.ts +0 -1007
  81. package/src/dgmo-router.ts +0 -296
  82. package/src/diagnostics-registry.ts +0 -108
  83. package/src/diagnostics.ts +0 -0
  84. package/src/dimensions.ts +0 -20
  85. package/src/directives-registry.ts +0 -396
  86. package/src/editor/dgmo.grammar +0 -76
  87. package/src/editor/dgmo.grammar.d.ts +0 -2
  88. package/src/editor/dgmo.grammar.js +0 -18
  89. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  90. package/src/editor/dgmo.grammar.terms.js +0 -35
  91. package/src/editor/highlight-api.ts +0 -596
  92. package/src/editor/highlight.ts +0 -36
  93. package/src/editor/index.ts +0 -27
  94. package/src/editor/keywords.ts +0 -128
  95. package/src/editor/tokens.ts +0 -30
  96. package/src/element/index.ts +0 -432
  97. package/src/embed/css.ts +0 -312
  98. package/src/embed/escape.ts +0 -15
  99. package/src/embed/index.ts +0 -280
  100. package/src/er/classify.ts +0 -228
  101. package/src/er/layout.ts +0 -530
  102. package/src/er/parser.ts +0 -750
  103. package/src/er/renderer.ts +0 -725
  104. package/src/er/types.ts +0 -52
  105. package/src/error-card.ts +0 -269
  106. package/src/event-line/diagnostics.ts +0 -117
  107. package/src/event-line/parser.ts +0 -643
  108. package/src/event-line/renderer.ts +0 -1742
  109. package/src/event-line/types.ts +0 -74
  110. package/src/fonts.ts +0 -3
  111. package/src/gantt/calculator.ts +0 -979
  112. package/src/gantt/parser.ts +0 -1641
  113. package/src/gantt/renderer.ts +0 -3787
  114. package/src/gantt/resolver.ts +0 -172
  115. package/src/gantt/types.ts +0 -206
  116. package/src/graph/diagnostics.ts +0 -64
  117. package/src/graph/edge-spline.ts +0 -29
  118. package/src/graph/flowchart-parser.ts +0 -759
  119. package/src/graph/flowchart-renderer.ts +0 -817
  120. package/src/graph/layout.ts +0 -472
  121. package/src/graph/notes.ts +0 -21
  122. package/src/graph/state-collapse.ts +0 -78
  123. package/src/graph/state-parser.ts +0 -574
  124. package/src/graph/state-renderer.ts +0 -653
  125. package/src/graph/types.ts +0 -65
  126. package/src/index.ts +0 -261
  127. package/src/infra/compute.ts +0 -1385
  128. package/src/infra/layout.ts +0 -888
  129. package/src/infra/parser.ts +0 -1234
  130. package/src/infra/renderer.ts +0 -2534
  131. package/src/infra/roles.ts +0 -75
  132. package/src/infra/types.ts +0 -216
  133. package/src/infra/validation.ts +0 -201
  134. package/src/journey-map/layout.ts +0 -490
  135. package/src/journey-map/parser.ts +0 -557
  136. package/src/journey-map/renderer.ts +0 -1580
  137. package/src/journey-map/types.ts +0 -47
  138. package/src/kanban/mutations.ts +0 -175
  139. package/src/kanban/parser.ts +0 -509
  140. package/src/kanban/renderer.ts +0 -1449
  141. package/src/kanban/types.ts +0 -39
  142. package/src/label-layout.ts +0 -368
  143. package/src/map/colorize.ts +0 -54
  144. package/src/map/completion.ts +0 -316
  145. package/src/map/context-labels.ts +0 -609
  146. package/src/map/data/PROVENANCE.json +0 -1
  147. package/src/map/data/README.md +0 -40
  148. package/src/map/data/airport-collisions.json +0 -1
  149. package/src/map/data/airports.json +0 -1
  150. package/src/map/data/gazetteer.json +0 -1
  151. package/src/map/data/lakes.json +0 -1
  152. package/src/map/data/mountain-ranges.json +0 -1
  153. package/src/map/data/na-lakes.json +0 -1
  154. package/src/map/data/na-land.json +0 -1
  155. package/src/map/data/region-names.json +0 -1
  156. package/src/map/data/rivers.json +0 -1
  157. package/src/map/data/types.ts +0 -121
  158. package/src/map/data/us-states.json +0 -1
  159. package/src/map/data/water-bodies.json +0 -1
  160. package/src/map/data/world-coarse.json +0 -1
  161. package/src/map/data/world-detail.json +0 -1
  162. package/src/map/diagnostics.ts +0 -134
  163. package/src/map/dimensions.ts +0 -150
  164. package/src/map/geo-query.ts +0 -295
  165. package/src/map/geo.ts +0 -438
  166. package/src/map/invert.ts +0 -111
  167. package/src/map/layout.ts +0 -5069
  168. package/src/map/legend-band.ts +0 -127
  169. package/src/map/load-data.ts +0 -186
  170. package/src/map/parser.ts +0 -783
  171. package/src/map/renderer.ts +0 -1445
  172. package/src/map/resolved-types.ts +0 -160
  173. package/src/map/resolver.ts +0 -1146
  174. package/src/map/types.ts +0 -194
  175. package/src/mindmap/collapse.ts +0 -67
  176. package/src/mindmap/layout.ts +0 -649
  177. package/src/mindmap/parser.ts +0 -430
  178. package/src/mindmap/renderer.ts +0 -637
  179. package/src/mindmap/text-wrap.ts +0 -218
  180. package/src/mindmap/types.ts +0 -56
  181. package/src/org/collapse.ts +0 -151
  182. package/src/org/layout.ts +0 -1262
  183. package/src/org/parser.ts +0 -519
  184. package/src/org/renderer.ts +0 -866
  185. package/src/org/resolver.ts +0 -636
  186. package/src/palettes/atlas.ts +0 -77
  187. package/src/palettes/blueprint.ts +0 -73
  188. package/src/palettes/catppuccin.ts +0 -84
  189. package/src/palettes/color-utils.ts +0 -443
  190. package/src/palettes/index.ts +0 -67
  191. package/src/palettes/nord.ts +0 -67
  192. package/src/palettes/registry.ts +0 -114
  193. package/src/palettes/slate.ts +0 -73
  194. package/src/palettes/tidewater.ts +0 -73
  195. package/src/palettes/tokyo-night.ts +0 -86
  196. package/src/palettes/types.ts +0 -88
  197. package/src/pert/analyzer.ts +0 -1183
  198. package/src/pert/diagnostics.ts +0 -110
  199. package/src/pert/index.ts +0 -43
  200. package/src/pert/internal.ts +0 -252
  201. package/src/pert/layout.ts +0 -971
  202. package/src/pert/monte-carlo.ts +0 -446
  203. package/src/pert/parser.ts +0 -1606
  204. package/src/pert/renderer.ts +0 -4009
  205. package/src/pert/share-normalize.ts +0 -62
  206. package/src/pert/types.ts +0 -604
  207. package/src/pyramid/parser.ts +0 -185
  208. package/src/pyramid/renderer.ts +0 -768
  209. package/src/pyramid/types.ts +0 -28
  210. package/src/quadrant/parser.ts +0 -18
  211. package/src/quadrant/renderer.ts +0 -638
  212. package/src/raci/diagnostics.ts +0 -213
  213. package/src/raci/index.ts +0 -23
  214. package/src/raci/mutations.ts +0 -321
  215. package/src/raci/parser.ts +0 -699
  216. package/src/raci/renderer.ts +0 -1627
  217. package/src/raci/types.ts +0 -82
  218. package/src/raci/variants.ts +0 -320
  219. package/src/render.ts +0 -268
  220. package/src/ring/parser.ts +0 -212
  221. package/src/ring/renderer.ts +0 -490
  222. package/src/ring/types.ts +0 -27
  223. package/src/sequence/collapse.ts +0 -174
  224. package/src/sequence/parser.ts +0 -1596
  225. package/src/sequence/participant-inference.ts +0 -152
  226. package/src/sequence/renderer.ts +0 -3168
  227. package/src/sequence/tag-resolution.ts +0 -164
  228. package/src/sharing.ts +0 -211
  229. package/src/sitemap/collapse.ts +0 -100
  230. package/src/sitemap/layout.ts +0 -925
  231. package/src/sitemap/parser.ts +0 -755
  232. package/src/sitemap/renderer.ts +0 -828
  233. package/src/sitemap/types.ts +0 -42
  234. package/src/slope/parser.ts +0 -18
  235. package/src/slope/renderer.ts +0 -378
  236. package/src/swimlane/diagnostics.ts +0 -94
  237. package/src/swimlane/layout.ts +0 -597
  238. package/src/swimlane/parser.ts +0 -613
  239. package/src/swimlane/renderer.ts +0 -681
  240. package/src/swimlane/types.ts +0 -151
  241. package/src/tech-radar/interactive.ts +0 -1129
  242. package/src/tech-radar/layout.ts +0 -197
  243. package/src/tech-radar/parser.ts +0 -436
  244. package/src/tech-radar/renderer.ts +0 -1239
  245. package/src/tech-radar/shared.ts +0 -151
  246. package/src/tech-radar/types.ts +0 -86
  247. package/src/themes.ts +0 -22
  248. package/src/timeline/parser.ts +0 -472
  249. package/src/timeline/renderer.ts +0 -3024
  250. package/src/timeline/types.ts +0 -33
  251. package/src/timeline/viz-parser.ts +0 -21
  252. package/src/treemap/diagnostics.ts +0 -62
  253. package/src/treemap/layout-radial.ts +0 -233
  254. package/src/treemap/layout.ts +0 -189
  255. package/src/treemap/parser.ts +0 -463
  256. package/src/treemap/renderer-radial.ts +0 -438
  257. package/src/treemap/renderer.ts +0 -572
  258. package/src/treemap/treemap-shared.ts +0 -309
  259. package/src/treemap/types.ts +0 -60
  260. package/src/utils/arrow-markers.ts +0 -51
  261. package/src/utils/arrows-diagnostics.ts +0 -62
  262. package/src/utils/arrows.ts +0 -227
  263. package/src/utils/brand.ts +0 -61
  264. package/src/utils/card.ts +0 -183
  265. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  266. package/src/utils/collapse-engine/tree.ts +0 -117
  267. package/src/utils/d3-helpers.ts +0 -219
  268. package/src/utils/d3-types.ts +0 -9
  269. package/src/utils/description-helpers.ts +0 -38
  270. package/src/utils/duration.ts +0 -312
  271. package/src/utils/export-container.ts +0 -41
  272. package/src/utils/expr-eval.ts +0 -27
  273. package/src/utils/fit-canvas.ts +0 -64
  274. package/src/utils/hover-styles.ts +0 -673
  275. package/src/utils/inline-markdown.ts +0 -96
  276. package/src/utils/legend-constants.ts +0 -48
  277. package/src/utils/legend-d3.ts +0 -618
  278. package/src/utils/legend-integration.ts +0 -107
  279. package/src/utils/legend-layout.ts +0 -776
  280. package/src/utils/legend-svg.ts +0 -214
  281. package/src/utils/legend-types.ts +0 -272
  282. package/src/utils/name-normalize.ts +0 -163
  283. package/src/utils/note-box/constants.ts +0 -25
  284. package/src/utils/note-box/index.ts +0 -11
  285. package/src/utils/note-box/metrics.ts +0 -90
  286. package/src/utils/note-box/svg.ts +0 -331
  287. package/src/utils/notes/bounds.ts +0 -30
  288. package/src/utils/notes/build.ts +0 -131
  289. package/src/utils/notes/index.ts +0 -18
  290. package/src/utils/notes/model.ts +0 -19
  291. package/src/utils/notes/parse.ts +0 -131
  292. package/src/utils/notes/place.ts +0 -177
  293. package/src/utils/notes/resolve.ts +0 -88
  294. package/src/utils/number-format.ts +0 -36
  295. package/src/utils/parsing.ts +0 -953
  296. package/src/utils/reserved-key-registry.ts +0 -239
  297. package/src/utils/safe-href.ts +0 -38
  298. package/src/utils/scaling.ts +0 -97
  299. package/src/utils/svg-embed.ts +0 -251
  300. package/src/utils/tag-groups.ts +0 -607
  301. package/src/utils/text-measure.ts +0 -122
  302. package/src/utils/time-ticks.ts +0 -253
  303. package/src/utils/title-constants.ts +0 -24
  304. package/src/utils/visual-conventions.ts +0 -61
  305. package/src/utils/wrapped-desc.ts +0 -87
  306. package/src/venn/parser.ts +0 -18
  307. package/src/venn/renderer.ts +0 -1047
  308. package/src/version-control/diagnostics.ts +0 -67
  309. package/src/version-control/parser.ts +0 -544
  310. package/src/version-control/renderer.ts +0 -724
  311. package/src/version-control/types.ts +0 -100
  312. package/src/visualizations/parse.ts +0 -1560
  313. package/src/visualizations/types.ts +0 -262
  314. package/src/wireframe/layout.ts +0 -479
  315. package/src/wireframe/parser.ts +0 -1114
  316. package/src/wireframe/renderer.ts +0 -1318
  317. package/src/wireframe/types.ts +0 -110
  318. package/src/wordcloud/parser.ts +0 -18
  319. 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
- }