@vielzeug/prism 26.11.5 → 26.11.6

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 (105) hide show
  1. package/README.md +25 -10
  2. package/dist/_dev.js +1 -2
  3. package/dist/_tappers.js +1 -2
  4. package/dist/animation/easing.js +1 -2
  5. package/dist/animation/motion.js +0 -1
  6. package/dist/animation/reveal.cjs +1 -1
  7. package/dist/animation/reveal.cjs.map +1 -1
  8. package/dist/animation/reveal.js +29 -30
  9. package/dist/animation/transition.js +1 -2
  10. package/dist/animation/tween.js +1 -2
  11. package/dist/axes/axis.js +1 -2
  12. package/dist/axes/grid.js +1 -2
  13. package/dist/axes/scale-utils.js +1 -2
  14. package/dist/charts/area/area-chart.cjs +1 -1
  15. package/dist/charts/area/area-chart.cjs.map +1 -1
  16. package/dist/charts/area/area-chart.js +34 -35
  17. package/dist/charts/area/area-renderer.cjs +1 -1
  18. package/dist/charts/area/area-renderer.cjs.map +1 -1
  19. package/dist/charts/area/area-renderer.js +56 -57
  20. package/dist/charts/area/area-renderer.js.map +1 -1
  21. package/dist/charts/bar/bar-chart.cjs +1 -1
  22. package/dist/charts/bar/bar-chart.cjs.map +1 -1
  23. package/dist/charts/bar/bar-chart.js +80 -81
  24. package/dist/charts/bar/bar-chart.js.map +1 -1
  25. package/dist/charts/bar/bar-hit-test.js +1 -2
  26. package/dist/charts/bar/bar-renderer.cjs +1 -1
  27. package/dist/charts/bar/bar-renderer.cjs.map +1 -1
  28. package/dist/charts/bar/bar-renderer.js +42 -43
  29. package/dist/charts/bar/bar-value-labels.cjs +1 -1
  30. package/dist/charts/bar/bar-value-labels.cjs.map +1 -1
  31. package/dist/charts/bar/bar-value-labels.js +17 -18
  32. package/dist/charts/heatmap/heatmap-chart.cjs +1 -1
  33. package/dist/charts/heatmap/heatmap-chart.cjs.map +1 -1
  34. package/dist/charts/heatmap/heatmap-chart.js +43 -44
  35. package/dist/charts/heatmap/heatmap-color.js +1 -2
  36. package/dist/charts/line/line-chart.cjs +1 -1
  37. package/dist/charts/line/line-chart.cjs.map +1 -1
  38. package/dist/charts/line/line-chart.js +41 -42
  39. package/dist/charts/line/line-renderer.cjs +1 -1
  40. package/dist/charts/line/line-renderer.cjs.map +1 -1
  41. package/dist/charts/line/line-renderer.js +81 -82
  42. package/dist/charts/line/line-renderer.js.map +1 -1
  43. package/dist/charts/pie/pie-chart.cjs +1 -1
  44. package/dist/charts/pie/pie-chart.cjs.map +1 -1
  45. package/dist/charts/pie/pie-chart.js +69 -70
  46. package/dist/charts/pie/pie-renderer.js +1 -2
  47. package/dist/charts/radar/radar-chart.cjs +1 -1
  48. package/dist/charts/radar/radar-chart.cjs.map +1 -1
  49. package/dist/charts/radar/radar-chart.js +118 -119
  50. package/dist/charts/radar/radar-chart.js.map +1 -1
  51. package/dist/charts/radar/radar-geometry.cjs +1 -1
  52. package/dist/charts/radar/radar-geometry.cjs.map +1 -1
  53. package/dist/charts/radar/radar-geometry.js +15 -16
  54. package/dist/charts/scatter/scatter-chart.cjs +1 -1
  55. package/dist/charts/scatter/scatter-chart.cjs.map +1 -1
  56. package/dist/charts/scatter/scatter-chart.js +33 -34
  57. package/dist/charts/scatter/scatter-renderer.cjs +1 -1
  58. package/dist/charts/scatter/scatter-renderer.cjs.map +1 -1
  59. package/dist/charts/scatter/scatter-renderer.js +22 -23
  60. package/dist/charts/sparkline/sparkline.cjs +1 -1
  61. package/dist/charts/sparkline/sparkline.cjs.map +1 -1
  62. package/dist/charts/sparkline/sparkline.js +87 -88
  63. package/dist/charts/sparkline/sparkline.js.map +1 -1
  64. package/dist/core/cartesian-model.js +1 -2
  65. package/dist/core/cartesian-scales.js +1 -2
  66. package/dist/core/chart-base.cjs +1 -1
  67. package/dist/core/chart-base.cjs.map +1 -1
  68. package/dist/core/chart-base.js +13 -14
  69. package/dist/core/chart-scaffold.cjs +1 -1
  70. package/dist/core/chart-scaffold.cjs.map +1 -1
  71. package/dist/core/chart-scaffold.js +33 -34
  72. package/dist/core/datum-style.js +1 -2
  73. package/dist/core/ids.js +1 -2
  74. package/dist/core/layout.js +1 -2
  75. package/dist/core/polar.js +1 -2
  76. package/dist/core/responsive.js +1 -2
  77. package/dist/core/series-id.js +1 -2
  78. package/dist/errors.js +1 -2
  79. package/dist/index.cjs +1 -1
  80. package/dist/index.js +14 -14
  81. package/dist/interaction/announcer.js +1 -2
  82. package/dist/interaction/crosshair.js +1 -2
  83. package/dist/interaction/events.js +1 -2
  84. package/dist/interaction/legend.js +1 -2
  85. package/dist/interaction/scatter-interaction.cjs +1 -1
  86. package/dist/interaction/scatter-interaction.cjs.map +1 -1
  87. package/dist/interaction/scatter-interaction.js +26 -27
  88. package/dist/interaction/series-interaction.cjs +1 -1
  89. package/dist/interaction/series-interaction.cjs.map +1 -1
  90. package/dist/interaction/series-interaction.js +40 -41
  91. package/dist/interaction/tooltip.js +1 -2
  92. package/dist/prism.cjs +1 -1
  93. package/dist/prism.cjs.map +1 -1
  94. package/dist/prism.iife.js +1 -1
  95. package/dist/prism.iife.js.map +1 -1
  96. package/dist/prism.js +52 -103
  97. package/dist/prism.js.map +1 -1
  98. package/dist/scales/band.js +1 -2
  99. package/dist/scales/linear.js +1 -2
  100. package/dist/scales/time.js +1 -2
  101. package/dist/svg/element.js +1 -2
  102. package/dist/svg/path.js +1 -2
  103. package/dist/svg/text.js +1 -2
  104. package/dist/theme/index.js +1 -2
  105. package/package.json +6 -6
@@ -1,8 +1,8 @@
1
- import { createSvgElement as e, setAttributes as t } from "../../svg/element.js";
2
- import { resolveMotion as n } from "../../animation/motion.js";
3
- import { animate as r } from "../../animation/transition.js";
4
- import { playGrowReveal as i } from "../../animation/reveal.js";
5
- //#region src/charts/bar/bar-renderer.ts
1
+ import { resolveMotion as e } from "../../animation/motion.js";
2
+ import { playGrowReveal as t } from "../../animation/reveal.js";
3
+ import { animate as n } from "../../animation/transition.js";
4
+ import { createSvgElement as r, setAttributes as i } from "../../svg/element.js";
5
+ // src/charts/bar/bar-renderer.ts
6
6
  var a = 20, o = 400, s = /* @__PURE__ */ new WeakMap();
7
7
  function barsOf(e) {
8
8
  return [...e.children].filter((e) => e.classList.contains("prism-bar"));
@@ -11,32 +11,32 @@ function renderBars(c, l, u, d, f, p) {
11
11
  s.get(c)?.(), s.delete(c);
12
12
  let m = barsOf(c);
13
13
  for (let e of m.slice(l.length)) e.remove();
14
- let h = u.bandwidth(), g = p.stacked ? h : h / p.seriesCount, _ = Math.max(0, g - 1), v = p.stacked ? 0 : g * p.seriesIndex, y = p.horizontal ?? !1, b = m.length === 0, x = [], S = [], C = [], w = n(p.transition, { defaultStagger: b ? 0 : a }), T = w.duration > 0, E = Math.min(w.stagger, o / Math.max(1, l.length));
15
- for (let n = 0; n < l.length; n++) {
16
- let r = l[n], i = u.map(r.key) + v, a = p.baselineYs ? p.baselineYs[n] : f, o = d.map(r.y), s = m[n], h = !s;
17
- if (s || (s = e("rect", { class: "prism-bar" }), c.appendChild(s)), s.style.display = r.present ? "" : "none", t(s, {
18
- opacity: r.opacity,
19
- stroke: r.dash ? p.color : void 0,
20
- "stroke-dasharray": r.dash,
21
- "stroke-width": r.dash ? 1 : void 0
14
+ let h = u.bandwidth(), g = p.stacked ? h : h / p.seriesCount, _ = Math.max(0, g - 1), v = p.stacked ? 0 : g * p.seriesIndex, y = p.horizontal ?? !1, b = m.length === 0, x = [], S = [], C = [], w = e(p.transition, { defaultStagger: b ? 0 : a }), T = w.duration > 0, E = Math.min(w.stagger, o / Math.max(1, l.length));
15
+ for (let e = 0; e < l.length; e++) {
16
+ let t = l[e], n = u.map(t.key) + v, a = p.baselineYs ? p.baselineYs[e] : f, o = d.map(t.y), s = m[e], h = !s;
17
+ if (s || (s = r("rect", { class: "prism-bar" }), c.appendChild(s)), s.style.display = t.present ? "" : "none", i(s, {
18
+ opacity: t.opacity,
19
+ stroke: t.dash ? p.color : void 0,
20
+ "stroke-dasharray": t.dash,
21
+ "stroke-width": t.dash ? 1 : void 0
22
22
  }), y) {
23
- let e = Math.max(0, Math.abs(a - o)), n = Math.min(o, a);
24
- t(s, {
23
+ let e = Math.max(0, Math.abs(a - o)), t = Math.min(o, a);
24
+ i(s, {
25
25
  fill: p.color,
26
26
  height: _,
27
27
  rx: p.borderRadius,
28
28
  ry: p.borderRadius,
29
- y: i
30
- }), b ? (t(s, {
29
+ y: n
30
+ }), b ? (i(s, {
31
31
  width: e,
32
- x: n
32
+ x: t
33
33
  }), T && C.push({
34
34
  anchor: a,
35
35
  height: _,
36
36
  width: e,
37
- x: n,
38
- y: i
39
- })) : T ? h ? (t(s, {
37
+ x: t,
38
+ y: n
39
+ })) : T ? h ? (i(s, {
40
40
  width: 0,
41
41
  x: a
42
42
  }), x.push({
@@ -47,7 +47,7 @@ function renderBars(c, l, u, d, f, p) {
47
47
  },
48
48
  x: {
49
49
  from: a,
50
- to: n
50
+ to: t
51
51
  }
52
52
  },
53
53
  el: s
@@ -58,33 +58,33 @@ function renderBars(c, l, u, d, f, p) {
58
58
  to: e
59
59
  },
60
60
  x: {
61
- from: Number(s.getAttribute("x") ?? n),
62
- to: n
61
+ from: Number(s.getAttribute("x") ?? t),
62
+ to: t
63
63
  }
64
64
  },
65
65
  el: s
66
- }) : t(s, {
66
+ }) : i(s, {
67
67
  width: e,
68
- x: n
68
+ x: t
69
69
  });
70
70
  } else {
71
- let e = Math.max(0, Math.abs(a - o)), n = Math.min(o, a);
72
- t(s, {
71
+ let e = Math.max(0, Math.abs(a - o)), t = Math.min(o, a);
72
+ i(s, {
73
73
  fill: p.color,
74
74
  rx: p.borderRadius,
75
75
  ry: p.borderRadius,
76
76
  width: _,
77
- x: i
78
- }), b ? (t(s, {
77
+ x: n
78
+ }), b ? (i(s, {
79
79
  height: e,
80
- y: n
80
+ y: t
81
81
  }), T && C.push({
82
82
  anchor: a,
83
83
  height: e,
84
84
  width: _,
85
- x: i,
86
- y: n
87
- })) : T ? h ? (t(s, {
85
+ x: n,
86
+ y: t
87
+ })) : T ? h ? (i(s, {
88
88
  height: 0,
89
89
  y: a
90
90
  }), x.push({
@@ -95,7 +95,7 @@ function renderBars(c, l, u, d, f, p) {
95
95
  },
96
96
  y: {
97
97
  from: a,
98
- to: n
98
+ to: t
99
99
  }
100
100
  },
101
101
  el: s
@@ -106,27 +106,27 @@ function renderBars(c, l, u, d, f, p) {
106
106
  to: e
107
107
  },
108
108
  y: {
109
- from: Number(s.getAttribute("y") ?? n),
110
- to: n
109
+ from: Number(s.getAttribute("y") ?? t),
110
+ to: t
111
111
  }
112
112
  },
113
113
  el: s
114
- }) : t(s, {
114
+ }) : i(s, {
115
115
  height: e,
116
- y: n
116
+ y: t
117
117
  });
118
118
  }
119
119
  }
120
120
  if (T) {
121
121
  let e = [];
122
- b && C.length > 0 && e.push(i(c, C, () => {}, w, {
122
+ b && C.length > 0 && e.push(t(c, C, () => {}, w, {
123
123
  axis: y ? "x" : "y",
124
124
  signal: p.disposalSignal,
125
125
  stagger: E
126
- })), x.length > 0 && e.push(r(x, {
126
+ })), x.length > 0 && e.push(n(x, {
127
127
  ...w,
128
128
  stagger: E
129
- }, void 0, p.disposalSignal)), S.length > 0 && e.push(r(S, {
129
+ }, void 0, p.disposalSignal)), S.length > 0 && e.push(n(S, {
130
130
  ...w,
131
131
  stagger: 0
132
132
  }, void 0, p.disposalSignal)), s.set(c, () => {
@@ -134,7 +134,6 @@ function renderBars(c, l, u, d, f, p) {
134
134
  });
135
135
  }
136
136
  }
137
- //#endregion
138
137
  export { renderBars };
139
138
 
140
139
  //# sourceMappingURL=bar-renderer.js.map
@@ -1,2 +1,2 @@
1
- const e=require("../../svg/element.cjs"),t=require("../../animation/motion.cjs"),n=require("../../animation/transition.cjs"),r=require("../heatmap/heatmap-color.cjs");var i=6.6,a=6,o=4,s=16;function estWidth(e){return e.length*i+o}function renderBarValueLabels(i,o,c){for(;i.firstChild;)i.removeChild(i.firstChild);for(let t of o){let n=estWidth(t.text),o=c.horizontal?t.dir*a:-t.dir*a,l=t.valueEdge+o,u=c.horizontal?o>0?l+n<=c.areaExtent:l-n>=0:o<0?l>=0:l+11<=c.areaExtent,d=c.horizontal?t.bandSize>s&&(o>0?t.valueEdge-a-n>=0:t.valueEdge+a+n<=c.areaExtent):t.barLength>s&&t.bandSize>=n;if(!u&&!d)continue;let f=!u,p=e.createSvgElement(`text`,{class:f?`prism-bar-value prism-bar-value--inside`:`prism-bar-value`});if(p.textContent=t.text,c.horizontal?e.setAttributes(p,{"dominant-baseline":`central`,"text-anchor":f?o>0?`end`:`start`:o>0?`start`:`end`,x:String(f?t.valueEdge-o:l),y:String(t.center)}):e.setAttributes(p,{"dominant-baseline":f?o<0?`hanging`:`auto`:o<0?`auto`:`hanging`,"text-anchor":`middle`,x:String(t.center),y:String(f?t.valueEdge-o:l)}),f){let e=barAt(i,c,t),n=e?getComputedStyle(e).fill:``;p.style.setProperty(`fill`,r.valueColorFor(n,`var(--prism-text-color, #0f172a)`))}i.appendChild(p)}let l=t.resolveMotion(c.transition,{defaultStagger:0});l.duration>0&&n.animate([{attrs:{opacity:{from:0,to:1}},el:i}],{...l,stagger:0},void 0,c.disposalSignal)}function barAt(e,t,n){let r=[...e.parentElement?.querySelectorAll(`.prism-bar`)??[]];for(let e of r){let r=Number(e.getAttribute(t.horizontal?`y`:`x`)),i=Number(e.getAttribute(t.horizontal?`height`:`width`));if(n.center>=r&&n.center<=r+i)return e}return r[0]??null}exports.renderBarValueLabels=renderBarValueLabels;
1
+ const e=require("../../animation/motion.cjs"),t=require("../../animation/transition.cjs"),n=require("../../svg/element.cjs"),r=require("../heatmap/heatmap-color.cjs");var i=6.6,a=6,o=4,s=16;function estWidth(e){return e.length*i+o}function renderBarValueLabels(i,o,c){for(;i.firstChild;)i.removeChild(i.firstChild);for(let e of o){let t=estWidth(e.text),o=c.horizontal?e.dir*a:-e.dir*a,l=e.valueEdge+o,u=c.horizontal?o>0?l+t<=c.areaExtent:l-t>=0:o<0?l>=0:l+11<=c.areaExtent,d=c.horizontal?e.bandSize>s&&(o>0?e.valueEdge-a-t>=0:e.valueEdge+a+t<=c.areaExtent):e.barLength>s&&e.bandSize>=t;if(!u&&!d)continue;let f=!u,p=n.createSvgElement(`text`,{class:f?`prism-bar-value prism-bar-value--inside`:`prism-bar-value`});if(p.textContent=e.text,c.horizontal?n.setAttributes(p,{"dominant-baseline":`central`,"text-anchor":f?o>0?`end`:`start`:o>0?`start`:`end`,x:String(f?e.valueEdge-o:l),y:String(e.center)}):n.setAttributes(p,{"dominant-baseline":f?o<0?`hanging`:`auto`:o<0?`auto`:`hanging`,"text-anchor":`middle`,x:String(e.center),y:String(f?e.valueEdge-o:l)}),f){let t=barAt(i,c,e),n=t?getComputedStyle(t).fill:``;p.style.setProperty(`fill`,r.valueColorFor(n,`var(--prism-text-color, #0f172a)`))}i.appendChild(p)}let l=e.resolveMotion(c.transition,{defaultStagger:0});l.duration>0&&t.animate([{attrs:{opacity:{from:0,to:1}},el:i}],{...l,stagger:0},void 0,c.disposalSignal)}function barAt(e,t,n){let r=[...e.parentElement?.querySelectorAll(`.prism-bar`)??[]];for(let e of r){let r=Number(e.getAttribute(t.horizontal?`y`:`x`)),i=Number(e.getAttribute(t.horizontal?`height`:`width`));if(n.center>=r&&n.center<=r+i)return e}return r[0]??null}exports.renderBarValueLabels=renderBarValueLabels;
2
2
  //# sourceMappingURL=bar-value-labels.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bar-value-labels.cjs","names":["createSvgElement","valueColorFor","resolveMotion","require_element","require_motion","require_transition","require_heatmap_color","CHAR_PX","STANDOFF_PX","PAD_PX","INSIDE_BAND_MIN","text","group","label","labels","width","step","options","outsidePos","outsideFits","insideFits","inside","bar","fill","motion","bars","bandPos","size"],"sources":["../../../src/charts/bar/bar-value-labels.ts"],"sourcesContent":["import { resolveMotion } from '../../animation/motion';\nimport { animate } from '../../animation/transition';\nimport { createSvgElement, setAttributes } from '../../svg/element';\nimport type { TransitionOption } from '../../types';\nimport { valueColorFor } from '../heatmap/heatmap-color';\n\n/**\n * One entry per label to paint, in value-axis/band-axis screen space: the\n * value axis runs along x for horizontal bars and along y (inverted) for\n * vertical ones; the band axis is the category axis.\n */\nexport interface BarValueLabel {\n /** Bar thickness along the category axis (inside-fit room for text height). */\n bandSize: number;\n /** Bar length along the value axis, from the value edge toward the baseline. */\n barLength: number;\n /** Band-axis center of the bar. */\n center: number;\n /** Sign of the bar's value: +1 grows right (horizontal) or up (vertical). */\n dir: 1 | -1;\n text: string;\n /** Value-axis pixel of the bar's value-side edge (screen px, plot-local). */\n valueEdge: number;\n}\n\nexport interface BarValueLabelOptions {\n /** Plot extent along the value axis (area.width horizontal, area.height vertical). */\n areaExtent: number;\n disposalSignal?: AbortSignal;\n horizontal: boolean;\n transition?: TransitionOption;\n}\n\n/**\n * Width estimate for the flip decision, matching the axis-label estimate used\n * across the examples: label font size (11px) times an average advance of\n * 0.6em, plus a small safety pad. Deliberately conservative: a label that is\n * misjudged flips inside its own bar, which is always readable.\n */\nconst CHAR_PX = 11 * 0.6;\nconst STANDOFF_PX = 6;\nconst PAD_PX = 4;\n/** Minimum room along the band axis for text to sit inside a bar. */\nconst INSIDE_BAND_MIN = 16;\n\nfunction estWidth(text: string): number {\n return text.length * CHAR_PX + PAD_PX;\n}\n\n/**\n * Paint (or repaint) the value labels into `group`, replacing its children.\n * Labels sit just past each bar's value-side end; one that would leave the\n * plot flips inside the bar with a fill contrasted against the bar's computed\n * color; one that fits neither way is skipped rather than clipped.\n */\nexport function renderBarValueLabels(group: SVGGElement, labels: BarValueLabel[], options: BarValueLabelOptions): void {\n while (group.firstChild) group.removeChild(group.firstChild);\n\n for (const label of labels) {\n const width = estWidth(label.text);\n // The outside direction in value-axis px: positive bars grow right\n // (horizontal) or up-screen (vertical, whose scale is inverted); negative\n // bars mirror. `step` is the signed stand-off toward the outside.\n const step = options.horizontal ? label.dir * STANDOFF_PX : -label.dir * STANDOFF_PX;\n const outsidePos = label.valueEdge + step;\n // Outside fits when the label's box stays inside the plot along the value\n // axis: [outsidePos, outsidePos + width] for horizontal, a text height of\n // ~11px for vertical.\n const outsideFits = options.horizontal\n ? step > 0\n ? outsidePos + width <= options.areaExtent\n : outsidePos - width >= 0\n : step < 0\n ? outsidePos >= 0\n : outsidePos + 11 <= options.areaExtent;\n // Inside: within the bar at its value end, along both axes.\n const insideFits = options.horizontal\n ? label.bandSize > INSIDE_BAND_MIN &&\n (step > 0\n ? label.valueEdge - STANDOFF_PX - width >= 0\n : label.valueEdge + STANDOFF_PX + width <= options.areaExtent)\n : label.barLength > INSIDE_BAND_MIN && label.bandSize >= width;\n\n if (!outsideFits && !insideFits) continue;\n\n const inside = !outsideFits;\n const text = createSvgElement('text', {\n class: inside ? 'prism-bar-value prism-bar-value--inside' : 'prism-bar-value',\n });\n\n text.textContent = label.text;\n\n if (options.horizontal) {\n setAttributes(text, {\n 'dominant-baseline': 'central',\n 'text-anchor': inside ? (step > 0 ? 'end' : 'start') : step > 0 ? 'start' : 'end',\n x: String(inside ? label.valueEdge - step : outsidePos),\n y: String(label.center),\n });\n } else {\n setAttributes(text, {\n 'dominant-baseline': inside ? (step < 0 ? 'hanging' : 'auto') : step < 0 ? 'auto' : 'hanging',\n 'text-anchor': 'middle',\n x: String(label.center),\n y: String(inside ? label.valueEdge - step : outsidePos),\n });\n }\n\n if (inside) {\n // Contrast against the bar itself: the rect's computed fill resolves CSS\n // colors (jsdom leaves it empty, where the theme text fallback applies).\n const bar = barAt(group, options, label);\n const fill = bar ? getComputedStyle(bar).fill : '';\n\n text.style.setProperty('fill', valueColorFor(fill, 'var(--prism-text-color, #0f172a)'));\n }\n\n group.appendChild(text);\n }\n\n // Bars grow in; labels fade in over the same window so they never sit on\n // empty space mid-reveal. Reduced motion (duration 0) shows them directly.\n const motion = resolveMotion(options.transition, { defaultStagger: 0 });\n\n if (motion.duration > 0) {\n animate(\n [{ attrs: { opacity: { from: 0, to: 1 } }, el: group }],\n { ...motion, stagger: 0 },\n undefined,\n options.disposalSignal,\n );\n }\n}\n\n/**\n * The bar a flipped label sits in: a rect whose band contains the label\n * center. Only used to read a computed fill for contrast, so an approximate\n * match is acceptable and a miss falls back to theme text.\n */\nfunction barAt(group: SVGGElement, options: BarValueLabelOptions, label: BarValueLabel): SVGRectElement | null {\n const bars = [...(group.parentElement?.querySelectorAll<SVGRectElement>('.prism-bar') ?? [])];\n\n for (const bar of bars) {\n const bandPos = Number(bar.getAttribute(options.horizontal ? 'y' : 'x'));\n const size = Number(bar.getAttribute(options.horizontal ? 'height' : 'width'));\n\n if (label.center >= bandPos && label.center <= bandPos + size) return bar;\n }\n\n return bars[0] ?? null;\n}\n"],"mappings":"A,uKAuCA,IAAMO,EAAU,IACVC,EAAc,EACdC,EAAS,EAETC,EAAkB,GAExB,SAAS,SAAS,EAAsB,CACtC,OAAOC,EAAK,OAASJ,EAAUE,CACjC,CAQA,SAAgB,qBAAqB,EAAoB,EAAyB,EAAqC,CACrH,KAAOG,EAAM,YAAYA,EAAM,YAAYA,EAAM,UAAU,EAE3D,IAAK,IAAMC,KAASC,EAAQ,CAC1B,IAAMC,EAAQ,SAASF,EAAM,IAAI,EAI3BG,EAAOC,EAAQ,WAAaJ,EAAM,IAAML,EAAc,CAACK,EAAM,IAAML,EACnEU,EAAaL,EAAM,UAAYG,EAI/BG,EAAcF,EAAQ,WACxBD,EAAO,EACLE,EAAaH,GAASE,EAAQ,WAC9BC,EAAaH,GAAS,EACxBC,EAAO,EACLE,GAAc,EACdA,EAAa,IAAMD,EAAQ,WAE3BG,EAAaH,EAAQ,WACvBJ,EAAM,SAAWH,IAChBM,EAAO,EACJH,EAAM,UAAYL,EAAcO,GAAS,EACzCF,EAAM,UAAYL,EAAcO,GAASE,EAAQ,YACrDJ,EAAM,UAAYH,GAAmBG,EAAM,UAAYE,EAE3D,GAAI,CAACI,GAAe,CAACC,EAAY,SAEjC,IAAMC,EAAS,CAACF,EACVR,EAAOX,EAAAA,iBAAiB,OAAQ,CACpC,MAAOqB,EAAS,0CAA4C,iBAC9D,CAAC,EAoBD,GAlBA,EAAK,YAAcR,EAAM,KAErBI,EAAQ,WACVd,EAAA,cAAcQ,EAAM,CAClB,oBAAqB,UACrB,cAAeU,EAAUL,EAAO,EAAI,MAAQ,QAAWA,EAAO,EAAI,QAAU,MAC5E,EAAG,OAAOK,EAASR,EAAM,UAAYG,EAAOE,CAAU,EACtD,EAAG,OAAOL,EAAM,MAAM,CACxB,CAAC,EAEDV,EAAA,cAAcQ,EAAM,CAClB,oBAAqBU,EAAUL,EAAO,EAAI,UAAY,OAAUA,EAAO,EAAI,OAAS,UACpF,cAAe,SACf,EAAG,OAAOH,EAAM,MAAM,EACtB,EAAG,OAAOQ,EAASR,EAAM,UAAYG,EAAOE,CAAU,CACxD,CAAC,EAGCG,EAAQ,CAGV,IAAMC,EAAM,MAAMV,EAAOK,EAASJ,CAAK,EACjCU,EAAOD,EAAM,iBAAiBA,CAAG,CAAC,CAAC,KAAO,GAEhDX,EAAK,MAAM,YAAY,OAAQV,EAAAA,cAAcsB,EAAM,kCAAkC,CAAC,CACxF,CAEAX,EAAM,YAAYD,CAAI,CACxB,CAIA,IAAMa,EAAStB,EAAAA,cAAce,EAAQ,WAAY,CAAE,eAAgB,CAAE,CAAC,EAElEO,EAAO,SAAW,GACpBnB,EAAA,QACE,CAAC,CAAE,MAAO,CAAE,QAAS,CAAE,KAAM,EAAG,GAAI,CAAE,CAAE,EAAG,GAAIO,CAAM,CAAC,EACtD,CAAE,GAAGY,EAAQ,QAAS,CAAE,EACxB,IAAA,GACAP,EAAQ,cACV,CAEJ,CAOA,SAAS,MAAM,EAAoB,EAA+B,EAA6C,CAC7G,IAAMQ,EAAO,CAAC,GAAIb,EAAM,eAAe,iBAAiC,YAAY,GAAK,CAAC,CAAE,EAE5F,IAAK,IAAMU,KAAOG,EAAM,CACtB,IAAMC,EAAU,OAAOJ,EAAI,aAAaL,EAAQ,WAAa,IAAM,GAAG,CAAC,EACjEU,EAAO,OAAOL,EAAI,aAAaL,EAAQ,WAAa,SAAW,OAAO,CAAC,EAE7E,GAAIJ,EAAM,QAAUa,GAAWb,EAAM,QAAUa,EAAUC,EAAM,OAAOL,CACxE,CAEA,OAAOG,EAAK,IAAM,IACpB,C"}
1
+ {"version":3,"file":"bar-value-labels.cjs","names":["createSvgElement","valueColorFor","resolveMotion","require_motion","require_transition","require_element","require_heatmap_color","CHAR_PX","STANDOFF_PX","PAD_PX","INSIDE_BAND_MIN","text","group","label","labels","width","step","options","outsidePos","outsideFits","insideFits","inside","bar","fill","motion","bars","bandPos","size"],"sources":["../../../src/charts/bar/bar-value-labels.ts"],"sourcesContent":["import { resolveMotion } from '../../animation/motion';\nimport { animate } from '../../animation/transition';\nimport { createSvgElement, setAttributes } from '../../svg/element';\nimport type { TransitionOption } from '../../types';\nimport { valueColorFor } from '../heatmap/heatmap-color';\n\n/**\n * One entry per label to paint, in value-axis/band-axis screen space: the\n * value axis runs along x for horizontal bars and along y (inverted) for\n * vertical ones; the band axis is the category axis.\n */\nexport interface BarValueLabel {\n /** Bar thickness along the category axis (inside-fit room for text height). */\n bandSize: number;\n /** Bar length along the value axis, from the value edge toward the baseline. */\n barLength: number;\n /** Band-axis center of the bar. */\n center: number;\n /** Sign of the bar's value: +1 grows right (horizontal) or up (vertical). */\n dir: 1 | -1;\n text: string;\n /** Value-axis pixel of the bar's value-side edge (screen px, plot-local). */\n valueEdge: number;\n}\n\nexport interface BarValueLabelOptions {\n /** Plot extent along the value axis (area.width horizontal, area.height vertical). */\n areaExtent: number;\n disposalSignal?: AbortSignal;\n horizontal: boolean;\n transition?: TransitionOption;\n}\n\n/**\n * Width estimate for the flip decision, matching the axis-label estimate used\n * across the examples: label font size (11px) times an average advance of\n * 0.6em, plus a small safety pad. Deliberately conservative: a label that is\n * misjudged flips inside its own bar, which is always readable.\n */\nconst CHAR_PX = 11 * 0.6;\nconst STANDOFF_PX = 6;\nconst PAD_PX = 4;\n/** Minimum room along the band axis for text to sit inside a bar. */\nconst INSIDE_BAND_MIN = 16;\n\nfunction estWidth(text: string): number {\n return text.length * CHAR_PX + PAD_PX;\n}\n\n/**\n * Paint (or repaint) the value labels into `group`, replacing its children.\n * Labels sit just past each bar's value-side end; one that would leave the\n * plot flips inside the bar with a fill contrasted against the bar's computed\n * color; one that fits neither way is skipped rather than clipped.\n */\nexport function renderBarValueLabels(group: SVGGElement, labels: BarValueLabel[], options: BarValueLabelOptions): void {\n while (group.firstChild) group.removeChild(group.firstChild);\n\n for (const label of labels) {\n const width = estWidth(label.text);\n // The outside direction in value-axis px: positive bars grow right\n // (horizontal) or up-screen (vertical, whose scale is inverted); negative\n // bars mirror. `step` is the signed stand-off toward the outside.\n const step = options.horizontal ? label.dir * STANDOFF_PX : -label.dir * STANDOFF_PX;\n const outsidePos = label.valueEdge + step;\n // Outside fits when the label's box stays inside the plot along the value\n // axis: [outsidePos, outsidePos + width] for horizontal, a text height of\n // ~11px for vertical.\n const outsideFits = options.horizontal\n ? step > 0\n ? outsidePos + width <= options.areaExtent\n : outsidePos - width >= 0\n : step < 0\n ? outsidePos >= 0\n : outsidePos + 11 <= options.areaExtent;\n // Inside: within the bar at its value end, along both axes.\n const insideFits = options.horizontal\n ? label.bandSize > INSIDE_BAND_MIN &&\n (step > 0\n ? label.valueEdge - STANDOFF_PX - width >= 0\n : label.valueEdge + STANDOFF_PX + width <= options.areaExtent)\n : label.barLength > INSIDE_BAND_MIN && label.bandSize >= width;\n\n if (!outsideFits && !insideFits) continue;\n\n const inside = !outsideFits;\n const text = createSvgElement('text', {\n class: inside ? 'prism-bar-value prism-bar-value--inside' : 'prism-bar-value',\n });\n\n text.textContent = label.text;\n\n if (options.horizontal) {\n setAttributes(text, {\n 'dominant-baseline': 'central',\n 'text-anchor': inside ? (step > 0 ? 'end' : 'start') : step > 0 ? 'start' : 'end',\n x: String(inside ? label.valueEdge - step : outsidePos),\n y: String(label.center),\n });\n } else {\n setAttributes(text, {\n 'dominant-baseline': inside ? (step < 0 ? 'hanging' : 'auto') : step < 0 ? 'auto' : 'hanging',\n 'text-anchor': 'middle',\n x: String(label.center),\n y: String(inside ? label.valueEdge - step : outsidePos),\n });\n }\n\n if (inside) {\n // Contrast against the bar itself: the rect's computed fill resolves CSS\n // colors (jsdom leaves it empty, where the theme text fallback applies).\n const bar = barAt(group, options, label);\n const fill = bar ? getComputedStyle(bar).fill : '';\n\n text.style.setProperty('fill', valueColorFor(fill, 'var(--prism-text-color, #0f172a)'));\n }\n\n group.appendChild(text);\n }\n\n // Bars grow in; labels fade in over the same window so they never sit on\n // empty space mid-reveal. Reduced motion (duration 0) shows them directly.\n const motion = resolveMotion(options.transition, { defaultStagger: 0 });\n\n if (motion.duration > 0) {\n animate(\n [{ attrs: { opacity: { from: 0, to: 1 } }, el: group }],\n { ...motion, stagger: 0 },\n undefined,\n options.disposalSignal,\n );\n }\n}\n\n/**\n * The bar a flipped label sits in: a rect whose band contains the label\n * center. Only used to read a computed fill for contrast, so an approximate\n * match is acceptable and a miss falls back to theme text.\n */\nfunction barAt(group: SVGGElement, options: BarValueLabelOptions, label: BarValueLabel): SVGRectElement | null {\n const bars = [...(group.parentElement?.querySelectorAll<SVGRectElement>('.prism-bar') ?? [])];\n\n for (const bar of bars) {\n const bandPos = Number(bar.getAttribute(options.horizontal ? 'y' : 'x'));\n const size = Number(bar.getAttribute(options.horizontal ? 'height' : 'width'));\n\n if (label.center >= bandPos && label.center <= bandPos + size) return bar;\n }\n\n return bars[0] ?? null;\n}\n"],"mappings":"A,uKAuCA,IAAMO,EAAU,IACVC,EAAc,EACdC,EAAS,EAETC,EAAkB,GAExB,SAAS,SAAS,EAAsB,CACtC,OAAOC,EAAK,OAASJ,EAAUE,CACjC,CAQA,SAAgB,qBAAqB,EAAoB,EAAyB,EAAqC,CACrH,KAAOG,EAAM,YAAYA,EAAM,YAAYA,EAAM,UAAU,EAE3D,IAAK,IAAMC,KAASC,EAAQ,CAC1B,IAAMC,EAAQ,SAASF,EAAM,IAAI,EAI3BG,EAAOC,EAAQ,WAAaJ,EAAM,IAAML,EAAc,CAACK,EAAM,IAAML,EACnEU,EAAaL,EAAM,UAAYG,EAI/BG,EAAcF,EAAQ,WACxBD,EAAO,EACLE,EAAaH,GAASE,EAAQ,WAC9BC,EAAaH,GAAS,EACxBC,EAAO,EACLE,GAAc,EACdA,EAAa,IAAMD,EAAQ,WAE3BG,EAAaH,EAAQ,WACvBJ,EAAM,SAAWH,IAChBM,EAAO,EACJH,EAAM,UAAYL,EAAcO,GAAS,EACzCF,EAAM,UAAYL,EAAcO,GAASE,EAAQ,YACrDJ,EAAM,UAAYH,GAAmBG,EAAM,UAAYE,EAE3D,GAAI,CAACI,GAAe,CAACC,EAAY,SAEjC,IAAMC,EAAS,CAACF,EACVR,EAAOX,EAAAA,iBAAiB,OAAQ,CACpC,MAAOqB,EAAS,0CAA4C,iBAC9D,CAAC,EAoBD,GAlBA,EAAK,YAAcR,EAAM,KAErBI,EAAQ,WACVZ,EAAA,cAAcM,EAAM,CAClB,oBAAqB,UACrB,cAAeU,EAAUL,EAAO,EAAI,MAAQ,QAAWA,EAAO,EAAI,QAAU,MAC5E,EAAG,OAAOK,EAASR,EAAM,UAAYG,EAAOE,CAAU,EACtD,EAAG,OAAOL,EAAM,MAAM,CACxB,CAAC,EAEDR,EAAA,cAAcM,EAAM,CAClB,oBAAqBU,EAAUL,EAAO,EAAI,UAAY,OAAUA,EAAO,EAAI,OAAS,UACpF,cAAe,SACf,EAAG,OAAOH,EAAM,MAAM,EACtB,EAAG,OAAOQ,EAASR,EAAM,UAAYG,EAAOE,CAAU,CACxD,CAAC,EAGCG,EAAQ,CAGV,IAAMC,EAAM,MAAMV,EAAOK,EAASJ,CAAK,EACjCU,EAAOD,EAAM,iBAAiBA,CAAG,CAAC,CAAC,KAAO,GAEhDX,EAAK,MAAM,YAAY,OAAQV,EAAAA,cAAcsB,EAAM,kCAAkC,CAAC,CACxF,CAEAX,EAAM,YAAYD,CAAI,CACxB,CAIA,IAAMa,EAAStB,EAAAA,cAAce,EAAQ,WAAY,CAAE,eAAgB,CAAE,CAAC,EAElEO,EAAO,SAAW,GACpBpB,EAAA,QACE,CAAC,CAAE,MAAO,CAAE,QAAS,CAAE,KAAM,EAAG,GAAI,CAAE,CAAE,EAAG,GAAIQ,CAAM,CAAC,EACtD,CAAE,GAAGY,EAAQ,QAAS,CAAE,EACxB,IAAA,GACAP,EAAQ,cACV,CAEJ,CAOA,SAAS,MAAM,EAAoB,EAA+B,EAA6C,CAC7G,IAAMQ,EAAO,CAAC,GAAIb,EAAM,eAAe,iBAAiC,YAAY,GAAK,CAAC,CAAE,EAE5F,IAAK,IAAMU,KAAOG,EAAM,CACtB,IAAMC,EAAU,OAAOJ,EAAI,aAAaL,EAAQ,WAAa,IAAM,GAAG,CAAC,EACjEU,EAAO,OAAOL,EAAI,aAAaL,EAAQ,WAAa,SAAW,OAAO,CAAC,EAE7E,GAAIJ,EAAM,QAAUa,GAAWb,EAAM,QAAUa,EAAUC,EAAM,OAAOL,CACxE,CAEA,OAAOG,EAAK,IAAM,IACpB,C"}
@@ -1,36 +1,36 @@
1
- import { createSvgElement as e, setAttributes as t } from "../../svg/element.js";
2
- import { resolveMotion as n } from "../../animation/motion.js";
3
- import { animate as r } from "../../animation/transition.js";
1
+ import { resolveMotion as e } from "../../animation/motion.js";
2
+ import { animate as t } from "../../animation/transition.js";
3
+ import { createSvgElement as n, setAttributes as r } from "../../svg/element.js";
4
4
  import { valueColorFor as i } from "../heatmap/heatmap-color.js";
5
- //#region src/charts/bar/bar-value-labels.ts
5
+ // src/charts/bar/bar-value-labels.ts
6
6
  var a = 6.6, o = 6, s = 4, c = 16;
7
7
  function estWidth(e) {
8
8
  return e.length * a + s;
9
9
  }
10
10
  function renderBarValueLabels(a, s, l) {
11
11
  for (; a.firstChild;) a.removeChild(a.firstChild);
12
- for (let n of s) {
13
- let r = estWidth(n.text), s = l.horizontal ? n.dir * o : -n.dir * o, u = n.valueEdge + s, d = l.horizontal ? s > 0 ? u + r <= l.areaExtent : u - r >= 0 : s < 0 ? u >= 0 : u + 11 <= l.areaExtent, f = l.horizontal ? n.bandSize > c && (s > 0 ? n.valueEdge - o - r >= 0 : n.valueEdge + o + r <= l.areaExtent) : n.barLength > c && n.bandSize >= r;
12
+ for (let e of s) {
13
+ let t = estWidth(e.text), s = l.horizontal ? e.dir * o : -e.dir * o, u = e.valueEdge + s, d = l.horizontal ? s > 0 ? u + t <= l.areaExtent : u - t >= 0 : s < 0 ? u >= 0 : u + 11 <= l.areaExtent, f = l.horizontal ? e.bandSize > c && (s > 0 ? e.valueEdge - o - t >= 0 : e.valueEdge + o + t <= l.areaExtent) : e.barLength > c && e.bandSize >= t;
14
14
  if (!d && !f) continue;
15
- let p = !d, m = e("text", { class: p ? "prism-bar-value prism-bar-value--inside" : "prism-bar-value" });
16
- if (m.textContent = n.text, l.horizontal ? t(m, {
15
+ let p = !d, m = n("text", { class: p ? "prism-bar-value prism-bar-value--inside" : "prism-bar-value" });
16
+ if (m.textContent = e.text, l.horizontal ? r(m, {
17
17
  "dominant-baseline": "central",
18
18
  "text-anchor": p ? s > 0 ? "end" : "start" : s > 0 ? "start" : "end",
19
- x: String(p ? n.valueEdge - s : u),
20
- y: String(n.center)
21
- }) : t(m, {
19
+ x: String(p ? e.valueEdge - s : u),
20
+ y: String(e.center)
21
+ }) : r(m, {
22
22
  "dominant-baseline": p ? s < 0 ? "hanging" : "auto" : s < 0 ? "auto" : "hanging",
23
23
  "text-anchor": "middle",
24
- x: String(n.center),
25
- y: String(p ? n.valueEdge - s : u)
24
+ x: String(e.center),
25
+ y: String(p ? e.valueEdge - s : u)
26
26
  }), p) {
27
- let e = barAt(a, l, n), t = e ? getComputedStyle(e).fill : "";
28
- m.style.setProperty("fill", i(t, "var(--prism-text-color, #0f172a)"));
27
+ let t = barAt(a, l, e), n = t ? getComputedStyle(t).fill : "";
28
+ m.style.setProperty("fill", i(n, "var(--prism-text-color, #0f172a)"));
29
29
  }
30
30
  a.appendChild(m);
31
31
  }
32
- let u = n(l.transition, { defaultStagger: 0 });
33
- u.duration > 0 && r([{
32
+ let u = e(l.transition, { defaultStagger: 0 });
33
+ u.duration > 0 && t([{
34
34
  attrs: { opacity: {
35
35
  from: 0,
36
36
  to: 1
@@ -49,7 +49,6 @@ function barAt(e, t, n) {
49
49
  }
50
50
  return r[0] ?? null;
51
51
  }
52
- //#endregion
53
52
  export { renderBarValueLabels };
54
53
 
55
54
  //# sourceMappingURL=bar-value-labels.js.map
@@ -1,2 +1,2 @@
1
- require("../../_dev.cjs");const e=require("../../svg/element.cjs"),t=require("../../svg/text.cjs"),n=require("../../interaction/tooltip.cjs"),r=require("../../core/layout.cjs"),i=require("../../core/chart-scaffold.cjs"),a=require("./heatmap-color.cjs");var o={ArrowDown:1,ArrowLeft:-1,ArrowRight:1,ArrowUp:-1},s=[`#e0f2fe`,`#7dd3fc`,`#0ea5e9`,`#0369a1`,`#082f49`],c=`var(--prism-grid-color, #e2e8f0)`,l=11,u=8,d=.4,f=96,p=.71;function createHeatmapChart(m,h){let g=h.rows,_=h.columns,v=h.cells,y=h.domain,b=h.rowSummary,x=r.resolveMargin({bottom:8,left:8,right:8,top:8,...h.margin}),S=Math.max(0,h.gap??2),C=h.showValues??!0,w=h.valueFormat??String,T=a.createRamp(h.colorRange??s),E=h.emptyColor??c,D=buildBinBounds(h.bins),O=D.length,k=new Map,cellKey=(e,t)=>`${e}\u0000${t}`,A=null,j=-1,M=null,positionOf=()=>.5,N=e.createSvgElement(`g`),add=(t,n,r)=>{let i=e.createSvgElement(t,r);return n.appendChild(i),i};function currentDomain(){if(y)return y;let e=v.filter(e=>e.value!==null).map(e=>e.value);return e.length===0?[0,1]:[Math.min(...e),Math.max(...e)]}function computePositionOf(){if(O===0){let[e,t]=currentDomain();return n=>t===e?1:Math.min(1,Math.max(0,(n-e)/(t-e)))}return e=>{let t=O-1;for(let n=0;n<O;n+=1){let r=D[n][1];if(r!==null&&e<r){t=n;break}}return O===1?.5:t/(O-1)}}function render(n){let{legend:i,svg:o}=n;o.contains(N)||o.appendChild(N),e.removeChildren(N),M=null,j=-1,A=null,k.clear();for(let e of v)k.set(cellKey(e.row,e.column),e);if(positionOf=computePositionOf(),O>0&&i){let e=D.map((e,t)=>({color:T(O===1?.5:t/(O-1)),name:(h.binLabel??defaultBinLabel(w))(e[0],e[1])}));h.emptyLabel&&e.push({color:E,name:h.emptyLabel}),i.update(e)}if(g.length===0||_.length===0){i?.update([]);return}let s=r.chartArea(n.dimensions.width,n.dimensions.height,x),c=b&&b.values.length>0?b:null,m=Math.min(Math.max(...g.map(e=>t.estimateTextWidth(e,l)))+16,s.width*d),y=0;if(c){let e=c.values.filter(e=>e!==null),n=Math.max(t.estimateTextWidth(c.header??``,l),...e.map(e=>t.estimateTextWidth(e,l)));y=Math.min(n+16,s.width*d)}let P=Math.max(..._.map(e=>t.estimateTextWidth(e,l))),F=P>s.width/_.length-u,I=F?Math.min(P*p+u,f):19,L=x.left+m,R=x.top+I,z=Math.max(0,s.width-m-y)/_.length,B=Math.max(0,s.height-I)/g.length,V=[];for(let e=0;e<g.length;e+=1)for(let t=0;t<_.length;t+=1){let n=g[e],r=_[t],i=k.get(cellKey(n,r))?.value??null;V.push({column:r,columnWidth:z,fill:i===null?E:T(positionOf(i)),row:n,rowHeight:B,x:L+t*z,y:R+e*B})}A={cells:V,columnCount:_.length,colWidth:z,gridX:L,gridY:R,rowCount:g.length,rowHeight:B};for(let t=0;t<_.length;t+=1){let n=L+t*z+z/2,r=add(`text`,N,{"aria-hidden":`true`,class:`prism-heatmap-column-label`,x:String(n),y:String(x.top+I-4)});r.textContent=_[t],F?e.setAttributes(r,{"text-anchor":`start`,transform:`rotate(-45 ${n} ${x.top+I-4})`}):e.setAttributes(r,{"text-anchor":`middle`})}for(let e=0;e<g.length;e+=1){let t=add(`text`,N,{"aria-hidden":`true`,class:`prism-heatmap-row-label`,"dominant-baseline":`central`,"text-anchor":`end`,x:String(L-u),y:String(R+e*B+B/2)});t.textContent=g[e]}if(c){let e=x.left+s.width-y/2;if(c.header){let t=add(`text`,N,{"aria-hidden":`true`,class:`prism-heatmap-summary-header`,"text-anchor":`middle`,x:String(e),y:String(x.top+I-4)});t.textContent=c.header}for(let t=0;t<g.length;t+=1){let n=add(`text`,N,{"aria-hidden":`true`,class:`prism-heatmap-summary`,"dominant-baseline":`central`,"text-anchor":`middle`,x:String(e),y:String(R+t*B+B/2)});n.textContent=c.values[t]??`—`}}for(let e of V){let t=k.get(cellKey(e.row,e.column))?.value??null,n=add(`g`,N,{"aria-hidden":`true`,class:`prism-heatmap-cell`});if(add(`rect`,n,{class:`prism-heatmap-cell-rect`,height:String(Math.max(0,e.rowHeight-S)),rx:`2`,width:String(Math.max(0,e.columnWidth-S)),x:String(e.x+S/2),y:String(e.y+S/2)}).style.setProperty(`fill`,e.fill),C&&t!==null&&z>28&&B>16){let r=add(`text`,n,{class:`prism-heatmap-value`,"dominant-baseline":`central`,"text-anchor":`middle`,x:String(e.x+z/2),y:String(e.y+B/2)});r.textContent=w(t),r.style.setProperty(`fill`,a.valueColorFor(e.fill,`var(--prism-text-color, #0f172a)`))}}M=add(`rect`,N,{class:`prism-heatmap-highlight`,height:`0`,rx:`2`,visibility:`hidden`,width:`0`,x:`0`,y:`0`})}function eventFor(e,t){return{cell:k.get(cellKey(e.row,e.column)),column:e.column,originalEvent:t,row:e.row}}function indexAt(e,t){if(!A)return-1;let n=e.getBoundingClientRect(),r=t.clientX-n.left,i=t.clientY-n.top,a=Math.floor((r-A.gridX)/A.colWidth),o=Math.floor((i-A.gridY)/A.rowHeight);return a<0||o<0||a>=A.columnCount||o>=A.rowCount?-1:o*A.columnCount+a}function describe(e){let t=A.cells[e],n=k.get(cellKey(t.row,t.column))?.value??null;return`${t.row} \u00b7 ${t.column}: ${n===null?`—`:w(n)}`}function activate(t,r,i){if(!A||!M)return;let a=A.cells[r];if(!a)return;j=r;let o=k.get(cellKey(a.row,a.column))?.value??null;e.setAttributes(M,{height:String(Math.max(0,a.rowHeight-S)),visibility:`visible`,width:String(Math.max(0,a.columnWidth-S)),x:String(a.x+S/2),y:String(a.y+S/2)}),h.onCellHover?.(eventFor(a,i));let s=describe(r);if(!t.tooltip){t.announcer.announce(s);return}let c=t.svg.getBoundingClientRect(),l=t.container.getBoundingClientRect();t.tooltip.show(a.x+a.columnWidth/2+(c.left-l.left),a.y+(c.top-l.top),{key:cellKey(a.row,a.column),meta:{text:s},value:o??0},{color:a.fill,data:[],name:`${a.row} \u00b7 ${a.column}`},n.comparisonContent(t.svg.ownerDocument,`${a.row} \u00b7 ${a.column}`,[{color:a.fill,name:a.column,value:o===null?`—`:w(o)}],s))}function deactivate(e){j!==-1&&(j=-1,M?.setAttribute(`visibility`,`hidden`),h.onCellHover?.(null),e.tooltip?.hide(),e.announcer.clear())}return i.createRadialScaffold(m,{a11y:h.a11y,legend:O>0&&h.legend!==!1&&{},margin:h.margin,tooltip:h.tooltip},e=>{if(render(e),A)return{onClick(t){let n=indexAt(e.svg,t);n>=0&&h.onCellClick?.(eventFor(A.cells[n],t))},onKeyDown(t){let n=o[t.key];if(n!==void 0){if(t.preventDefault(),j<0){activate(e,0,t);return}let r=A.columnCount,i=Math.floor(j/r),a=j%r;if(t.key===`ArrowUp`||t.key===`ArrowDown`){let o=i+n;o>=0&&o<A.rowCount&&activate(e,o*r+a,t)}else{let o=a+n;o>=0&&o<A.columnCount&&activate(e,i*r+o,t)}}else(t.key===`Enter`||t.key===` `)&&j>=0?(t.preventDefault(),h.onCellClick?.(eventFor(A.cells[j],t))):t.key===`Escape`&&deactivate(e)},onMouseLeave:()=>deactivate(e),onMouseMove(t){let n=indexAt(e.svg,t);n<0?j>=0&&deactivate(e):n!==j&&activate(e,n,t)}}},e=>{e.rows&&(g=e.rows),e.columns&&(_=e.columns),v=e.cells,e.domain&&(y=e.domain),`rowSummary`in e&&(b=e.rowSummary)})}function buildBinBounds(e){if(!e||e.length===0)return[];let t=[...e].sort((e,t)=>e-t),n=[],r=null;for(let e of t)n.push([r,e]),r=e;return n.push([r,null]),n}function defaultBinLabel(e){return(t,n)=>t===null&&n!==null?`<${e(n)}`:t!==null&&n===null?`\u2265${e(t)}`:t!==null&&n!==null?`${e(t)}\u2013${e(n)}`:`—`}exports.createHeatmapChart=createHeatmapChart;
1
+ require("../../_dev.cjs");const e=require("../../core/chart-scaffold.cjs"),t=require("../../core/layout.cjs"),n=require("../../interaction/tooltip.cjs"),r=require("../../svg/element.cjs"),i=require("../../svg/text.cjs"),a=require("./heatmap-color.cjs");var o={ArrowDown:1,ArrowLeft:-1,ArrowRight:1,ArrowUp:-1},s=[`#e0f2fe`,`#7dd3fc`,`#0ea5e9`,`#0369a1`,`#082f49`],c=`var(--prism-grid-color, #e2e8f0)`,l=11,u=8,d=.4,f=96,p=.71;function createHeatmapChart(m,h){let g=h.rows,_=h.columns,v=h.cells,y=h.domain,b=h.rowSummary,x=t.resolveMargin({bottom:8,left:8,right:8,top:8,...h.margin}),S=Math.max(0,h.gap??2),C=h.showValues??!0,w=h.valueFormat??String,T=a.createRamp(h.colorRange??s),E=h.emptyColor??c,D=buildBinBounds(h.bins),O=D.length,k=new Map,cellKey=(e,t)=>`${e}\u0000${t}`,A=null,j=-1,M=null,positionOf=()=>.5,N=r.createSvgElement(`g`),add=(e,t,n)=>{let i=r.createSvgElement(e,n);return t.appendChild(i),i};function currentDomain(){if(y)return y;let e=v.filter(e=>e.value!==null).map(e=>e.value);return e.length===0?[0,1]:[Math.min(...e),Math.max(...e)]}function computePositionOf(){if(O===0){let[e,t]=currentDomain();return n=>t===e?1:Math.min(1,Math.max(0,(n-e)/(t-e)))}return e=>{let t=O-1;for(let n=0;n<O;n+=1){let r=D[n][1];if(r!==null&&e<r){t=n;break}}return O===1?.5:t/(O-1)}}function render(e){let{legend:n,svg:o}=e;o.contains(N)||o.appendChild(N),r.removeChildren(N),M=null,j=-1,A=null,k.clear();for(let e of v)k.set(cellKey(e.row,e.column),e);if(positionOf=computePositionOf(),O>0&&n){let e=D.map((e,t)=>({color:T(O===1?.5:t/(O-1)),name:(h.binLabel??defaultBinLabel(w))(e[0],e[1])}));h.emptyLabel&&e.push({color:E,name:h.emptyLabel}),n.update(e)}if(g.length===0||_.length===0){n?.update([]);return}let s=t.chartArea(e.dimensions.width,e.dimensions.height,x),c=b&&b.values.length>0?b:null,m=Math.min(Math.max(...g.map(e=>i.estimateTextWidth(e,l)))+16,s.width*d),y=0;if(c){let e=c.values.filter(e=>e!==null),t=Math.max(i.estimateTextWidth(c.header??``,l),...e.map(e=>i.estimateTextWidth(e,l)));y=Math.min(t+16,s.width*d)}let P=Math.max(..._.map(e=>i.estimateTextWidth(e,l))),F=P>s.width/_.length-u,I=F?Math.min(P*p+u,f):19,L=x.left+m,R=x.top+I,z=Math.max(0,s.width-m-y)/_.length,B=Math.max(0,s.height-I)/g.length,V=[];for(let e=0;e<g.length;e+=1)for(let t=0;t<_.length;t+=1){let n=g[e],r=_[t],i=k.get(cellKey(n,r))?.value??null;V.push({column:r,columnWidth:z,fill:i===null?E:T(positionOf(i)),row:n,rowHeight:B,x:L+t*z,y:R+e*B})}A={cells:V,columnCount:_.length,colWidth:z,gridX:L,gridY:R,rowCount:g.length,rowHeight:B};for(let e=0;e<_.length;e+=1){let t=L+e*z+z/2,n=add(`text`,N,{"aria-hidden":`true`,class:`prism-heatmap-column-label`,x:String(t),y:String(x.top+I-4)});n.textContent=_[e],F?r.setAttributes(n,{"text-anchor":`start`,transform:`rotate(-45 ${t} ${x.top+I-4})`}):r.setAttributes(n,{"text-anchor":`middle`})}for(let e=0;e<g.length;e+=1){let t=add(`text`,N,{"aria-hidden":`true`,class:`prism-heatmap-row-label`,"dominant-baseline":`central`,"text-anchor":`end`,x:String(L-u),y:String(R+e*B+B/2)});t.textContent=g[e]}if(c){let e=x.left+s.width-y/2;if(c.header){let t=add(`text`,N,{"aria-hidden":`true`,class:`prism-heatmap-summary-header`,"text-anchor":`middle`,x:String(e),y:String(x.top+I-4)});t.textContent=c.header}for(let t=0;t<g.length;t+=1){let n=add(`text`,N,{"aria-hidden":`true`,class:`prism-heatmap-summary`,"dominant-baseline":`central`,"text-anchor":`middle`,x:String(e),y:String(R+t*B+B/2)});n.textContent=c.values[t]??`—`}}for(let e of V){let t=k.get(cellKey(e.row,e.column))?.value??null,n=add(`g`,N,{"aria-hidden":`true`,class:`prism-heatmap-cell`});if(add(`rect`,n,{class:`prism-heatmap-cell-rect`,height:String(Math.max(0,e.rowHeight-S)),rx:`2`,width:String(Math.max(0,e.columnWidth-S)),x:String(e.x+S/2),y:String(e.y+S/2)}).style.setProperty(`fill`,e.fill),C&&t!==null&&z>28&&B>16){let r=add(`text`,n,{class:`prism-heatmap-value`,"dominant-baseline":`central`,"text-anchor":`middle`,x:String(e.x+z/2),y:String(e.y+B/2)});r.textContent=w(t),r.style.setProperty(`fill`,a.valueColorFor(e.fill,`var(--prism-text-color, #0f172a)`))}}M=add(`rect`,N,{class:`prism-heatmap-highlight`,height:`0`,rx:`2`,visibility:`hidden`,width:`0`,x:`0`,y:`0`})}function eventFor(e,t){return{cell:k.get(cellKey(e.row,e.column)),column:e.column,originalEvent:t,row:e.row}}function indexAt(e,t){if(!A)return-1;let n=e.getBoundingClientRect(),r=t.clientX-n.left,i=t.clientY-n.top,a=Math.floor((r-A.gridX)/A.colWidth),o=Math.floor((i-A.gridY)/A.rowHeight);return a<0||o<0||a>=A.columnCount||o>=A.rowCount?-1:o*A.columnCount+a}function describe(e){let t=A.cells[e],n=k.get(cellKey(t.row,t.column))?.value??null;return`${t.row} \u00b7 ${t.column}: ${n===null?`—`:w(n)}`}function activate(e,t,i){if(!A||!M)return;let a=A.cells[t];if(!a)return;j=t;let o=k.get(cellKey(a.row,a.column))?.value??null;r.setAttributes(M,{height:String(Math.max(0,a.rowHeight-S)),visibility:`visible`,width:String(Math.max(0,a.columnWidth-S)),x:String(a.x+S/2),y:String(a.y+S/2)}),h.onCellHover?.(eventFor(a,i));let s=describe(t);if(!e.tooltip){e.announcer.announce(s);return}let c=e.svg.getBoundingClientRect(),l=e.container.getBoundingClientRect();e.tooltip.show(a.x+a.columnWidth/2+(c.left-l.left),a.y+(c.top-l.top),{key:cellKey(a.row,a.column),meta:{text:s},value:o??0},{color:a.fill,data:[],name:`${a.row} \u00b7 ${a.column}`},n.comparisonContent(e.svg.ownerDocument,`${a.row} \u00b7 ${a.column}`,[{color:a.fill,name:a.column,value:o===null?`—`:w(o)}],s))}function deactivate(e){j!==-1&&(j=-1,M?.setAttribute(`visibility`,`hidden`),h.onCellHover?.(null),e.tooltip?.hide(),e.announcer.clear())}return e.createRadialScaffold(m,{a11y:h.a11y,legend:O>0&&h.legend!==!1&&{},margin:h.margin,tooltip:h.tooltip},e=>{if(render(e),A)return{onClick(t){let n=indexAt(e.svg,t);n>=0&&h.onCellClick?.(eventFor(A.cells[n],t))},onKeyDown(t){let n=o[t.key];if(n!==void 0){if(t.preventDefault(),j<0){activate(e,0,t);return}let r=A.columnCount,i=Math.floor(j/r),a=j%r;if(t.key===`ArrowUp`||t.key===`ArrowDown`){let o=i+n;o>=0&&o<A.rowCount&&activate(e,o*r+a,t)}else{let o=a+n;o>=0&&o<A.columnCount&&activate(e,i*r+o,t)}}else(t.key===`Enter`||t.key===` `)&&j>=0?(t.preventDefault(),h.onCellClick?.(eventFor(A.cells[j],t))):t.key===`Escape`&&deactivate(e)},onMouseLeave:()=>deactivate(e),onMouseMove(t){let n=indexAt(e.svg,t);n<0?j>=0&&deactivate(e):n!==j&&activate(e,n,t)}}},e=>{e.rows&&(g=e.rows),e.columns&&(_=e.columns),v=e.cells,e.domain&&(y=e.domain),`rowSummary`in e&&(b=e.rowSummary)})}function buildBinBounds(e){if(!e||e.length===0)return[];let t=[...e].sort((e,t)=>e-t),n=[],r=null;for(let e of t)n.push([r,e]),r=e;return n.push([r,null]),n}function defaultBinLabel(e){return(t,n)=>t===null&&n!==null?`<${e(n)}`:t!==null&&n===null?`\u2265${e(t)}`:t!==null&&n!==null?`${e(t)}\u2013${e(n)}`:`—`}exports.createHeatmapChart=createHeatmapChart;
2
2
  //# sourceMappingURL=heatmap-chart.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"heatmap-chart.cjs","names":["resolveMargin","createRamp","createSvgElement","chartArea","estimateTextWidth","valueColorFor","comparisonContent","createRadialScaffold","require_element","require_text","require_tooltip","require_layout","require_chart_scaffold","require_heatmap_color","ARROW_STEPS","DEFAULT_COLOR_RANGE","EMPTY_FALLBACK","LABEL_FONT_SIZE","LABEL_PAD","MAX_LABEL_GUTTER_RATIO","MAX_HEADER_BAND","ROTATED_LABEL_SLOPE","rows","config","columns","cells","domain","rowSummary","margin","gap","showValues","formatValue","ramp","emptyColor","binBounds","binCount","cellsByKey","row","column","layout","activeIndex","highlight","root","el","tag","attrs","parent","values","cell","d0","d1","value","bin","i","upper","ctx","svg","legend","items","bounds","area","summary","leftGutter","rightGutter","summaryTexts","widest","maxColumnLabelWidth","rotate","headerBand","gridX","gridY","colWidth","rowHeight","geometries","r","c","cx","label","summaryX","header","geometry","group","text","rect","x","event","y","col","index","originalEvent","svgRect","containerRect","container","step","colCount","nextRow","nextCol","next","bins","thresholds","a","b","lower","threshold","from","to"],"sources":["../../../src/charts/heatmap/heatmap-chart.ts"],"sourcesContent":["import { devOnly, warn } from '../../_dev';\nimport type { ChartEventHandlers, RadialScaffoldContext } from '../../core/chart-scaffold';\nimport { createRadialScaffold } from '../../core/chart-scaffold';\nimport { chartArea, resolveMargin } from '../../core/layout';\nimport { comparisonContent } from '../../interaction/tooltip';\nimport { createSvgElement, removeChildren, setAttributes } from '../../svg/element';\nimport { estimateTextWidth } from '../../svg/text';\nimport type { ChartHandle, HeatmapCell, HeatmapChartConfig, HeatmapData, HeatmapEvent } from '../../types';\nimport { createRamp, valueColorFor } from './heatmap-color';\n\nconst ARROW_STEPS: Record<string, number> = { ArrowDown: 1, ArrowLeft: -1, ArrowRight: 1, ArrowUp: -1 };\nconst DEFAULT_COLOR_RANGE = ['#e0f2fe', '#7dd3fc', '#0ea5e9', '#0369a1', '#082f49'];\nconst EMPTY_FALLBACK = 'var(--prism-grid-color, #e2e8f0)';\nconst LABEL_FONT_SIZE = 11;\nconst LABEL_PAD = 8;\nconst MAX_LABEL_GUTTER_RATIO = 0.4;\nconst MAX_HEADER_BAND = 96;\nconst ROTATED_LABEL_SLOPE = 0.71;\n\ninterface CellGeometry {\n column: string;\n columnWidth: number;\n fill: string;\n row: string;\n rowHeight: number;\n x: number;\n y: number;\n}\n\ninterface HeatmapLayout {\n cells: CellGeometry[];\n columnCount: number;\n colWidth: number;\n gridX: number;\n gridY: number;\n rowCount: number;\n rowHeight: number;\n}\n\nfunction warnOnInvalid(rows: readonly string[], columns: readonly string[], cells: readonly HeatmapCell[]): void {\n devOnly(() => {\n const rowSet = new Set(rows);\n const columnSet = new Set(columns);\n\n for (const cell of cells) {\n if (!rowSet.has(cell.row)) warn(`Heatmap cell for column \"${cell.column}\" has unknown row \"${cell.row}\".`);\n if (!columnSet.has(cell.column)) {\n warn(`Heatmap cell for row \"${cell.row}\" has unknown column \"${cell.column}\".`);\n }\n }\n });\n}\n\n/**\n * A matrix heatmap: rows down the left, columns across the top, one colored cell per\n * row/column pair. The ramp is continuous by default; `bins` turns it into discrete\n * steps with a swatch legend. Cells show their formatted value when they are large\n * enough, choosing dark or light text against the fill.\n *\n * Keyboard: arrows move the active cell, Enter/Space fires `onCellClick`, Escape\n * deactivates. The tooltip (or the live region without one) describes the active cell.\n */\nexport function createHeatmapChart(container: HTMLElement, config: HeatmapChartConfig): ChartHandle<HeatmapData> {\n let rows = config.rows;\n let columns = config.columns;\n let cells = config.cells;\n let domain = config.domain;\n let rowSummary = config.rowSummary;\n\n warnOnInvalid(rows, columns, cells);\n\n const margin = resolveMargin({ bottom: 8, left: 8, right: 8, top: 8, ...config.margin });\n const gap = Math.max(0, config.gap ?? 2);\n const showValues = config.showValues ?? true;\n const formatValue = config.valueFormat ?? String;\n const ramp = createRamp(config.colorRange ?? DEFAULT_COLOR_RANGE);\n const emptyColor = config.emptyColor ?? EMPTY_FALLBACK;\n const binBounds = buildBinBounds(config.bins);\n const binCount = binBounds.length;\n\n const cellsByKey = new Map<string, HeatmapCell>();\n const cellKey = (row: string, column: string): string => `${row}\\u0000${column}`;\n\n let layout: HeatmapLayout | null = null;\n let activeIndex = -1;\n let highlight: SVGRectElement | null = null;\n // Set by render(): the ramp position for a value depends on the current domain/bins.\n let positionOf: (value: number) => number = () => 0.5;\n\n const root = createSvgElement('g');\n\n const add = <K extends keyof SVGElementTagNameMap>(\n tag: K,\n parent: SVGElement,\n attrs?: Record<string, number | string | undefined>,\n ): SVGElementTagNameMap[K] => {\n const el = createSvgElement(tag, attrs);\n\n parent.appendChild(el);\n\n return el;\n };\n\n function currentDomain(): [number, number] {\n if (domain) return domain;\n\n const values = cells.filter((cell) => cell.value !== null).map((cell) => cell.value as number);\n\n if (values.length === 0) return [0, 1];\n\n return [Math.min(...values), Math.max(...values)];\n }\n\n function computePositionOf(): (value: number) => number {\n if (binCount === 0) {\n const [d0, d1] = currentDomain();\n\n return (value) => (d1 === d0 ? 1 : Math.min(1, Math.max(0, (value - d0) / (d1 - d0))));\n }\n\n return (value) => {\n let bin = binCount - 1;\n\n for (let i = 0; i < binCount; i += 1) {\n const upper = binBounds[i]![1];\n\n if (upper !== null && value < upper) {\n bin = i;\n break;\n }\n }\n\n return binCount === 1 ? 0.5 : bin / (binCount - 1);\n };\n }\n\n function render(ctx: RadialScaffoldContext): void {\n const { legend, svg } = ctx;\n\n if (!svg.contains(root)) svg.appendChild(root);\n removeChildren(root);\n highlight = null;\n activeIndex = -1;\n layout = null;\n\n cellsByKey.clear();\n for (const cell of cells) cellsByKey.set(cellKey(cell.row, cell.column), cell);\n\n positionOf = computePositionOf();\n\n if (binCount > 0 && legend) {\n const items = binBounds.map((bounds, i) => ({\n color: ramp(binCount === 1 ? 0.5 : i / (binCount - 1)),\n name: (config.binLabel ?? defaultBinLabel(formatValue))(bounds[0], bounds[1]),\n }));\n\n if (config.emptyLabel) items.push({ color: emptyColor, name: config.emptyLabel });\n legend.update(items);\n }\n\n if (rows.length === 0 || columns.length === 0) {\n legend?.update([]);\n\n return;\n }\n\n const area = chartArea(ctx.dimensions.width, ctx.dimensions.height, margin);\n const summary = rowSummary && rowSummary.values.length > 0 ? rowSummary : null;\n const leftGutter = Math.min(\n Math.max(...rows.map((row) => estimateTextWidth(row, LABEL_FONT_SIZE))) + LABEL_PAD * 2,\n area.width * MAX_LABEL_GUTTER_RATIO,\n );\n let rightGutter = 0;\n\n if (summary) {\n const summaryTexts = summary.values.filter((value): value is string => value !== null);\n const widest = Math.max(\n estimateTextWidth(summary.header ?? '', LABEL_FONT_SIZE),\n ...summaryTexts.map((value) => estimateTextWidth(value, LABEL_FONT_SIZE)),\n );\n\n rightGutter = Math.min(widest + LABEL_PAD * 2, area.width * MAX_LABEL_GUTTER_RATIO);\n }\n\n const maxColumnLabelWidth = Math.max(...columns.map((column) => estimateTextWidth(column, LABEL_FONT_SIZE)));\n const rotate = maxColumnLabelWidth > area.width / columns.length - LABEL_PAD;\n const headerBand = rotate\n ? Math.min(maxColumnLabelWidth * ROTATED_LABEL_SLOPE + LABEL_PAD, MAX_HEADER_BAND)\n : LABEL_FONT_SIZE + LABEL_PAD;\n\n const gridX = margin.left + leftGutter;\n const gridY = margin.top + headerBand;\n const colWidth = Math.max(0, area.width - leftGutter - rightGutter) / columns.length;\n const rowHeight = Math.max(0, area.height - headerBand) / rows.length;\n\n const geometries: CellGeometry[] = [];\n\n for (let r = 0; r < rows.length; r += 1) {\n for (let c = 0; c < columns.length; c += 1) {\n const row = rows[r]!;\n const column = columns[c]!;\n const value = cellsByKey.get(cellKey(row, column))?.value ?? null;\n\n geometries.push({\n column,\n columnWidth: colWidth,\n fill: value === null ? emptyColor : ramp(positionOf(value)),\n row,\n rowHeight,\n x: gridX + c * colWidth,\n y: gridY + r * rowHeight,\n });\n }\n }\n\n layout = {\n cells: geometries,\n columnCount: columns.length,\n colWidth,\n gridX,\n gridY,\n rowCount: rows.length,\n rowHeight,\n };\n\n // Column headers: horizontal when they fit their column, rotated -45° otherwise.\n for (let c = 0; c < columns.length; c += 1) {\n const cx = gridX + c * colWidth + colWidth / 2;\n const label = add('text', root, {\n 'aria-hidden': 'true',\n class: 'prism-heatmap-column-label',\n x: String(cx),\n y: String(margin.top + headerBand - 4),\n });\n\n label.textContent = columns[c];\n\n if (rotate) {\n setAttributes(label, { 'text-anchor': 'start', transform: `rotate(-45 ${cx} ${margin.top + headerBand - 4})` });\n } else {\n setAttributes(label, { 'text-anchor': 'middle' });\n }\n }\n\n // Row labels in the left gutter.\n for (let r = 0; r < rows.length; r += 1) {\n const label = add('text', root, {\n 'aria-hidden': 'true',\n class: 'prism-heatmap-row-label',\n 'dominant-baseline': 'central',\n 'text-anchor': 'end',\n x: String(gridX - LABEL_PAD),\n y: String(gridY + r * rowHeight + rowHeight / 2),\n });\n\n label.textContent = rows[r];\n }\n\n // Row summary in the right gutter: header in the column-header band, one value per row.\n if (summary) {\n const summaryX = margin.left + area.width - rightGutter / 2;\n\n if (summary.header) {\n const header = add('text', root, {\n 'aria-hidden': 'true',\n class: 'prism-heatmap-summary-header',\n 'text-anchor': 'middle',\n x: String(summaryX),\n y: String(margin.top + headerBand - 4),\n });\n\n header.textContent = summary.header;\n }\n\n for (let r = 0; r < rows.length; r += 1) {\n const label = add('text', root, {\n 'aria-hidden': 'true',\n class: 'prism-heatmap-summary',\n 'dominant-baseline': 'central',\n 'text-anchor': 'middle',\n x: String(summaryX),\n y: String(gridY + r * rowHeight + rowHeight / 2),\n });\n\n label.textContent = summary.values[r] ?? '\\u2014';\n }\n }\n\n // Cells, with their in-cell value when it fits.\n for (const geometry of geometries) {\n const value = cellsByKey.get(cellKey(geometry.row, geometry.column))?.value ?? null;\n const group = add('g', root, { 'aria-hidden': 'true', class: 'prism-heatmap-cell' });\n const rect = add('rect', group, {\n class: 'prism-heatmap-cell-rect',\n height: String(Math.max(0, geometry.rowHeight - gap)),\n rx: '2',\n width: String(Math.max(0, geometry.columnWidth - gap)),\n x: String(geometry.x + gap / 2),\n y: String(geometry.y + gap / 2),\n });\n\n rect.style.setProperty('fill', geometry.fill);\n\n if (showValues && value !== null && colWidth > 28 && rowHeight > 16) {\n const text = add('text', group, {\n class: 'prism-heatmap-value',\n 'dominant-baseline': 'central',\n 'text-anchor': 'middle',\n x: String(geometry.x + colWidth / 2),\n y: String(geometry.y + rowHeight / 2),\n });\n\n text.textContent = formatValue(value);\n text.style.setProperty('fill', valueColorFor(geometry.fill, 'var(--prism-text-color, #0f172a)'));\n }\n }\n\n highlight = add('rect', root, {\n class: 'prism-heatmap-highlight',\n height: '0',\n rx: '2',\n visibility: 'hidden',\n width: '0',\n x: '0',\n y: '0',\n });\n }\n\n function eventFor(geometry: CellGeometry, originalEvent: Event): HeatmapEvent {\n return {\n cell: cellsByKey.get(cellKey(geometry.row, geometry.column)),\n column: geometry.column,\n originalEvent,\n row: geometry.row,\n };\n }\n\n function indexAt(svg: SVGSVGElement, event: MouseEvent): number {\n if (!layout) return -1;\n\n const rect = svg.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const col = Math.floor((x - layout.gridX) / layout.colWidth);\n const row = Math.floor((y - layout.gridY) / layout.rowHeight);\n\n if (col < 0 || row < 0 || col >= layout.columnCount || row >= layout.rowCount) return -1;\n\n return row * layout.columnCount + col;\n }\n\n function describe(index: number): string {\n const geometry = layout!.cells[index]!;\n const value = cellsByKey.get(cellKey(geometry.row, geometry.column))?.value ?? null;\n\n return `${geometry.row} \\u00b7 ${geometry.column}: ${value === null ? '\\u2014' : formatValue(value)}`;\n }\n\n function activate(ctx: RadialScaffoldContext, index: number, originalEvent: Event): void {\n if (!layout || !highlight) return;\n\n const geometry = layout.cells[index];\n\n if (!geometry) return;\n\n activeIndex = index;\n const value = cellsByKey.get(cellKey(geometry.row, geometry.column))?.value ?? null;\n\n setAttributes(highlight, {\n height: String(Math.max(0, geometry.rowHeight - gap)),\n visibility: 'visible',\n width: String(Math.max(0, geometry.columnWidth - gap)),\n x: String(geometry.x + gap / 2),\n y: String(geometry.y + gap / 2),\n });\n\n config.onCellHover?.(eventFor(geometry, originalEvent));\n\n const text = describe(index);\n\n if (!ctx.tooltip) {\n ctx.announcer.announce(text);\n\n return;\n }\n\n const svgRect = ctx.svg.getBoundingClientRect();\n const containerRect = ctx.container.getBoundingClientRect();\n\n ctx.tooltip.show(\n geometry.x + geometry.columnWidth / 2 + (svgRect.left - containerRect.left),\n geometry.y + (svgRect.top - containerRect.top),\n { key: cellKey(geometry.row, geometry.column), meta: { text }, value: value ?? 0 },\n { color: geometry.fill, data: [], name: `${geometry.row} \\u00b7 ${geometry.column}` },\n comparisonContent(\n ctx.svg.ownerDocument,\n `${geometry.row} \\u00b7 ${geometry.column}`,\n [{ color: geometry.fill, name: geometry.column, value: value === null ? '\\u2014' : formatValue(value) }],\n text,\n ),\n );\n }\n\n function deactivate(ctx: RadialScaffoldContext): void {\n if (activeIndex === -1) return;\n\n activeIndex = -1;\n highlight?.setAttribute('visibility', 'hidden');\n config.onCellHover?.(null);\n ctx.tooltip?.hide();\n ctx.announcer.clear();\n }\n\n return createRadialScaffold<HeatmapData>(\n container,\n {\n a11y: config.a11y,\n legend: binCount > 0 && config.legend !== false ? {} : false,\n margin: config.margin,\n tooltip: config.tooltip,\n },\n (ctx): ChartEventHandlers | undefined => {\n render(ctx);\n\n if (!layout) return undefined;\n\n return {\n onClick(event) {\n const index = indexAt(ctx.svg, event);\n\n if (index >= 0) config.onCellClick?.(eventFor(layout!.cells[index]!, event));\n },\n onKeyDown(event) {\n const step = ARROW_STEPS[event.key];\n\n if (step !== undefined) {\n event.preventDefault();\n\n if (activeIndex < 0) {\n activate(ctx, 0, event);\n\n return;\n }\n\n const colCount = layout!.columnCount;\n const row = Math.floor(activeIndex / colCount);\n const col = activeIndex % colCount;\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {\n const nextRow = row + step;\n\n if (nextRow >= 0 && nextRow < layout!.rowCount) {\n activate(ctx, nextRow * colCount + col, event);\n }\n } else {\n const nextCol = col + step;\n\n if (nextCol >= 0 && nextCol < layout!.columnCount) {\n activate(ctx, row * colCount + nextCol, event);\n }\n }\n } else if ((event.key === 'Enter' || event.key === ' ') && activeIndex >= 0) {\n event.preventDefault();\n config.onCellClick?.(eventFor(layout!.cells[activeIndex]!, event));\n } else if (event.key === 'Escape') {\n deactivate(ctx);\n }\n },\n onMouseLeave: () => deactivate(ctx),\n onMouseMove(event) {\n const index = indexAt(ctx.svg, event);\n\n if (index < 0) {\n if (activeIndex >= 0) deactivate(ctx);\n } else if (index !== activeIndex) {\n activate(ctx, index, event);\n }\n },\n };\n },\n (next) => {\n if (next.rows) rows = next.rows;\n if (next.columns) columns = next.columns;\n cells = next.cells;\n if (next.domain) domain = next.domain;\n // Key present (even as `undefined`) replaces the summary; absent keeps it (partial updates).\n if ('rowSummary' in next) rowSummary = next.rowSummary;\n warnOnInvalid(rows, columns, cells);\n },\n );\n}\n\n// ─── Bins ─────────────────────────────────────────────────────────────────────\n\nfunction buildBinBounds(bins: number[] | undefined): Array<[number | null, number | null]> {\n if (!bins || bins.length === 0) return [];\n\n const thresholds = [...bins].sort((a, b) => a - b);\n const bounds: Array<[number | null, number | null]> = [];\n let lower: number | null = null;\n\n for (const threshold of thresholds) {\n bounds.push([lower, threshold]);\n lower = threshold;\n }\n\n bounds.push([lower, null]);\n\n return bounds;\n}\n\nfunction defaultBinLabel(formatValue: (value: number) => string): (from: number | null, to: number | null) => string {\n return (from, to) => {\n if (from === null && to !== null) return `<${formatValue(to)}`;\n if (from !== null && to === null) return `\\u2265${formatValue(from)}`;\n if (from !== null && to !== null) return `${formatValue(from)}\\u2013${formatValue(to)}`;\n\n return '\\u2014';\n };\n}\n"],"mappings":"A,6PAUA,IAAMc,EAAsC,CAAE,UAAW,EAAG,UAAW,GAAI,WAAY,EAAG,QAAS,EAAG,EAChGC,EAAsB,CAAC,UAAW,UAAW,UAAW,UAAW,SAAS,EAC5EC,EAAiB,mCACjBC,EAAkB,GAClBC,EAAY,EACZC,EAAyB,GACzBC,EAAkB,GAClBC,EAAsB,IA6C5B,SAAgB,mBAAmB,EAAwB,EAAsD,CAC/G,IAAIC,EAAOC,EAAO,KACdC,EAAUD,EAAO,QACjBE,EAAQF,EAAO,MACfG,EAASH,EAAO,OAChBI,EAAaJ,EAAO,WAIlBK,EAAS5B,EAAAA,cAAc,CAAE,OAAQ,EAAG,KAAM,EAAG,MAAO,EAAG,IAAK,EAAG,GAAGuB,EAAO,MAAO,CAAC,EACjFM,EAAM,KAAK,IAAI,EAAGN,EAAO,KAAO,CAAC,EACjCO,EAAaP,EAAO,YAAc,GAClCQ,EAAcR,EAAO,aAAe,OACpCS,EAAO/B,EAAAA,WAAWsB,EAAO,YAAcR,CAAmB,EAC1DkB,EAAaV,EAAO,YAAcP,EAClCkB,EAAY,eAAeX,EAAO,IAAI,EACtCY,EAAWD,EAAU,OAErBE,EAAa,IAAI,IACjB,SAAW,EAAa,IAA2B,GAAGC,EAAI,QAAQC,IAEpEC,EAA+B,KAC/BC,EAAc,GACdC,EAAmC,KAEnC,eAA8C,GAE5CC,EAAOxC,EAAAA,iBAAiB,GAAG,EAE3B,KACJ,EACA,EACA,IAC4B,CAC5B,IAAMyC,EAAKzC,EAAAA,iBAAiB0C,EAAKC,CAAK,EAItC,OAFAC,EAAO,YAAYH,CAAE,EAEdA,CACT,EAEA,SAAS,eAAkC,CACzC,GAAIjB,EAAQ,OAAOA,EAEnB,IAAMqB,EAAStB,EAAM,OAAQ,GAASuB,EAAK,QAAU,IAAI,CAAC,CAAC,IAAK,GAASA,EAAK,KAAe,EAI7F,OAFID,EAAO,SAAW,EAAU,CAAC,EAAG,CAAC,EAE9B,CAAC,KAAK,IAAI,GAAGA,CAAM,EAAG,KAAK,IAAI,GAAGA,CAAM,CAAC,CAClD,CAEA,SAAS,mBAA+C,CACtD,GAAIZ,IAAa,EAAG,CAClB,GAAM,CAACc,EAAIC,GAAM,cAAc,EAE/B,MAAQ,IAAWA,IAAOD,EAAK,EAAI,KAAK,IAAI,EAAG,KAAK,IAAI,GAAIE,EAAQF,IAAOC,EAAKD,EAAG,CAAC,CACtF,CAEA,MAAQ,IAAU,CAChB,IAAIG,EAAMjB,EAAW,EAErB,IAAK,IAAIkB,EAAI,EAAGA,EAAIlB,EAAU,GAAK,EAAG,CACpC,IAAMmB,EAAQpB,EAAUmB,EAAE,CAAE,GAE5B,GAAIC,IAAU,MAAQH,EAAQG,EAAO,CACnC,EAAMD,EACN,KACF,CACF,CAEA,OAAOlB,IAAa,EAAI,GAAMiB,GAAOjB,EAAW,EAClD,CACF,CAEA,SAAS,OAAO,EAAkC,CAChD,GAAM,CAAE,SAAQ,OAAQoB,EAEnBC,EAAI,SAASd,CAAI,GAAGc,EAAI,YAAYd,CAAI,EAC7ClC,EAAA,eAAekC,CAAI,EACnB,EAAY,KACZ,EAAc,GACd,EAAS,KAETN,EAAW,MAAM,EACjB,IAAK,IAAMY,KAAQvB,EAAOW,EAAW,IAAI,QAAQY,EAAK,IAAKA,EAAK,MAAM,EAAGA,CAAI,EAI7E,GAFA,WAAa,kBAAkB,EAE3Bb,EAAW,GAAKsB,EAAQ,CAC1B,IAAMC,EAAQxB,EAAU,KAAK,EAAQ,KAAO,CAC1C,MAAOF,EAAKG,IAAa,EAAI,GAAMkB,GAAKlB,EAAW,EAAE,EACrD,MAAOZ,EAAO,UAAY,gBAAgBQ,CAAW,EAAA,CAAG4B,EAAO,GAAIA,EAAO,EAAE,CAC9E,EAAE,EAEEpC,EAAO,YAAYmC,EAAM,KAAK,CAAE,MAAOzB,EAAY,KAAMV,EAAO,UAAW,CAAC,EAChFkC,EAAO,OAAOC,CAAK,CACrB,CAEA,GAAIpC,EAAK,SAAW,GAAKE,EAAQ,SAAW,EAAG,CAC7CiC,GAAQ,OAAO,CAAC,CAAC,EAEjB,MACF,CAEA,IAAMG,EAAOzD,EAAAA,UAAUoD,EAAI,WAAW,MAAOA,EAAI,WAAW,OAAQ3B,CAAM,EACpEiC,EAAUlC,GAAcA,EAAW,OAAO,OAAS,EAAIA,EAAa,KACpEmC,EAAa,KAAK,IACtB,KAAK,IAAI,GAAGxC,EAAK,IAAK,GAAQlB,EAAAA,kBAAkBiC,EAAKpB,CAAe,CAAC,CAAC,EAAI,GAC1E2C,EAAK,MAAQzC,CACf,EACI4C,EAAc,EAElB,GAAIF,EAAS,CACX,IAAMG,EAAeH,EAAQ,OAAO,OAAQ,GAA2BV,IAAU,IAAI,EAC/Ec,EAAS,KAAK,IAClB7D,EAAAA,kBAAkByD,EAAQ,QAAU,GAAI5C,CAAe,EACvD,GAAG+C,EAAa,IAAK,GAAU5D,EAAAA,kBAAkB+C,EAAOlC,CAAe,CAAC,CAC1E,EAEA,EAAc,KAAK,IAAIgD,EAAS,GAAeL,EAAK,MAAQzC,CAAsB,CACpF,CAEA,IAAM+C,EAAsB,KAAK,IAAI,GAAG1C,EAAQ,IAAK,GAAWpB,EAAAA,kBAAkBkC,EAAQrB,CAAe,CAAC,CAAC,EACrGkD,EAASD,EAAsBN,EAAK,MAAQpC,EAAQ,OAASN,EAC7DkD,EAAaD,EACf,KAAK,IAAID,EAAsB7C,EAAsBH,EAAWE,CAAe,EAC/E,GAEEiD,EAAQzC,EAAO,KAAOkC,EACtBQ,EAAQ1C,EAAO,IAAMwC,EACrBG,EAAW,KAAK,IAAI,EAAGX,EAAK,MAAQE,EAAaC,CAAW,EAAIvC,EAAQ,OACxEgD,EAAY,KAAK,IAAI,EAAGZ,EAAK,OAASQ,CAAU,EAAI9C,EAAK,OAEzDmD,EAA6B,CAAC,EAEpC,IAAK,IAAIC,EAAI,EAAGA,EAAIpD,EAAK,OAAQ,GAAK,EACpC,IAAK,IAAIqD,EAAI,EAAGA,EAAInD,EAAQ,OAAQ,GAAK,EAAG,CAC1C,IAAMa,EAAMf,EAAKoD,GACXpC,EAASd,EAAQmD,GACjBxB,EAAQf,EAAW,IAAI,QAAQC,EAAKC,CAAM,CAAC,CAAC,EAAE,OAAS,KAE7DmC,EAAW,KAAK,CACd,SACA,YAAaF,EACb,KAAMpB,IAAU,KAAOlB,EAAaD,EAAK,WAAWmB,CAAK,CAAC,EAC1D,MACA,YACA,EAAGkB,EAAQM,EAAIJ,EACf,EAAGD,EAAQI,EAAIF,CACjB,CAAC,CACH,CAGF,EAAS,CACP,MAAOC,EACP,YAAajD,EAAQ,OACrB,WACA,QACA,QACA,SAAUF,EAAK,OACf,WACF,EAGA,IAAK,IAAIqD,EAAI,EAAGA,EAAInD,EAAQ,OAAQ,GAAK,EAAG,CAC1C,IAAMoD,EAAKP,EAAQM,EAAIJ,EAAWA,EAAW,EACvCM,EAAQ,IAAI,OAAQnC,EAAM,CAC9B,cAAe,OACf,MAAO,6BACP,EAAG,OAAOkC,CAAE,EACZ,EAAG,OAAOhD,EAAO,IAAMwC,EAAa,CAAC,CACvC,CAAC,EAED,EAAM,YAAc5C,EAAQmD,GAExBR,EACF3D,EAAA,cAAcqE,EAAO,CAAE,cAAe,QAAS,UAAW,cAAcD,EAAG,GAAGhD,EAAO,IAAMwC,EAAa,EAAE,EAAG,CAAC,EAE9G5D,EAAA,cAAcqE,EAAO,CAAE,cAAe,QAAS,CAAC,CAEpD,CAGA,IAAK,IAAIH,EAAI,EAAGA,EAAIpD,EAAK,OAAQ,GAAK,EAAG,CACvC,IAAMuD,EAAQ,IAAI,OAAQnC,EAAM,CAC9B,cAAe,OACf,MAAO,0BACP,oBAAqB,UACrB,cAAe,MACf,EAAG,OAAO2B,EAAQnD,CAAS,EAC3B,EAAG,OAAOoD,EAAQI,EAAIF,EAAYA,EAAY,CAAC,CACjD,CAAC,EAED,EAAM,YAAclD,EAAKoD,EAC3B,CAGA,GAAIb,EAAS,CACX,IAAMiB,EAAWlD,EAAO,KAAOgC,EAAK,MAAQG,EAAc,EAE1D,GAAIF,EAAQ,OAAQ,CAClB,IAAMkB,EAAS,IAAI,OAAQrC,EAAM,CAC/B,cAAe,OACf,MAAO,+BACP,cAAe,SACf,EAAG,OAAOoC,CAAQ,EAClB,EAAG,OAAOlD,EAAO,IAAMwC,EAAa,CAAC,CACvC,CAAC,EAED,EAAO,YAAcP,EAAQ,MAC/B,CAEA,IAAK,IAAIa,EAAI,EAAGA,EAAIpD,EAAK,OAAQ,GAAK,EAAG,CACvC,IAAMuD,EAAQ,IAAI,OAAQnC,EAAM,CAC9B,cAAe,OACf,MAAO,wBACP,oBAAqB,UACrB,cAAe,SACf,EAAG,OAAOoC,CAAQ,EAClB,EAAG,OAAOR,EAAQI,EAAIF,EAAYA,EAAY,CAAC,CACjD,CAAC,EAED,EAAM,YAAcX,EAAQ,OAAOa,IAAM,GAC3C,CACF,CAGA,IAAK,IAAMM,KAAYP,EAAY,CACjC,IAAMtB,EAAQf,EAAW,IAAI,QAAQ4C,EAAS,IAAKA,EAAS,MAAM,CAAC,CAAC,EAAE,OAAS,KACzEC,EAAQ,IAAI,IAAKvC,EAAM,CAAE,cAAe,OAAQ,MAAO,oBAAqB,CAAC,EAYnF,GAFA,IATiB,OAAQuC,EAAO,CAC9B,MAAO,0BACP,OAAQ,OAAO,KAAK,IAAI,EAAGD,EAAS,UAAYnD,CAAG,CAAC,EACpD,GAAI,IACJ,MAAO,OAAO,KAAK,IAAI,EAAGmD,EAAS,YAAcnD,CAAG,CAAC,EACrD,EAAG,OAAOmD,EAAS,EAAInD,EAAM,CAAC,EAC9B,EAAG,OAAOmD,EAAS,EAAInD,EAAM,CAAC,CAChC,CAEA,CAAA,CAAK,MAAM,YAAY,OAAQmD,EAAS,IAAI,EAExClD,GAAcqB,IAAU,MAAQoB,EAAW,IAAMC,EAAY,GAAI,CACnE,IAAMU,EAAO,IAAI,OAAQD,EAAO,CAC9B,MAAO,sBACP,oBAAqB,UACrB,cAAe,SACf,EAAG,OAAOD,EAAS,EAAIT,EAAW,CAAC,EACnC,EAAG,OAAOS,EAAS,EAAIR,EAAY,CAAC,CACtC,CAAC,EAED,EAAK,YAAczC,EAAYoB,CAAK,EACpC+B,EAAK,MAAM,YAAY,OAAQ7E,EAAAA,cAAc2E,EAAS,KAAM,kCAAkC,CAAC,CACjG,CACF,CAEA,EAAY,IAAI,OAAQtC,EAAM,CAC5B,MAAO,0BACP,OAAQ,IACR,GAAI,IACJ,WAAY,SACZ,MAAO,IACP,EAAG,IACH,EAAG,GACL,CAAC,CACH,CAEA,SAAS,SAAS,EAAwB,EAAoC,CAC5E,MAAO,CACL,KAAMN,EAAW,IAAI,QAAQ4C,EAAS,IAAKA,EAAS,MAAM,CAAC,EAC3D,OAAQA,EAAS,OACjB,gBACA,IAAKA,EAAS,GAChB,CACF,CAEA,SAAS,QAAQ,EAAoB,EAA2B,CAC9D,GAAI,CAACzC,EAAQ,MAAO,GAEpB,IAAM4C,EAAO3B,EAAI,sBAAsB,EACjC4B,EAAIC,EAAM,QAAUF,EAAK,KACzBG,EAAID,EAAM,QAAUF,EAAK,IACzBI,EAAM,KAAK,OAAOH,EAAI7C,EAAO,OAASA,EAAO,QAAQ,EACrDF,EAAM,KAAK,OAAOiD,EAAI/C,EAAO,OAASA,EAAO,SAAS,EAI5D,OAFIgD,EAAM,GAAKlD,EAAM,GAAKkD,GAAOhD,EAAO,aAAeF,GAAOE,EAAO,SAAiB,GAE/EF,EAAME,EAAO,YAAcgD,CACpC,CAEA,SAAS,SAAS,EAAuB,CACvC,IAAMP,EAAWzC,EAAQ,MAAMiD,GACzBrC,EAAQf,EAAW,IAAI,QAAQ4C,EAAS,IAAKA,EAAS,MAAM,CAAC,CAAC,EAAE,OAAS,KAE/E,MAAO,GAAGA,EAAS,IAAI,UAAUA,EAAS,OAAO,IAAI7B,IAAU,KAAO,IAAWpB,EAAYoB,CAAK,GACpG,CAEA,SAAS,SAAS,EAA4B,EAAe,EAA4B,CACvF,GAAI,CAACZ,GAAU,CAACE,EAAW,OAE3B,IAAMuC,EAAWzC,EAAO,MAAMiD,GAE9B,GAAI,CAACR,EAAU,OAEf,EAAcQ,EACd,IAAMrC,EAAQf,EAAW,IAAI,QAAQ4C,EAAS,IAAKA,EAAS,MAAM,CAAC,CAAC,EAAE,OAAS,KAE/ExE,EAAA,cAAciC,EAAW,CACvB,OAAQ,OAAO,KAAK,IAAI,EAAGuC,EAAS,UAAYnD,CAAG,CAAC,EACpD,WAAY,UACZ,MAAO,OAAO,KAAK,IAAI,EAAGmD,EAAS,YAAcnD,CAAG,CAAC,EACrD,EAAG,OAAOmD,EAAS,EAAInD,EAAM,CAAC,EAC9B,EAAG,OAAOmD,EAAS,EAAInD,EAAM,CAAC,CAChC,CAAC,EAEDN,EAAO,cAAc,SAASyD,EAAUS,CAAa,CAAC,EAEtD,IAAMP,EAAO,SAASM,CAAK,EAE3B,GAAI,CAACjC,EAAI,QAAS,CAChBA,EAAI,UAAU,SAAS2B,CAAI,EAE3B,MACF,CAEA,IAAMQ,EAAUnC,EAAI,IAAI,sBAAsB,EACxCoC,EAAgBpC,EAAI,UAAU,sBAAsB,EAE1DA,EAAI,QAAQ,KACVyB,EAAS,EAAIA,EAAS,YAAc,GAAKU,EAAQ,KAAOC,EAAc,MACtEX,EAAS,GAAKU,EAAQ,IAAMC,EAAc,KAC1C,CAAE,IAAK,QAAQX,EAAS,IAAKA,EAAS,MAAM,EAAG,KAAM,CAAE,MAAK,EAAG,MAAO7B,GAAS,CAAE,EACjF,CAAE,MAAO6B,EAAS,KAAM,KAAM,CAAC,EAAG,KAAM,GAAGA,EAAS,IAAI,UAAUA,EAAS,QAAS,EACpF1E,EAAAA,kBACEiD,EAAI,IAAI,cACR,GAAGyB,EAAS,IAAI,UAAUA,EAAS,SACnC,CAAC,CAAE,MAAOA,EAAS,KAAM,KAAMA,EAAS,OAAQ,MAAO7B,IAAU,KAAO,IAAWpB,EAAYoB,CAAK,CAAE,CAAC,EACvG+B,CACF,CACF,CACF,CAEA,SAAS,WAAW,EAAkC,CAChD1C,IAAgB,KAEpB,EAAc,GACdC,GAAW,aAAa,aAAc,QAAQ,EAC9ClB,EAAO,cAAc,IAAI,EACzBgC,EAAI,SAAS,KAAK,EAClBA,EAAI,UAAU,MAAM,EACtB,CAEA,OAAOhD,EAAAA,qBACLqF,EACA,CACE,KAAMrE,EAAO,KACb,OAAQY,EAAW,GAAKZ,EAAO,SAAW,IAAQ,CAAC,EACnD,OAAQA,EAAO,OACf,QAASA,EAAO,OAClB,EACC,GAAwC,CAGvC,GAFA,OAAOgC,CAAG,EAELhB,EAEL,MAAO,CACL,QAAQ,EAAO,CACb,IAAMiD,EAAQ,QAAQjC,EAAI,IAAK8B,CAAK,EAEhCG,GAAS,GAAGjE,EAAO,cAAc,SAASgB,EAAQ,MAAMiD,GAASH,CAAK,CAAC,CAC7E,EACA,UAAU,EAAO,CACf,IAAMQ,EAAO/E,EAAYuE,EAAM,KAE/B,GAAIQ,IAAS,IAAA,GAAW,CAGtB,GAFAR,EAAM,eAAe,EAEjB7C,EAAc,EAAG,CACnB,SAASe,EAAK,EAAG8B,CAAK,EAEtB,MACF,CAEA,IAAMS,EAAWvD,EAAQ,YACnBF,EAAM,KAAK,MAAMG,EAAcsD,CAAQ,EACvCP,EAAM/C,EAAcsD,EAE1B,GAAIT,EAAM,MAAQ,WAAaA,EAAM,MAAQ,YAAa,CACxD,IAAMU,EAAU1D,EAAMwD,EAElBE,GAAW,GAAKA,EAAUxD,EAAQ,UACpC,SAASgB,EAAKwC,EAAUD,EAAWP,EAAKF,CAAK,CAEjD,KAAO,CACL,IAAMW,EAAUT,EAAMM,EAElBG,GAAW,GAAKA,EAAUzD,EAAQ,aACpC,SAASgB,EAAKlB,EAAMyD,EAAWE,EAASX,CAAK,CAEjD,CACF,MAAYA,EAAM,MAAQ,SAAWA,EAAM,MAAQ,MAAQ7C,GAAe,GACxE6C,EAAM,eAAe,EACrB9D,EAAO,cAAc,SAASgB,EAAQ,MAAMC,GAAe6C,CAAK,CAAC,GACxDA,EAAM,MAAQ,UACvB,WAAW9B,CAAG,CAElB,EACA,iBAAoB,WAAWA,CAAG,EAClC,YAAY,EAAO,CACjB,IAAMiC,EAAQ,QAAQjC,EAAI,IAAK8B,CAAK,EAEhCG,EAAQ,EACNhD,GAAe,GAAG,WAAWe,CAAG,EAC3BiC,IAAUhD,GACnB,SAASe,EAAKiC,EAAOH,CAAK,CAE9B,CACF,CACF,EACC,GAAS,CACJY,EAAK,OAAM,EAAOA,EAAK,MACvBA,EAAK,UAAS,EAAUA,EAAK,SACjC,EAAQA,EAAK,MACTA,EAAK,SAAQ,EAASA,EAAK,QAE3B,eAAgBA,IAAM,EAAaA,EAAK,WAE9C,CACF,CACF,CAIA,SAAS,eAAe,EAAmE,CACzF,GAAI,CAACC,GAAQA,EAAK,SAAW,EAAG,MAAO,CAAC,EAExC,IAAMC,EAAa,CAAC,GAAGD,CAAI,CAAC,CAAC,MAAM,EAAG,IAAME,EAAIC,CAAC,EAC3C1C,EAAgD,CAAC,EACnD2C,EAAuB,KAE3B,IAAK,IAAMC,KAAaJ,EACtBxC,EAAO,KAAK,CAAC2C,EAAOC,CAAS,CAAC,EAC9B,EAAQA,EAKV,OAFA5C,EAAO,KAAK,CAAC2C,EAAO,IAAI,CAAC,EAElB3C,CACT,CAEA,SAAS,gBAAgB,EAA4F,CACnH,OAAQ,EAAM,IACR6C,IAAS,MAAQC,IAAO,KAAa,IAAI1E,EAAY0E,CAAE,IACvDD,IAAS,MAAQC,IAAO,KAAa,SAAS1E,EAAYyE,CAAI,IAC9DA,IAAS,MAAQC,IAAO,KAAa,GAAG1E,EAAYyE,CAAI,EAAE,QAAQzE,EAAY0E,CAAE,IAE7E,GAEX,C"}
1
+ {"version":3,"file":"heatmap-chart.cjs","names":["resolveMargin","createRamp","createSvgElement","chartArea","estimateTextWidth","valueColorFor","comparisonContent","createRadialScaffold","require_chart_scaffold","require_layout","require_tooltip","require_element","require_text","require_heatmap_color","ARROW_STEPS","DEFAULT_COLOR_RANGE","EMPTY_FALLBACK","LABEL_FONT_SIZE","LABEL_PAD","MAX_LABEL_GUTTER_RATIO","MAX_HEADER_BAND","ROTATED_LABEL_SLOPE","rows","config","columns","cells","domain","rowSummary","margin","gap","showValues","formatValue","ramp","emptyColor","binBounds","binCount","cellsByKey","row","column","layout","activeIndex","highlight","root","el","tag","attrs","parent","values","cell","d0","d1","value","bin","i","upper","ctx","svg","legend","items","bounds","area","summary","leftGutter","rightGutter","summaryTexts","widest","maxColumnLabelWidth","rotate","headerBand","gridX","gridY","colWidth","rowHeight","geometries","r","c","cx","label","summaryX","header","geometry","group","text","rect","x","event","y","col","index","originalEvent","svgRect","containerRect","container","step","colCount","nextRow","nextCol","next","bins","thresholds","a","b","lower","threshold","from","to"],"sources":["../../../src/charts/heatmap/heatmap-chart.ts"],"sourcesContent":["import { devOnly, warn } from '../../_dev';\nimport type { ChartEventHandlers, RadialScaffoldContext } from '../../core/chart-scaffold';\nimport { createRadialScaffold } from '../../core/chart-scaffold';\nimport { chartArea, resolveMargin } from '../../core/layout';\nimport { comparisonContent } from '../../interaction/tooltip';\nimport { createSvgElement, removeChildren, setAttributes } from '../../svg/element';\nimport { estimateTextWidth } from '../../svg/text';\nimport type { ChartHandle, HeatmapCell, HeatmapChartConfig, HeatmapData, HeatmapEvent } from '../../types';\nimport { createRamp, valueColorFor } from './heatmap-color';\n\nconst ARROW_STEPS: Record<string, number> = { ArrowDown: 1, ArrowLeft: -1, ArrowRight: 1, ArrowUp: -1 };\nconst DEFAULT_COLOR_RANGE = ['#e0f2fe', '#7dd3fc', '#0ea5e9', '#0369a1', '#082f49'];\nconst EMPTY_FALLBACK = 'var(--prism-grid-color, #e2e8f0)';\nconst LABEL_FONT_SIZE = 11;\nconst LABEL_PAD = 8;\nconst MAX_LABEL_GUTTER_RATIO = 0.4;\nconst MAX_HEADER_BAND = 96;\nconst ROTATED_LABEL_SLOPE = 0.71;\n\ninterface CellGeometry {\n column: string;\n columnWidth: number;\n fill: string;\n row: string;\n rowHeight: number;\n x: number;\n y: number;\n}\n\ninterface HeatmapLayout {\n cells: CellGeometry[];\n columnCount: number;\n colWidth: number;\n gridX: number;\n gridY: number;\n rowCount: number;\n rowHeight: number;\n}\n\nfunction warnOnInvalid(rows: readonly string[], columns: readonly string[], cells: readonly HeatmapCell[]): void {\n devOnly(() => {\n const rowSet = new Set(rows);\n const columnSet = new Set(columns);\n\n for (const cell of cells) {\n if (!rowSet.has(cell.row)) warn(`Heatmap cell for column \"${cell.column}\" has unknown row \"${cell.row}\".`);\n if (!columnSet.has(cell.column)) {\n warn(`Heatmap cell for row \"${cell.row}\" has unknown column \"${cell.column}\".`);\n }\n }\n });\n}\n\n/**\n * A matrix heatmap: rows down the left, columns across the top, one colored cell per\n * row/column pair. The ramp is continuous by default; `bins` turns it into discrete\n * steps with a swatch legend. Cells show their formatted value when they are large\n * enough, choosing dark or light text against the fill.\n *\n * Keyboard: arrows move the active cell, Enter/Space fires `onCellClick`, Escape\n * deactivates. The tooltip (or the live region without one) describes the active cell.\n */\nexport function createHeatmapChart(container: HTMLElement, config: HeatmapChartConfig): ChartHandle<HeatmapData> {\n let rows = config.rows;\n let columns = config.columns;\n let cells = config.cells;\n let domain = config.domain;\n let rowSummary = config.rowSummary;\n\n warnOnInvalid(rows, columns, cells);\n\n const margin = resolveMargin({ bottom: 8, left: 8, right: 8, top: 8, ...config.margin });\n const gap = Math.max(0, config.gap ?? 2);\n const showValues = config.showValues ?? true;\n const formatValue = config.valueFormat ?? String;\n const ramp = createRamp(config.colorRange ?? DEFAULT_COLOR_RANGE);\n const emptyColor = config.emptyColor ?? EMPTY_FALLBACK;\n const binBounds = buildBinBounds(config.bins);\n const binCount = binBounds.length;\n\n const cellsByKey = new Map<string, HeatmapCell>();\n const cellKey = (row: string, column: string): string => `${row}\\u0000${column}`;\n\n let layout: HeatmapLayout | null = null;\n let activeIndex = -1;\n let highlight: SVGRectElement | null = null;\n // Set by render(): the ramp position for a value depends on the current domain/bins.\n let positionOf: (value: number) => number = () => 0.5;\n\n const root = createSvgElement('g');\n\n const add = <K extends keyof SVGElementTagNameMap>(\n tag: K,\n parent: SVGElement,\n attrs?: Record<string, number | string | undefined>,\n ): SVGElementTagNameMap[K] => {\n const el = createSvgElement(tag, attrs);\n\n parent.appendChild(el);\n\n return el;\n };\n\n function currentDomain(): [number, number] {\n if (domain) return domain;\n\n const values = cells.filter((cell) => cell.value !== null).map((cell) => cell.value as number);\n\n if (values.length === 0) return [0, 1];\n\n return [Math.min(...values), Math.max(...values)];\n }\n\n function computePositionOf(): (value: number) => number {\n if (binCount === 0) {\n const [d0, d1] = currentDomain();\n\n return (value) => (d1 === d0 ? 1 : Math.min(1, Math.max(0, (value - d0) / (d1 - d0))));\n }\n\n return (value) => {\n let bin = binCount - 1;\n\n for (let i = 0; i < binCount; i += 1) {\n const upper = binBounds[i]![1];\n\n if (upper !== null && value < upper) {\n bin = i;\n break;\n }\n }\n\n return binCount === 1 ? 0.5 : bin / (binCount - 1);\n };\n }\n\n function render(ctx: RadialScaffoldContext): void {\n const { legend, svg } = ctx;\n\n if (!svg.contains(root)) svg.appendChild(root);\n removeChildren(root);\n highlight = null;\n activeIndex = -1;\n layout = null;\n\n cellsByKey.clear();\n for (const cell of cells) cellsByKey.set(cellKey(cell.row, cell.column), cell);\n\n positionOf = computePositionOf();\n\n if (binCount > 0 && legend) {\n const items = binBounds.map((bounds, i) => ({\n color: ramp(binCount === 1 ? 0.5 : i / (binCount - 1)),\n name: (config.binLabel ?? defaultBinLabel(formatValue))(bounds[0], bounds[1]),\n }));\n\n if (config.emptyLabel) items.push({ color: emptyColor, name: config.emptyLabel });\n legend.update(items);\n }\n\n if (rows.length === 0 || columns.length === 0) {\n legend?.update([]);\n\n return;\n }\n\n const area = chartArea(ctx.dimensions.width, ctx.dimensions.height, margin);\n const summary = rowSummary && rowSummary.values.length > 0 ? rowSummary : null;\n const leftGutter = Math.min(\n Math.max(...rows.map((row) => estimateTextWidth(row, LABEL_FONT_SIZE))) + LABEL_PAD * 2,\n area.width * MAX_LABEL_GUTTER_RATIO,\n );\n let rightGutter = 0;\n\n if (summary) {\n const summaryTexts = summary.values.filter((value): value is string => value !== null);\n const widest = Math.max(\n estimateTextWidth(summary.header ?? '', LABEL_FONT_SIZE),\n ...summaryTexts.map((value) => estimateTextWidth(value, LABEL_FONT_SIZE)),\n );\n\n rightGutter = Math.min(widest + LABEL_PAD * 2, area.width * MAX_LABEL_GUTTER_RATIO);\n }\n\n const maxColumnLabelWidth = Math.max(...columns.map((column) => estimateTextWidth(column, LABEL_FONT_SIZE)));\n const rotate = maxColumnLabelWidth > area.width / columns.length - LABEL_PAD;\n const headerBand = rotate\n ? Math.min(maxColumnLabelWidth * ROTATED_LABEL_SLOPE + LABEL_PAD, MAX_HEADER_BAND)\n : LABEL_FONT_SIZE + LABEL_PAD;\n\n const gridX = margin.left + leftGutter;\n const gridY = margin.top + headerBand;\n const colWidth = Math.max(0, area.width - leftGutter - rightGutter) / columns.length;\n const rowHeight = Math.max(0, area.height - headerBand) / rows.length;\n\n const geometries: CellGeometry[] = [];\n\n for (let r = 0; r < rows.length; r += 1) {\n for (let c = 0; c < columns.length; c += 1) {\n const row = rows[r]!;\n const column = columns[c]!;\n const value = cellsByKey.get(cellKey(row, column))?.value ?? null;\n\n geometries.push({\n column,\n columnWidth: colWidth,\n fill: value === null ? emptyColor : ramp(positionOf(value)),\n row,\n rowHeight,\n x: gridX + c * colWidth,\n y: gridY + r * rowHeight,\n });\n }\n }\n\n layout = {\n cells: geometries,\n columnCount: columns.length,\n colWidth,\n gridX,\n gridY,\n rowCount: rows.length,\n rowHeight,\n };\n\n // Column headers: horizontal when they fit their column, rotated -45° otherwise.\n for (let c = 0; c < columns.length; c += 1) {\n const cx = gridX + c * colWidth + colWidth / 2;\n const label = add('text', root, {\n 'aria-hidden': 'true',\n class: 'prism-heatmap-column-label',\n x: String(cx),\n y: String(margin.top + headerBand - 4),\n });\n\n label.textContent = columns[c];\n\n if (rotate) {\n setAttributes(label, { 'text-anchor': 'start', transform: `rotate(-45 ${cx} ${margin.top + headerBand - 4})` });\n } else {\n setAttributes(label, { 'text-anchor': 'middle' });\n }\n }\n\n // Row labels in the left gutter.\n for (let r = 0; r < rows.length; r += 1) {\n const label = add('text', root, {\n 'aria-hidden': 'true',\n class: 'prism-heatmap-row-label',\n 'dominant-baseline': 'central',\n 'text-anchor': 'end',\n x: String(gridX - LABEL_PAD),\n y: String(gridY + r * rowHeight + rowHeight / 2),\n });\n\n label.textContent = rows[r];\n }\n\n // Row summary in the right gutter: header in the column-header band, one value per row.\n if (summary) {\n const summaryX = margin.left + area.width - rightGutter / 2;\n\n if (summary.header) {\n const header = add('text', root, {\n 'aria-hidden': 'true',\n class: 'prism-heatmap-summary-header',\n 'text-anchor': 'middle',\n x: String(summaryX),\n y: String(margin.top + headerBand - 4),\n });\n\n header.textContent = summary.header;\n }\n\n for (let r = 0; r < rows.length; r += 1) {\n const label = add('text', root, {\n 'aria-hidden': 'true',\n class: 'prism-heatmap-summary',\n 'dominant-baseline': 'central',\n 'text-anchor': 'middle',\n x: String(summaryX),\n y: String(gridY + r * rowHeight + rowHeight / 2),\n });\n\n label.textContent = summary.values[r] ?? '\\u2014';\n }\n }\n\n // Cells, with their in-cell value when it fits.\n for (const geometry of geometries) {\n const value = cellsByKey.get(cellKey(geometry.row, geometry.column))?.value ?? null;\n const group = add('g', root, { 'aria-hidden': 'true', class: 'prism-heatmap-cell' });\n const rect = add('rect', group, {\n class: 'prism-heatmap-cell-rect',\n height: String(Math.max(0, geometry.rowHeight - gap)),\n rx: '2',\n width: String(Math.max(0, geometry.columnWidth - gap)),\n x: String(geometry.x + gap / 2),\n y: String(geometry.y + gap / 2),\n });\n\n rect.style.setProperty('fill', geometry.fill);\n\n if (showValues && value !== null && colWidth > 28 && rowHeight > 16) {\n const text = add('text', group, {\n class: 'prism-heatmap-value',\n 'dominant-baseline': 'central',\n 'text-anchor': 'middle',\n x: String(geometry.x + colWidth / 2),\n y: String(geometry.y + rowHeight / 2),\n });\n\n text.textContent = formatValue(value);\n text.style.setProperty('fill', valueColorFor(geometry.fill, 'var(--prism-text-color, #0f172a)'));\n }\n }\n\n highlight = add('rect', root, {\n class: 'prism-heatmap-highlight',\n height: '0',\n rx: '2',\n visibility: 'hidden',\n width: '0',\n x: '0',\n y: '0',\n });\n }\n\n function eventFor(geometry: CellGeometry, originalEvent: Event): HeatmapEvent {\n return {\n cell: cellsByKey.get(cellKey(geometry.row, geometry.column)),\n column: geometry.column,\n originalEvent,\n row: geometry.row,\n };\n }\n\n function indexAt(svg: SVGSVGElement, event: MouseEvent): number {\n if (!layout) return -1;\n\n const rect = svg.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const col = Math.floor((x - layout.gridX) / layout.colWidth);\n const row = Math.floor((y - layout.gridY) / layout.rowHeight);\n\n if (col < 0 || row < 0 || col >= layout.columnCount || row >= layout.rowCount) return -1;\n\n return row * layout.columnCount + col;\n }\n\n function describe(index: number): string {\n const geometry = layout!.cells[index]!;\n const value = cellsByKey.get(cellKey(geometry.row, geometry.column))?.value ?? null;\n\n return `${geometry.row} \\u00b7 ${geometry.column}: ${value === null ? '\\u2014' : formatValue(value)}`;\n }\n\n function activate(ctx: RadialScaffoldContext, index: number, originalEvent: Event): void {\n if (!layout || !highlight) return;\n\n const geometry = layout.cells[index];\n\n if (!geometry) return;\n\n activeIndex = index;\n const value = cellsByKey.get(cellKey(geometry.row, geometry.column))?.value ?? null;\n\n setAttributes(highlight, {\n height: String(Math.max(0, geometry.rowHeight - gap)),\n visibility: 'visible',\n width: String(Math.max(0, geometry.columnWidth - gap)),\n x: String(geometry.x + gap / 2),\n y: String(geometry.y + gap / 2),\n });\n\n config.onCellHover?.(eventFor(geometry, originalEvent));\n\n const text = describe(index);\n\n if (!ctx.tooltip) {\n ctx.announcer.announce(text);\n\n return;\n }\n\n const svgRect = ctx.svg.getBoundingClientRect();\n const containerRect = ctx.container.getBoundingClientRect();\n\n ctx.tooltip.show(\n geometry.x + geometry.columnWidth / 2 + (svgRect.left - containerRect.left),\n geometry.y + (svgRect.top - containerRect.top),\n { key: cellKey(geometry.row, geometry.column), meta: { text }, value: value ?? 0 },\n { color: geometry.fill, data: [], name: `${geometry.row} \\u00b7 ${geometry.column}` },\n comparisonContent(\n ctx.svg.ownerDocument,\n `${geometry.row} \\u00b7 ${geometry.column}`,\n [{ color: geometry.fill, name: geometry.column, value: value === null ? '\\u2014' : formatValue(value) }],\n text,\n ),\n );\n }\n\n function deactivate(ctx: RadialScaffoldContext): void {\n if (activeIndex === -1) return;\n\n activeIndex = -1;\n highlight?.setAttribute('visibility', 'hidden');\n config.onCellHover?.(null);\n ctx.tooltip?.hide();\n ctx.announcer.clear();\n }\n\n return createRadialScaffold<HeatmapData>(\n container,\n {\n a11y: config.a11y,\n legend: binCount > 0 && config.legend !== false ? {} : false,\n margin: config.margin,\n tooltip: config.tooltip,\n },\n (ctx): ChartEventHandlers | undefined => {\n render(ctx);\n\n if (!layout) return undefined;\n\n return {\n onClick(event) {\n const index = indexAt(ctx.svg, event);\n\n if (index >= 0) config.onCellClick?.(eventFor(layout!.cells[index]!, event));\n },\n onKeyDown(event) {\n const step = ARROW_STEPS[event.key];\n\n if (step !== undefined) {\n event.preventDefault();\n\n if (activeIndex < 0) {\n activate(ctx, 0, event);\n\n return;\n }\n\n const colCount = layout!.columnCount;\n const row = Math.floor(activeIndex / colCount);\n const col = activeIndex % colCount;\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {\n const nextRow = row + step;\n\n if (nextRow >= 0 && nextRow < layout!.rowCount) {\n activate(ctx, nextRow * colCount + col, event);\n }\n } else {\n const nextCol = col + step;\n\n if (nextCol >= 0 && nextCol < layout!.columnCount) {\n activate(ctx, row * colCount + nextCol, event);\n }\n }\n } else if ((event.key === 'Enter' || event.key === ' ') && activeIndex >= 0) {\n event.preventDefault();\n config.onCellClick?.(eventFor(layout!.cells[activeIndex]!, event));\n } else if (event.key === 'Escape') {\n deactivate(ctx);\n }\n },\n onMouseLeave: () => deactivate(ctx),\n onMouseMove(event) {\n const index = indexAt(ctx.svg, event);\n\n if (index < 0) {\n if (activeIndex >= 0) deactivate(ctx);\n } else if (index !== activeIndex) {\n activate(ctx, index, event);\n }\n },\n };\n },\n (next) => {\n if (next.rows) rows = next.rows;\n if (next.columns) columns = next.columns;\n cells = next.cells;\n if (next.domain) domain = next.domain;\n // Key present (even as `undefined`) replaces the summary; absent keeps it (partial updates).\n if ('rowSummary' in next) rowSummary = next.rowSummary;\n warnOnInvalid(rows, columns, cells);\n },\n );\n}\n\n// ─── Bins ─────────────────────────────────────────────────────────────────────\n\nfunction buildBinBounds(bins: number[] | undefined): Array<[number | null, number | null]> {\n if (!bins || bins.length === 0) return [];\n\n const thresholds = [...bins].sort((a, b) => a - b);\n const bounds: Array<[number | null, number | null]> = [];\n let lower: number | null = null;\n\n for (const threshold of thresholds) {\n bounds.push([lower, threshold]);\n lower = threshold;\n }\n\n bounds.push([lower, null]);\n\n return bounds;\n}\n\nfunction defaultBinLabel(formatValue: (value: number) => string): (from: number | null, to: number | null) => string {\n return (from, to) => {\n if (from === null && to !== null) return `<${formatValue(to)}`;\n if (from !== null && to === null) return `\\u2265${formatValue(from)}`;\n if (from !== null && to !== null) return `${formatValue(from)}\\u2013${formatValue(to)}`;\n\n return '\\u2014';\n };\n}\n"],"mappings":"A,6PAUA,IAAMc,EAAsC,CAAE,UAAW,EAAG,UAAW,GAAI,WAAY,EAAG,QAAS,EAAG,EAChGC,EAAsB,CAAC,UAAW,UAAW,UAAW,UAAW,SAAS,EAC5EC,EAAiB,mCACjBC,EAAkB,GAClBC,EAAY,EACZC,EAAyB,GACzBC,EAAkB,GAClBC,EAAsB,IA6C5B,SAAgB,mBAAmB,EAAwB,EAAsD,CAC/G,IAAIC,EAAOC,EAAO,KACdC,EAAUD,EAAO,QACjBE,EAAQF,EAAO,MACfG,EAASH,EAAO,OAChBI,EAAaJ,EAAO,WAIlBK,EAAS5B,EAAAA,cAAc,CAAE,OAAQ,EAAG,KAAM,EAAG,MAAO,EAAG,IAAK,EAAG,GAAGuB,EAAO,MAAO,CAAC,EACjFM,EAAM,KAAK,IAAI,EAAGN,EAAO,KAAO,CAAC,EACjCO,EAAaP,EAAO,YAAc,GAClCQ,EAAcR,EAAO,aAAe,OACpCS,EAAO/B,EAAAA,WAAWsB,EAAO,YAAcR,CAAmB,EAC1DkB,EAAaV,EAAO,YAAcP,EAClCkB,EAAY,eAAeX,EAAO,IAAI,EACtCY,EAAWD,EAAU,OAErBE,EAAa,IAAI,IACjB,SAAW,EAAa,IAA2B,GAAGC,EAAI,QAAQC,IAEpEC,EAA+B,KAC/BC,EAAc,GACdC,EAAmC,KAEnC,eAA8C,GAE5CC,EAAOxC,EAAAA,iBAAiB,GAAG,EAE3B,KACJ,EACA,EACA,IAC4B,CAC5B,IAAMyC,EAAKzC,EAAAA,iBAAiB0C,EAAKC,CAAK,EAItC,OAFAC,EAAO,YAAYH,CAAE,EAEdA,CACT,EAEA,SAAS,eAAkC,CACzC,GAAIjB,EAAQ,OAAOA,EAEnB,IAAMqB,EAAStB,EAAM,OAAQ,GAASuB,EAAK,QAAU,IAAI,CAAC,CAAC,IAAK,GAASA,EAAK,KAAe,EAI7F,OAFID,EAAO,SAAW,EAAU,CAAC,EAAG,CAAC,EAE9B,CAAC,KAAK,IAAI,GAAGA,CAAM,EAAG,KAAK,IAAI,GAAGA,CAAM,CAAC,CAClD,CAEA,SAAS,mBAA+C,CACtD,GAAIZ,IAAa,EAAG,CAClB,GAAM,CAACc,EAAIC,GAAM,cAAc,EAE/B,MAAQ,IAAWA,IAAOD,EAAK,EAAI,KAAK,IAAI,EAAG,KAAK,IAAI,GAAIE,EAAQF,IAAOC,EAAKD,EAAG,CAAC,CACtF,CAEA,MAAQ,IAAU,CAChB,IAAIG,EAAMjB,EAAW,EAErB,IAAK,IAAIkB,EAAI,EAAGA,EAAIlB,EAAU,GAAK,EAAG,CACpC,IAAMmB,EAAQpB,EAAUmB,EAAE,CAAE,GAE5B,GAAIC,IAAU,MAAQH,EAAQG,EAAO,CACnC,EAAMD,EACN,KACF,CACF,CAEA,OAAOlB,IAAa,EAAI,GAAMiB,GAAOjB,EAAW,EAClD,CACF,CAEA,SAAS,OAAO,EAAkC,CAChD,GAAM,CAAE,SAAQ,OAAQoB,EAEnBC,EAAI,SAASd,CAAI,GAAGc,EAAI,YAAYd,CAAI,EAC7C/B,EAAA,eAAe+B,CAAI,EACnB,EAAY,KACZ,EAAc,GACd,EAAS,KAETN,EAAW,MAAM,EACjB,IAAK,IAAMY,KAAQvB,EAAOW,EAAW,IAAI,QAAQY,EAAK,IAAKA,EAAK,MAAM,EAAGA,CAAI,EAI7E,GAFA,WAAa,kBAAkB,EAE3Bb,EAAW,GAAKsB,EAAQ,CAC1B,IAAMC,EAAQxB,EAAU,KAAK,EAAQ,KAAO,CAC1C,MAAOF,EAAKG,IAAa,EAAI,GAAMkB,GAAKlB,EAAW,EAAE,EACrD,MAAOZ,EAAO,UAAY,gBAAgBQ,CAAW,EAAA,CAAG4B,EAAO,GAAIA,EAAO,EAAE,CAC9E,EAAE,EAEEpC,EAAO,YAAYmC,EAAM,KAAK,CAAE,MAAOzB,EAAY,KAAMV,EAAO,UAAW,CAAC,EAChFkC,EAAO,OAAOC,CAAK,CACrB,CAEA,GAAIpC,EAAK,SAAW,GAAKE,EAAQ,SAAW,EAAG,CAC7CiC,GAAQ,OAAO,CAAC,CAAC,EAEjB,MACF,CAEA,IAAMG,EAAOzD,EAAAA,UAAUoD,EAAI,WAAW,MAAOA,EAAI,WAAW,OAAQ3B,CAAM,EACpEiC,EAAUlC,GAAcA,EAAW,OAAO,OAAS,EAAIA,EAAa,KACpEmC,EAAa,KAAK,IACtB,KAAK,IAAI,GAAGxC,EAAK,IAAK,GAAQlB,EAAAA,kBAAkBiC,EAAKpB,CAAe,CAAC,CAAC,EAAI,GAC1E2C,EAAK,MAAQzC,CACf,EACI4C,EAAc,EAElB,GAAIF,EAAS,CACX,IAAMG,EAAeH,EAAQ,OAAO,OAAQ,GAA2BV,IAAU,IAAI,EAC/Ec,EAAS,KAAK,IAClB7D,EAAAA,kBAAkByD,EAAQ,QAAU,GAAI5C,CAAe,EACvD,GAAG+C,EAAa,IAAK,GAAU5D,EAAAA,kBAAkB+C,EAAOlC,CAAe,CAAC,CAC1E,EAEA,EAAc,KAAK,IAAIgD,EAAS,GAAeL,EAAK,MAAQzC,CAAsB,CACpF,CAEA,IAAM+C,EAAsB,KAAK,IAAI,GAAG1C,EAAQ,IAAK,GAAWpB,EAAAA,kBAAkBkC,EAAQrB,CAAe,CAAC,CAAC,EACrGkD,EAASD,EAAsBN,EAAK,MAAQpC,EAAQ,OAASN,EAC7DkD,EAAaD,EACf,KAAK,IAAID,EAAsB7C,EAAsBH,EAAWE,CAAe,EAC/E,GAEEiD,EAAQzC,EAAO,KAAOkC,EACtBQ,EAAQ1C,EAAO,IAAMwC,EACrBG,EAAW,KAAK,IAAI,EAAGX,EAAK,MAAQE,EAAaC,CAAW,EAAIvC,EAAQ,OACxEgD,EAAY,KAAK,IAAI,EAAGZ,EAAK,OAASQ,CAAU,EAAI9C,EAAK,OAEzDmD,EAA6B,CAAC,EAEpC,IAAK,IAAIC,EAAI,EAAGA,EAAIpD,EAAK,OAAQ,GAAK,EACpC,IAAK,IAAIqD,EAAI,EAAGA,EAAInD,EAAQ,OAAQ,GAAK,EAAG,CAC1C,IAAMa,EAAMf,EAAKoD,GACXpC,EAASd,EAAQmD,GACjBxB,EAAQf,EAAW,IAAI,QAAQC,EAAKC,CAAM,CAAC,CAAC,EAAE,OAAS,KAE7DmC,EAAW,KAAK,CACd,SACA,YAAaF,EACb,KAAMpB,IAAU,KAAOlB,EAAaD,EAAK,WAAWmB,CAAK,CAAC,EAC1D,MACA,YACA,EAAGkB,EAAQM,EAAIJ,EACf,EAAGD,EAAQI,EAAIF,CACjB,CAAC,CACH,CAGF,EAAS,CACP,MAAOC,EACP,YAAajD,EAAQ,OACrB,WACA,QACA,QACA,SAAUF,EAAK,OACf,WACF,EAGA,IAAK,IAAIqD,EAAI,EAAGA,EAAInD,EAAQ,OAAQ,GAAK,EAAG,CAC1C,IAAMoD,EAAKP,EAAQM,EAAIJ,EAAWA,EAAW,EACvCM,EAAQ,IAAI,OAAQnC,EAAM,CAC9B,cAAe,OACf,MAAO,6BACP,EAAG,OAAOkC,CAAE,EACZ,EAAG,OAAOhD,EAAO,IAAMwC,EAAa,CAAC,CACvC,CAAC,EAED,EAAM,YAAc5C,EAAQmD,GAExBR,EACFxD,EAAA,cAAckE,EAAO,CAAE,cAAe,QAAS,UAAW,cAAcD,EAAG,GAAGhD,EAAO,IAAMwC,EAAa,EAAE,EAAG,CAAC,EAE9GzD,EAAA,cAAckE,EAAO,CAAE,cAAe,QAAS,CAAC,CAEpD,CAGA,IAAK,IAAIH,EAAI,EAAGA,EAAIpD,EAAK,OAAQ,GAAK,EAAG,CACvC,IAAMuD,EAAQ,IAAI,OAAQnC,EAAM,CAC9B,cAAe,OACf,MAAO,0BACP,oBAAqB,UACrB,cAAe,MACf,EAAG,OAAO2B,EAAQnD,CAAS,EAC3B,EAAG,OAAOoD,EAAQI,EAAIF,EAAYA,EAAY,CAAC,CACjD,CAAC,EAED,EAAM,YAAclD,EAAKoD,EAC3B,CAGA,GAAIb,EAAS,CACX,IAAMiB,EAAWlD,EAAO,KAAOgC,EAAK,MAAQG,EAAc,EAE1D,GAAIF,EAAQ,OAAQ,CAClB,IAAMkB,EAAS,IAAI,OAAQrC,EAAM,CAC/B,cAAe,OACf,MAAO,+BACP,cAAe,SACf,EAAG,OAAOoC,CAAQ,EAClB,EAAG,OAAOlD,EAAO,IAAMwC,EAAa,CAAC,CACvC,CAAC,EAED,EAAO,YAAcP,EAAQ,MAC/B,CAEA,IAAK,IAAIa,EAAI,EAAGA,EAAIpD,EAAK,OAAQ,GAAK,EAAG,CACvC,IAAMuD,EAAQ,IAAI,OAAQnC,EAAM,CAC9B,cAAe,OACf,MAAO,wBACP,oBAAqB,UACrB,cAAe,SACf,EAAG,OAAOoC,CAAQ,EAClB,EAAG,OAAOR,EAAQI,EAAIF,EAAYA,EAAY,CAAC,CACjD,CAAC,EAED,EAAM,YAAcX,EAAQ,OAAOa,IAAM,GAC3C,CACF,CAGA,IAAK,IAAMM,KAAYP,EAAY,CACjC,IAAMtB,EAAQf,EAAW,IAAI,QAAQ4C,EAAS,IAAKA,EAAS,MAAM,CAAC,CAAC,EAAE,OAAS,KACzEC,EAAQ,IAAI,IAAKvC,EAAM,CAAE,cAAe,OAAQ,MAAO,oBAAqB,CAAC,EAYnF,GAFA,IATiB,OAAQuC,EAAO,CAC9B,MAAO,0BACP,OAAQ,OAAO,KAAK,IAAI,EAAGD,EAAS,UAAYnD,CAAG,CAAC,EACpD,GAAI,IACJ,MAAO,OAAO,KAAK,IAAI,EAAGmD,EAAS,YAAcnD,CAAG,CAAC,EACrD,EAAG,OAAOmD,EAAS,EAAInD,EAAM,CAAC,EAC9B,EAAG,OAAOmD,EAAS,EAAInD,EAAM,CAAC,CAChC,CAEA,CAAA,CAAK,MAAM,YAAY,OAAQmD,EAAS,IAAI,EAExClD,GAAcqB,IAAU,MAAQoB,EAAW,IAAMC,EAAY,GAAI,CACnE,IAAMU,EAAO,IAAI,OAAQD,EAAO,CAC9B,MAAO,sBACP,oBAAqB,UACrB,cAAe,SACf,EAAG,OAAOD,EAAS,EAAIT,EAAW,CAAC,EACnC,EAAG,OAAOS,EAAS,EAAIR,EAAY,CAAC,CACtC,CAAC,EAED,EAAK,YAAczC,EAAYoB,CAAK,EACpC+B,EAAK,MAAM,YAAY,OAAQ7E,EAAAA,cAAc2E,EAAS,KAAM,kCAAkC,CAAC,CACjG,CACF,CAEA,EAAY,IAAI,OAAQtC,EAAM,CAC5B,MAAO,0BACP,OAAQ,IACR,GAAI,IACJ,WAAY,SACZ,MAAO,IACP,EAAG,IACH,EAAG,GACL,CAAC,CACH,CAEA,SAAS,SAAS,EAAwB,EAAoC,CAC5E,MAAO,CACL,KAAMN,EAAW,IAAI,QAAQ4C,EAAS,IAAKA,EAAS,MAAM,CAAC,EAC3D,OAAQA,EAAS,OACjB,gBACA,IAAKA,EAAS,GAChB,CACF,CAEA,SAAS,QAAQ,EAAoB,EAA2B,CAC9D,GAAI,CAACzC,EAAQ,MAAO,GAEpB,IAAM4C,EAAO3B,EAAI,sBAAsB,EACjC4B,EAAIC,EAAM,QAAUF,EAAK,KACzBG,EAAID,EAAM,QAAUF,EAAK,IACzBI,EAAM,KAAK,OAAOH,EAAI7C,EAAO,OAASA,EAAO,QAAQ,EACrDF,EAAM,KAAK,OAAOiD,EAAI/C,EAAO,OAASA,EAAO,SAAS,EAI5D,OAFIgD,EAAM,GAAKlD,EAAM,GAAKkD,GAAOhD,EAAO,aAAeF,GAAOE,EAAO,SAAiB,GAE/EF,EAAME,EAAO,YAAcgD,CACpC,CAEA,SAAS,SAAS,EAAuB,CACvC,IAAMP,EAAWzC,EAAQ,MAAMiD,GACzBrC,EAAQf,EAAW,IAAI,QAAQ4C,EAAS,IAAKA,EAAS,MAAM,CAAC,CAAC,EAAE,OAAS,KAE/E,MAAO,GAAGA,EAAS,IAAI,UAAUA,EAAS,OAAO,IAAI7B,IAAU,KAAO,IAAWpB,EAAYoB,CAAK,GACpG,CAEA,SAAS,SAAS,EAA4B,EAAe,EAA4B,CACvF,GAAI,CAACZ,GAAU,CAACE,EAAW,OAE3B,IAAMuC,EAAWzC,EAAO,MAAMiD,GAE9B,GAAI,CAACR,EAAU,OAEf,EAAcQ,EACd,IAAMrC,EAAQf,EAAW,IAAI,QAAQ4C,EAAS,IAAKA,EAAS,MAAM,CAAC,CAAC,EAAE,OAAS,KAE/ErE,EAAA,cAAc8B,EAAW,CACvB,OAAQ,OAAO,KAAK,IAAI,EAAGuC,EAAS,UAAYnD,CAAG,CAAC,EACpD,WAAY,UACZ,MAAO,OAAO,KAAK,IAAI,EAAGmD,EAAS,YAAcnD,CAAG,CAAC,EACrD,EAAG,OAAOmD,EAAS,EAAInD,EAAM,CAAC,EAC9B,EAAG,OAAOmD,EAAS,EAAInD,EAAM,CAAC,CAChC,CAAC,EAEDN,EAAO,cAAc,SAASyD,EAAUS,CAAa,CAAC,EAEtD,IAAMP,EAAO,SAASM,CAAK,EAE3B,GAAI,CAACjC,EAAI,QAAS,CAChBA,EAAI,UAAU,SAAS2B,CAAI,EAE3B,MACF,CAEA,IAAMQ,EAAUnC,EAAI,IAAI,sBAAsB,EACxCoC,EAAgBpC,EAAI,UAAU,sBAAsB,EAE1DA,EAAI,QAAQ,KACVyB,EAAS,EAAIA,EAAS,YAAc,GAAKU,EAAQ,KAAOC,EAAc,MACtEX,EAAS,GAAKU,EAAQ,IAAMC,EAAc,KAC1C,CAAE,IAAK,QAAQX,EAAS,IAAKA,EAAS,MAAM,EAAG,KAAM,CAAE,MAAK,EAAG,MAAO7B,GAAS,CAAE,EACjF,CAAE,MAAO6B,EAAS,KAAM,KAAM,CAAC,EAAG,KAAM,GAAGA,EAAS,IAAI,UAAUA,EAAS,QAAS,EACpF1E,EAAAA,kBACEiD,EAAI,IAAI,cACR,GAAGyB,EAAS,IAAI,UAAUA,EAAS,SACnC,CAAC,CAAE,MAAOA,EAAS,KAAM,KAAMA,EAAS,OAAQ,MAAO7B,IAAU,KAAO,IAAWpB,EAAYoB,CAAK,CAAE,CAAC,EACvG+B,CACF,CACF,CACF,CAEA,SAAS,WAAW,EAAkC,CAChD1C,IAAgB,KAEpB,EAAc,GACdC,GAAW,aAAa,aAAc,QAAQ,EAC9ClB,EAAO,cAAc,IAAI,EACzBgC,EAAI,SAAS,KAAK,EAClBA,EAAI,UAAU,MAAM,EACtB,CAEA,OAAOhD,EAAAA,qBACLqF,EACA,CACE,KAAMrE,EAAO,KACb,OAAQY,EAAW,GAAKZ,EAAO,SAAW,IAAQ,CAAC,EACnD,OAAQA,EAAO,OACf,QAASA,EAAO,OAClB,EACC,GAAwC,CAGvC,GAFA,OAAOgC,CAAG,EAELhB,EAEL,MAAO,CACL,QAAQ,EAAO,CACb,IAAMiD,EAAQ,QAAQjC,EAAI,IAAK8B,CAAK,EAEhCG,GAAS,GAAGjE,EAAO,cAAc,SAASgB,EAAQ,MAAMiD,GAASH,CAAK,CAAC,CAC7E,EACA,UAAU,EAAO,CACf,IAAMQ,EAAO/E,EAAYuE,EAAM,KAE/B,GAAIQ,IAAS,IAAA,GAAW,CAGtB,GAFAR,EAAM,eAAe,EAEjB7C,EAAc,EAAG,CACnB,SAASe,EAAK,EAAG8B,CAAK,EAEtB,MACF,CAEA,IAAMS,EAAWvD,EAAQ,YACnBF,EAAM,KAAK,MAAMG,EAAcsD,CAAQ,EACvCP,EAAM/C,EAAcsD,EAE1B,GAAIT,EAAM,MAAQ,WAAaA,EAAM,MAAQ,YAAa,CACxD,IAAMU,EAAU1D,EAAMwD,EAElBE,GAAW,GAAKA,EAAUxD,EAAQ,UACpC,SAASgB,EAAKwC,EAAUD,EAAWP,EAAKF,CAAK,CAEjD,KAAO,CACL,IAAMW,EAAUT,EAAMM,EAElBG,GAAW,GAAKA,EAAUzD,EAAQ,aACpC,SAASgB,EAAKlB,EAAMyD,EAAWE,EAASX,CAAK,CAEjD,CACF,MAAYA,EAAM,MAAQ,SAAWA,EAAM,MAAQ,MAAQ7C,GAAe,GACxE6C,EAAM,eAAe,EACrB9D,EAAO,cAAc,SAASgB,EAAQ,MAAMC,GAAe6C,CAAK,CAAC,GACxDA,EAAM,MAAQ,UACvB,WAAW9B,CAAG,CAElB,EACA,iBAAoB,WAAWA,CAAG,EAClC,YAAY,EAAO,CACjB,IAAMiC,EAAQ,QAAQjC,EAAI,IAAK8B,CAAK,EAEhCG,EAAQ,EACNhD,GAAe,GAAG,WAAWe,CAAG,EAC3BiC,IAAUhD,GACnB,SAASe,EAAKiC,EAAOH,CAAK,CAE9B,CACF,CACF,EACC,GAAS,CACJY,EAAK,OAAM,EAAOA,EAAK,MACvBA,EAAK,UAAS,EAAUA,EAAK,SACjC,EAAQA,EAAK,MACTA,EAAK,SAAQ,EAASA,EAAK,QAE3B,eAAgBA,IAAM,EAAaA,EAAK,WAE9C,CACF,CACF,CAIA,SAAS,eAAe,EAAmE,CACzF,GAAI,CAACC,GAAQA,EAAK,SAAW,EAAG,MAAO,CAAC,EAExC,IAAMC,EAAa,CAAC,GAAGD,CAAI,CAAC,CAAC,MAAM,EAAG,IAAME,EAAIC,CAAC,EAC3C1C,EAAgD,CAAC,EACnD2C,EAAuB,KAE3B,IAAK,IAAMC,KAAaJ,EACtBxC,EAAO,KAAK,CAAC2C,EAAOC,CAAS,CAAC,EAC9B,EAAQA,EAKV,OAFA5C,EAAO,KAAK,CAAC2C,EAAO,IAAI,CAAC,EAElB3C,CACT,CAEA,SAAS,gBAAgB,EAA4F,CACnH,OAAQ,EAAM,IACR6C,IAAS,MAAQC,IAAO,KAAa,IAAI1E,EAAY0E,CAAE,IACvDD,IAAS,MAAQC,IAAO,KAAa,SAAS1E,EAAYyE,CAAI,IAC9DA,IAAS,MAAQC,IAAO,KAAa,GAAG1E,EAAYyE,CAAI,EAAE,QAAQzE,EAAY0E,CAAE,IAE7E,GAEX,C"}