graphein 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/dist/animation/animate.d.ts +6 -1
  2. package/dist/animation/animate.d.ts.map +1 -1
  3. package/dist/animation/animate.js +2 -2
  4. package/dist/animation/animate.js.map +2 -2
  5. package/dist/animation/env.d.ts +5 -0
  6. package/dist/animation/env.d.ts.map +1 -1
  7. package/dist/animation/env.js +4 -0
  8. package/dist/animation/env.js.map +2 -2
  9. package/dist/animation/index.d.ts +1 -0
  10. package/dist/animation/index.d.ts.map +1 -1
  11. package/dist/animation/index.js +1 -0
  12. package/dist/animation/index.js.map +2 -2
  13. package/dist/animation/stagger.d.ts +13 -0
  14. package/dist/animation/stagger.d.ts.map +1 -0
  15. package/dist/animation/stagger.js +13 -0
  16. package/dist/animation/stagger.js.map +7 -0
  17. package/dist/axes/draw.d.ts +5 -1
  18. package/dist/axes/draw.d.ts.map +1 -1
  19. package/dist/axes/draw.js +13 -1
  20. package/dist/axes/draw.js.map +2 -2
  21. package/dist/axes/morph.d.ts +111 -0
  22. package/dist/axes/morph.d.ts.map +1 -0
  23. package/dist/axes/morph.js +207 -0
  24. package/dist/axes/morph.js.map +7 -0
  25. package/dist/axes/overlay.d.ts.map +1 -1
  26. package/dist/axes/overlay.js +88 -25
  27. package/dist/axes/overlay.js.map +2 -2
  28. package/dist/axes/text.d.ts +2 -0
  29. package/dist/axes/text.d.ts.map +1 -1
  30. package/dist/axes/text.js +1 -0
  31. package/dist/axes/text.js.map +2 -2
  32. package/dist/axes/tickLabels.d.ts +2 -0
  33. package/dist/axes/tickLabels.d.ts.map +1 -1
  34. package/dist/axes/tickLabels.js +2 -2
  35. package/dist/axes/tickLabels.js.map +2 -2
  36. package/dist/charts/annotations.d.ts +9 -3
  37. package/dist/charts/annotations.d.ts.map +1 -1
  38. package/dist/charts/annotations.js +82 -27
  39. package/dist/charts/annotations.js.map +2 -2
  40. package/dist/charts/area.d.ts +37 -1
  41. package/dist/charts/area.d.ts.map +1 -1
  42. package/dist/charts/area.js +35 -17
  43. package/dist/charts/area.js.map +2 -2
  44. package/dist/charts/bar.d.ts +53 -0
  45. package/dist/charts/bar.d.ts.map +1 -1
  46. package/dist/charts/bar.js +117 -83
  47. package/dist/charts/bar.js.map +2 -2
  48. package/dist/charts/box.d.ts +31 -0
  49. package/dist/charts/box.d.ts.map +1 -1
  50. package/dist/charts/box.js +93 -70
  51. package/dist/charts/box.js.map +2 -2
  52. package/dist/charts/bubbleMap.d.ts +20 -0
  53. package/dist/charts/bubbleMap.d.ts.map +1 -0
  54. package/dist/charts/bubbleMap.js +951 -0
  55. package/dist/charts/bubbleMap.js.map +7 -0
  56. package/dist/charts/bullet.d.ts.map +1 -1
  57. package/dist/charts/bullet.js +69 -51
  58. package/dist/charts/bullet.js.map +2 -2
  59. package/dist/charts/calendarHeatmap.d.ts.map +1 -1
  60. package/dist/charts/calendarHeatmap.js +56 -19
  61. package/dist/charts/calendarHeatmap.js.map +2 -2
  62. package/dist/charts/candlestick.d.ts +40 -0
  63. package/dist/charts/candlestick.d.ts.map +1 -0
  64. package/dist/charts/candlestick.js +325 -0
  65. package/dist/charts/candlestick.js.map +7 -0
  66. package/dist/charts/choropleth.d.ts.map +1 -1
  67. package/dist/charts/choropleth.js +46 -67
  68. package/dist/charts/choropleth.js.map +2 -2
  69. package/dist/charts/combo.d.ts.map +1 -1
  70. package/dist/charts/combo.js +127 -27
  71. package/dist/charts/combo.js.map +3 -3
  72. package/dist/charts/dumbbell.d.ts.map +1 -1
  73. package/dist/charts/dumbbell.js +100 -31
  74. package/dist/charts/dumbbell.js.map +2 -2
  75. package/dist/charts/funnel.d.ts.map +1 -1
  76. package/dist/charts/funnel.js +93 -28
  77. package/dist/charts/funnel.js.map +3 -3
  78. package/dist/charts/gantt.d.ts +22 -0
  79. package/dist/charts/gantt.d.ts.map +1 -0
  80. package/dist/charts/gantt.js +564 -0
  81. package/dist/charts/gantt.js.map +7 -0
  82. package/dist/charts/gauge.d.ts.map +1 -1
  83. package/dist/charts/gauge.js +30 -4
  84. package/dist/charts/gauge.js.map +2 -2
  85. package/dist/charts/geo.d.ts +87 -0
  86. package/dist/charts/geo.d.ts.map +1 -0
  87. package/dist/charts/geo.js +140 -0
  88. package/dist/charts/geo.js.map +7 -0
  89. package/dist/charts/geometry.d.ts +20 -0
  90. package/dist/charts/geometry.d.ts.map +1 -0
  91. package/dist/charts/geometry.js +25 -0
  92. package/dist/charts/geometry.js.map +7 -0
  93. package/dist/charts/heatmap.d.ts.map +1 -1
  94. package/dist/charts/heatmap.js +58 -49
  95. package/dist/charts/heatmap.js.map +2 -2
  96. package/dist/charts/histogram.d.ts +1 -1
  97. package/dist/charts/histogram.d.ts.map +1 -1
  98. package/dist/charts/histogram.js +49 -31
  99. package/dist/charts/histogram.js.map +2 -2
  100. package/dist/charts/index.d.ts +35 -1
  101. package/dist/charts/index.d.ts.map +1 -1
  102. package/dist/charts/index.js +12 -0
  103. package/dist/charts/index.js.map +2 -2
  104. package/dist/charts/legibility.d.ts +38 -0
  105. package/dist/charts/legibility.d.ts.map +1 -0
  106. package/dist/charts/legibility.js +72 -0
  107. package/dist/charts/legibility.js.map +7 -0
  108. package/dist/charts/line.d.ts +39 -1
  109. package/dist/charts/line.d.ts.map +1 -1
  110. package/dist/charts/line.js +48 -16
  111. package/dist/charts/line.js.map +2 -2
  112. package/dist/charts/matrix.d.ts +2 -1
  113. package/dist/charts/matrix.d.ts.map +1 -1
  114. package/dist/charts/matrix.js +481 -319
  115. package/dist/charts/matrix.js.map +3 -3
  116. package/dist/charts/morphFrame.d.ts +83 -0
  117. package/dist/charts/morphFrame.d.ts.map +1 -0
  118. package/dist/charts/morphFrame.js +128 -0
  119. package/dist/charts/morphFrame.js.map +7 -0
  120. package/dist/charts/pie.d.ts.map +1 -1
  121. package/dist/charts/pie.js +89 -26
  122. package/dist/charts/pie.js.map +2 -2
  123. package/dist/charts/pieState.d.ts +27 -0
  124. package/dist/charts/pieState.d.ts.map +1 -0
  125. package/dist/charts/pieState.js +55 -0
  126. package/dist/charts/pieState.js.map +7 -0
  127. package/dist/charts/radar.d.ts +62 -0
  128. package/dist/charts/radar.d.ts.map +1 -0
  129. package/dist/charts/radar.js +912 -0
  130. package/dist/charts/radar.js.map +7 -0
  131. package/dist/charts/radialBar.d.ts +60 -0
  132. package/dist/charts/radialBar.d.ts.map +1 -0
  133. package/dist/charts/radialBar.js +708 -0
  134. package/dist/charts/radialBar.js.map +7 -0
  135. package/dist/charts/sankey.d.ts.map +1 -1
  136. package/dist/charts/sankey.js +223 -57
  137. package/dist/charts/sankey.js.map +3 -3
  138. package/dist/charts/scatter.d.ts +31 -0
  139. package/dist/charts/scatter.d.ts.map +1 -1
  140. package/dist/charts/scatter.js +48 -12
  141. package/dist/charts/scatter.js.map +2 -2
  142. package/dist/charts/slope.d.ts.map +1 -1
  143. package/dist/charts/slope.js +55 -3
  144. package/dist/charts/slope.js.map +2 -2
  145. package/dist/charts/sunburst.d.ts +14 -0
  146. package/dist/charts/sunburst.d.ts.map +1 -0
  147. package/dist/charts/sunburst.js +885 -0
  148. package/dist/charts/sunburst.js.map +7 -0
  149. package/dist/charts/table.d.ts +2 -1
  150. package/dist/charts/table.d.ts.map +1 -1
  151. package/dist/charts/table.js +214 -224
  152. package/dist/charts/table.js.map +3 -3
  153. package/dist/charts/tableView/canvas.d.ts +16 -1
  154. package/dist/charts/tableView/canvas.d.ts.map +1 -1
  155. package/dist/charts/tableView/canvas.js +361 -252
  156. package/dist/charts/tableView/canvas.js.map +3 -3
  157. package/dist/charts/tableView/conditional.d.ts +66 -40
  158. package/dist/charts/tableView/conditional.d.ts.map +1 -1
  159. package/dist/charts/tableView/conditional.js +163 -256
  160. package/dist/charts/tableView/conditional.js.map +3 -3
  161. package/dist/charts/tableView/dom.d.ts +26 -2
  162. package/dist/charts/tableView/dom.d.ts.map +1 -1
  163. package/dist/charts/tableView/dom.js +609 -269
  164. package/dist/charts/tableView/dom.js.map +3 -3
  165. package/dist/charts/tableView/layout.d.ts +22 -0
  166. package/dist/charts/tableView/layout.d.ts.map +1 -0
  167. package/dist/charts/tableView/layout.js +246 -0
  168. package/dist/charts/tableView/layout.js.map +7 -0
  169. package/dist/charts/tableView/sketch.d.ts +3 -2
  170. package/dist/charts/tableView/sketch.d.ts.map +1 -1
  171. package/dist/charts/tableView/sketch.js +2 -2
  172. package/dist/charts/tableView/sketch.js.map +2 -2
  173. package/dist/charts/tableView/sketchBar.d.ts +7 -0
  174. package/dist/charts/tableView/sketchBar.d.ts.map +1 -0
  175. package/dist/charts/tableView/sketchBar.js +32 -0
  176. package/dist/charts/tableView/sketchBar.js.map +7 -0
  177. package/dist/charts/tableView/sort.d.ts +9 -0
  178. package/dist/charts/tableView/sort.d.ts.map +1 -0
  179. package/dist/charts/tableView/sort.js +70 -0
  180. package/dist/charts/tableView/sort.js.map +7 -0
  181. package/dist/charts/tableView/sparkline.d.ts +30 -0
  182. package/dist/charts/tableView/sparkline.d.ts.map +1 -0
  183. package/dist/charts/tableView/sparkline.js +63 -0
  184. package/dist/charts/tableView/sparkline.js.map +7 -0
  185. package/dist/charts/tableView/style.d.ts +67 -0
  186. package/dist/charts/tableView/style.d.ts.map +1 -0
  187. package/dist/charts/tableView/style.js +131 -0
  188. package/dist/charts/tableView/style.js.map +7 -0
  189. package/dist/charts/tableView/types.d.ts +69 -46
  190. package/dist/charts/tableView/types.d.ts.map +1 -1
  191. package/dist/charts/tableView/types.js +22 -87
  192. package/dist/charts/tableView/types.js.map +2 -2
  193. package/dist/charts/tableView.d.ts +10 -5
  194. package/dist/charts/tableView.d.ts.map +1 -1
  195. package/dist/charts/tableView.js +12 -8
  196. package/dist/charts/tableView.js.map +2 -2
  197. package/dist/charts/treemap.d.ts.map +1 -1
  198. package/dist/charts/treemap.js +177 -68
  199. package/dist/charts/treemap.js.map +3 -3
  200. package/dist/charts/trendline.d.ts +13 -5
  201. package/dist/charts/trendline.d.ts.map +1 -1
  202. package/dist/charts/trendline.js +205 -56
  203. package/dist/charts/trendline.js.map +2 -2
  204. package/dist/charts/waterfall.d.ts.map +1 -1
  205. package/dist/charts/waterfall.js +76 -5
  206. package/dist/charts/waterfall.js.map +2 -2
  207. package/dist/index.d.ts +1 -1
  208. package/dist/index.js +1 -1
  209. package/dist/index.js.map +1 -1
  210. package/dist/interaction/cartesian.d.ts +4 -1
  211. package/dist/interaction/cartesian.d.ts.map +1 -1
  212. package/dist/interaction/cartesian.js +104 -34
  213. package/dist/interaction/cartesian.js.map +2 -2
  214. package/dist/interaction/controller.d.ts +29 -1
  215. package/dist/interaction/controller.d.ts.map +1 -1
  216. package/dist/interaction/controller.js +131 -8
  217. package/dist/interaction/controller.js.map +2 -2
  218. package/dist/interaction/focus.d.ts +23 -0
  219. package/dist/interaction/focus.d.ts.map +1 -0
  220. package/dist/interaction/focus.js +65 -0
  221. package/dist/interaction/focus.js.map +7 -0
  222. package/dist/interaction/tooltip.d.ts +6 -0
  223. package/dist/interaction/tooltip.d.ts.map +1 -1
  224. package/dist/interaction/tooltip.js +41 -16
  225. package/dist/interaction/tooltip.js.map +2 -2
  226. package/dist/interaction/types.d.ts +19 -0
  227. package/dist/interaction/types.d.ts.map +1 -1
  228. package/dist/interaction/veil.d.ts +25 -0
  229. package/dist/interaction/veil.d.ts.map +1 -0
  230. package/dist/interaction/veil.js +35 -0
  231. package/dist/interaction/veil.js.map +7 -0
  232. package/dist/pivot/pivot.d.ts +18 -0
  233. package/dist/pivot/pivot.d.ts.map +1 -1
  234. package/dist/pivot/pivot.js +46 -34
  235. package/dist/pivot/pivot.js.map +2 -2
  236. package/dist/render/chromeMotion.d.ts +65 -0
  237. package/dist/render/chromeMotion.d.ts.map +1 -0
  238. package/dist/render/chromeMotion.js +79 -0
  239. package/dist/render/chromeMotion.js.map +7 -0
  240. package/dist/render/pathSample.d.ts +24 -0
  241. package/dist/render/pathSample.d.ts.map +1 -0
  242. package/dist/render/pathSample.js +76 -0
  243. package/dist/render/pathSample.js.map +7 -0
  244. package/dist/render/transient.d.ts +18 -0
  245. package/dist/render/transient.d.ts.map +1 -0
  246. package/dist/render/transient.js +19 -0
  247. package/dist/render/transient.js.map +7 -0
  248. package/dist/runtime/candlestick.d.ts +112 -0
  249. package/dist/runtime/candlestick.d.ts.map +1 -0
  250. package/dist/runtime/candlestick.js +531 -0
  251. package/dist/runtime/candlestick.js.map +7 -0
  252. package/dist/runtime/cartesian.d.ts +14 -3
  253. package/dist/runtime/cartesian.d.ts.map +1 -1
  254. package/dist/runtime/cartesian.js.map +2 -2
  255. package/dist/runtime/chartAnimation.d.ts +54 -7
  256. package/dist/runtime/chartAnimation.d.ts.map +1 -1
  257. package/dist/runtime/chartAnimation.js +146 -28
  258. package/dist/runtime/chartAnimation.js.map +2 -2
  259. package/dist/runtime/chartRuntime.d.ts +42 -0
  260. package/dist/runtime/chartRuntime.d.ts.map +1 -1
  261. package/dist/runtime/chartRuntime.js +196 -30
  262. package/dist/runtime/chartRuntime.js.map +2 -2
  263. package/dist/runtime/dashboardDom.d.ts.map +1 -1
  264. package/dist/runtime/dashboardDom.js +45 -0
  265. package/dist/runtime/dashboardDom.js.map +2 -2
  266. package/dist/runtime/facet.d.ts +1 -1
  267. package/dist/runtime/facet.d.ts.map +1 -1
  268. package/dist/runtime/gantt.d.ts +137 -0
  269. package/dist/runtime/gantt.d.ts.map +1 -0
  270. package/dist/runtime/gantt.js +647 -0
  271. package/dist/runtime/gantt.js.map +7 -0
  272. package/dist/runtime/morph.d.ts +33 -0
  273. package/dist/runtime/morph.d.ts.map +1 -0
  274. package/dist/runtime/morph.js +332 -0
  275. package/dist/runtime/morph.js.map +7 -0
  276. package/dist/runtime/radar.d.ts +75 -0
  277. package/dist/runtime/radar.d.ts.map +1 -0
  278. package/dist/runtime/radar.js +201 -0
  279. package/dist/runtime/radar.js.map +7 -0
  280. package/dist/runtime/radialBar.d.ts +81 -0
  281. package/dist/runtime/radialBar.d.ts.map +1 -0
  282. package/dist/runtime/radialBar.js +125 -0
  283. package/dist/runtime/radialBar.js.map +7 -0
  284. package/dist/runtime/report/constants.d.ts +2 -2
  285. package/dist/runtime/report/constants.d.ts.map +1 -1
  286. package/dist/runtime/runtimeShared.d.ts +3 -3
  287. package/dist/runtime/runtimeShared.d.ts.map +1 -1
  288. package/dist/runtime/sunburst.d.ts +74 -0
  289. package/dist/runtime/sunburst.d.ts.map +1 -0
  290. package/dist/runtime/sunburst.js +150 -0
  291. package/dist/runtime/sunburst.js.map +7 -0
  292. package/dist/runtime/wire.d.ts.map +1 -1
  293. package/dist/runtime/wire.js +3 -1
  294. package/dist/runtime/wire.js.map +2 -2
  295. package/dist/shape/arc.d.ts +15 -2
  296. package/dist/shape/arc.d.ts.map +1 -1
  297. package/dist/shape/arc.js +204 -32
  298. package/dist/shape/arc.js.map +2 -2
  299. package/dist/spec/chartDefinitions.d.ts +6 -0
  300. package/dist/spec/chartDefinitions.d.ts.map +1 -1
  301. package/dist/spec/chartDefinitions.js +7 -1
  302. package/dist/spec/chartDefinitions.js.map +2 -2
  303. package/dist/spec/templates.d.ts.map +1 -1
  304. package/dist/spec/templates.js +177 -2
  305. package/dist/spec/templates.js.map +2 -2
  306. package/dist/spec/types.d.ts +258 -6
  307. package/dist/spec/types.d.ts.map +1 -1
  308. package/dist/spec/types.js +6 -0
  309. package/dist/spec/types.js.map +2 -2
  310. package/dist/spec/validate/charts.js +147 -15
  311. package/dist/spec/validate/charts.js.map +2 -2
  312. package/dist/spec/validate/common.d.ts +6 -0
  313. package/dist/spec/validate/common.d.ts.map +1 -1
  314. package/dist/spec/validate/overlays.js +2 -2
  315. package/dist/spec/validate/overlays.js.map +2 -2
  316. package/dist/spec/validate.data.d.ts.map +1 -1
  317. package/dist/spec/validate.data.js +8 -3
  318. package/dist/spec/validate.data.js.map +2 -2
  319. package/package.json +1 -1
@@ -1,9 +1,34 @@
1
1
  import { linearRegression } from "../analyze/regression.js";
2
+ import { parseColor, rgbaToCss } from "../color/index.js";
2
3
  import { fontString, measureText } from "../render/text.js";
3
- import { overlayTextToCanvasCmd, paintCanvasText } from "../render/overlayText.js";
4
+ import { CHROME_VISIBLE, chromeEntrance, chromeMorph, revealAt, stashChrome, visibleItems } from "../render/chromeMotion.js";
4
5
  import { accessor, toDate, toNumber } from "../util/data.js";
5
6
  import { crisp } from "../util/math.js";
6
- const DEFAULT_STROKE_WIDTH = 2;
7
+ import { morphedModel } from "../axes/morph.js";
8
+ import { addOverlayText } from "./chrome.js";
9
+ import { spreadCenters } from "./labelLayout.js";
10
+ import { contrastTarget, knownBackdrop, toneForContrast } from "./legibility.js";
11
+ import { lerp, mixColor, switchAt } from "./morphFrame.js";
12
+ const DEFAULT_STROKE_WIDTH = 1.5;
13
+ const DEFAULT_STROKE_DASH = [6, 4];
14
+ const DEFAULT_OPACITY = 0.85;
15
+ const LABEL_PAD_X = 4;
16
+ const LABEL_PAD_Y = 1;
17
+ const LABEL_GAP = 6;
18
+ const TRENDLINES = "trendlines";
19
+ const TREND_LABELS = "trendlines:labels";
20
+ function blendByKey(from, to, t, mix) {
21
+ const before = new Map(from.map((item) => [item.key, item]));
22
+ const now = new Set(to.map((item) => item.key));
23
+ return [
24
+ ...from.filter((item) => !now.has(item.key)).map((item) => ({ ...item, alpha: item.alpha * (1 - t) })),
25
+ ...to.map((item) => {
26
+ const prev = before.get(item.key);
27
+ return prev ? { ...mix(prev, item), alpha: lerp(prev.alpha, item.alpha, t) } : { ...item, alpha: item.alpha * t };
28
+ })
29
+ ];
30
+ }
31
+ const TEXT_CONTRAST_MIN = 4.5;
7
32
  function resolveTrendline(value) {
8
33
  if (!value) return null;
9
34
  const cfg = typeof value === "object" ? value : {};
@@ -13,7 +38,8 @@ function resolveTrendline(value) {
13
38
  label: cfg.label ?? false,
14
39
  color: cfg.color,
15
40
  strokeWidth: cfg.strokeWidth ?? DEFAULT_STROKE_WIDTH,
16
- strokeDash: cfg.strokeDash ?? []
41
+ strokeDash: cfg.strokeDash ?? DEFAULT_STROKE_DASH,
42
+ opacity: cfg.color === void 0 ? DEFAULT_OPACITY : 1
17
43
  };
18
44
  }
19
45
  function xToNum(model, value) {
@@ -28,7 +54,7 @@ function trendGroups(model, cfg) {
28
54
  const readY = accessor(model.y.field ?? "");
29
55
  const grouped = cfg.groupBy ?? model.series.length > 1;
30
56
  const muted = cfg.color ?? model.tokens.color.textMuted;
31
- const buckets = grouped ? model.series.map((s) => ({ color: cfg.color ?? s.color, rows: s.rows })) : [{ color: muted, rows: model.series.flatMap((s) => s.rows) }];
57
+ const buckets = grouped ? model.series.map((s) => ({ key: s.key, color: cfg.color ?? s.color, rows: s.rows })) : [{ key: "all", color: muted, rows: model.series.flatMap((s) => s.rows) }];
32
58
  const out = [];
33
59
  for (const bucket of buckets) {
34
60
  const xs = [];
@@ -42,83 +68,206 @@ function trendGroups(model, cfg) {
42
68
  }
43
69
  const fit = linearRegression(xs, ys);
44
70
  if (!fit) continue;
45
- out.push({ color: bucket.color, fit, xMin: Math.min(...xs), xMax: Math.max(...xs) });
71
+ out.push({ key: bucket.key, color: bucket.color, fit, xMin: Math.min(...xs), xMax: Math.max(...xs) });
46
72
  }
47
73
  return out;
48
74
  }
75
+ function trendSegments(model, cfg, groups) {
76
+ const out = [];
77
+ for (const g of groups) {
78
+ const x0 = model.x.pixel(g.xMin);
79
+ const x1 = model.x.pixel(g.xMax);
80
+ const y0 = model.y.pixel(g.fit.predict(g.xMin));
81
+ const y1 = model.y.pixel(g.fit.predict(g.xMax));
82
+ if (x0 == null || x1 == null || !Number.isFinite(x0) || !Number.isFinite(x1) || !Number.isFinite(y0) || !Number.isFinite(y1)) {
83
+ continue;
84
+ }
85
+ out.push({ key: g.key, color: g.color, x0, y0, x1, y1, alpha: 1, width: cfg.strokeWidth, dash: cfg.strokeDash, opacity: cfg.opacity });
86
+ }
87
+ return out;
88
+ }
89
+ function capFor(dash) {
90
+ const dashed = dash.some((n) => n > 0) && !dash.some((n) => n === 0);
91
+ return dashed ? "butt" : "round";
92
+ }
49
93
  function drawTrendlines(surface, model) {
50
94
  const cfg = resolveTrendline(model.spec.trendline);
51
- if (!cfg || model.x.kind === "band" || model.x.kind === "point") return;
52
- const groups = trendGroups(model, cfg);
53
- if (groups.length === 0) return;
95
+ const usable = !!cfg && model.x.kind !== "band" && model.x.kind !== "point";
96
+ const groups = usable ? trendGroups(model, cfg) : [];
97
+ const segments = cfg ? trendSegments(model, cfg, groups) : [];
98
+ const morph = chromeMorph(surface, TRENDLINES);
99
+ if (morph) {
100
+ const drawn = visibleItems(blendByKey(morph.from, segments, morph.t, (a, b) => ({
101
+ ...b,
102
+ x0: lerp(a.x0, b.x0, morph.t),
103
+ y0: lerp(a.y0, b.y0, morph.t),
104
+ x1: lerp(a.x1, b.x1, morph.t),
105
+ y1: lerp(a.y1, b.y1, morph.t),
106
+ color: mixColor(a.color, b.color, morph.t)
107
+ })));
108
+ stashChrome(surface, TRENDLINES, drawn);
109
+ paintTrendSegments(surface.marks.ctx, morphedModel(surface, model).plot, drawn);
110
+ return;
111
+ }
112
+ const entrance = chromeEntrance(surface);
113
+ const reveal = entrance == null ? 1 : revealAt(entrance, 0.45, 1);
114
+ stashChrome(surface, TRENDLINES, reveal < 1 ? segments.map((s) => ({ ...s, alpha: reveal })) : segments);
115
+ if (!cfg || !usable || groups.length === 0) return;
116
+ if (!(reveal > 0)) return;
54
117
  const ctx = surface.marks.ctx;
55
118
  const { plot } = model;
56
119
  ctx.save();
57
120
  ctx.beginPath();
58
- ctx.rect(plot.x, plot.y, plot.width, plot.height);
121
+ ctx.rect(plot.x, plot.y, reveal < 1 ? plot.width * reveal : plot.width, plot.height);
59
122
  ctx.clip();
60
123
  ctx.lineWidth = cfg.strokeWidth;
61
124
  ctx.setLineDash(cfg.strokeDash);
62
- ctx.lineCap = "round";
63
- for (const g of groups) {
64
- const x0 = model.x.pixel(g.xMin);
65
- const x1 = model.x.pixel(g.xMax);
66
- const y0 = model.y.pixel(g.fit.predict(g.xMin));
67
- const y1 = model.y.pixel(g.fit.predict(g.xMax));
68
- if (x0 == null || x1 == null || !Number.isFinite(x0) || !Number.isFinite(x1) || !Number.isFinite(y0) || !Number.isFinite(y1)) {
69
- continue;
70
- }
71
- ctx.strokeStyle = g.color;
125
+ ctx.lineCap = capFor(cfg.strokeDash);
126
+ ctx.globalAlpha = cfg.opacity;
127
+ for (const s of segments) {
128
+ ctx.strokeStyle = s.color;
129
+ ctx.beginPath();
130
+ ctx.moveTo(s.x0, crisp(s.y0));
131
+ ctx.lineTo(s.x1, crisp(s.y1));
132
+ ctx.stroke();
133
+ }
134
+ ctx.restore();
135
+ }
136
+ function paintTrendSegments(ctx, plot, segments) {
137
+ if (segments.length === 0) return;
138
+ ctx.save();
139
+ ctx.beginPath();
140
+ ctx.rect(plot.x, plot.y, plot.width, plot.height);
141
+ ctx.clip();
142
+ for (const s of segments) {
143
+ if (!(s.alpha > 2e-3)) continue;
144
+ ctx.lineWidth = s.width;
145
+ ctx.setLineDash(s.dash);
146
+ ctx.lineCap = capFor(s.dash);
147
+ ctx.globalAlpha = s.opacity * Math.min(1, s.alpha);
148
+ ctx.strokeStyle = s.color;
72
149
  ctx.beginPath();
73
- ctx.moveTo(x0, crisp(y0));
74
- ctx.lineTo(x1, crisp(y1));
150
+ ctx.moveTo(s.x0, crisp(s.y0));
151
+ ctx.lineTo(s.x1, crisp(s.y1));
75
152
  ctx.stroke();
76
153
  }
77
154
  ctx.restore();
78
155
  }
156
+ function placeTrendLabel(model, g, font, size, color) {
157
+ const { plot } = model;
158
+ const x0 = model.x.pixel(g.xMin);
159
+ const x1 = model.x.pixel(g.xMax);
160
+ const y0 = model.y.pixel(g.fit.predict(g.xMin));
161
+ const y1 = model.y.pixel(g.fit.predict(g.xMax));
162
+ if (x1 == null || !Number.isFinite(x1) || !Number.isFinite(y1)) return null;
163
+ const text = `R\xB2=${g.fit.r2.toFixed(2)}`;
164
+ const width = measureText(text, font).width + LABEL_PAD_X * 2;
165
+ const height = Math.max(size + LABEL_PAD_Y * 2, size * 1.2);
166
+ const right = plot.x + plot.width;
167
+ let cx;
168
+ let cy;
169
+ if (x1 + LABEL_GAP + width <= right) {
170
+ cx = x1 + LABEL_GAP + width / 2;
171
+ cy = y1;
172
+ } else {
173
+ cx = x1 - LABEL_GAP - width / 2;
174
+ const falling = x0 != null && Number.isFinite(x0) && Number.isFinite(y0) && y1 - y0 > 0.5;
175
+ cy = falling ? y1 + 3 + height / 2 : y1 - 3 - height / 2;
176
+ }
177
+ const clampTo = (v, lo, hi) => lo > hi ? (lo + hi) / 2 : Math.min(hi, Math.max(lo, v));
178
+ cx = clampTo(cx, plot.x + width / 2, right - width / 2);
179
+ cy = clampTo(cy, plot.y + height / 2, plot.y + plot.height - height / 2);
180
+ return { key: g.key, text, color, cx, cy, width, height, alpha: 1 };
181
+ }
182
+ function decollide(labels, plot) {
183
+ const ordered = [...labels].sort((a, b) => a.cx - a.width / 2 - (b.cx - b.width / 2));
184
+ const clusters = [];
185
+ let reach = -Infinity;
186
+ for (const label of ordered) {
187
+ const left = label.cx - label.width / 2;
188
+ const rightEdge = label.cx + label.width / 2;
189
+ if (clusters.length && left < reach + 2) {
190
+ clusters[clusters.length - 1].push(label);
191
+ reach = Math.max(reach, rightEdge);
192
+ } else {
193
+ clusters.push([label]);
194
+ reach = rightEdge;
195
+ }
196
+ }
197
+ const kept = [];
198
+ let dropped = 0;
199
+ for (const cluster of clusters) {
200
+ const rowH = Math.max(...cluster.map((l) => l.height)) + 2;
201
+ const capacity = Math.max(1, Math.floor((plot.height + 2) / rowH));
202
+ const fits = cluster.slice(0, capacity);
203
+ dropped += cluster.length - fits.length;
204
+ const centers = spreadCenters(fits.map((l) => l.cy), rowH, plot.y + rowH / 2 - 1, plot.y + plot.height - rowH / 2 + 1);
205
+ fits.forEach((l, i) => kept.push({ ...l, cy: centers[i] }));
206
+ }
207
+ return { kept, dropped };
208
+ }
79
209
  function drawTrendlineLabels(surface, model) {
80
210
  const cfg = resolveTrendline(model.spec.trendline);
81
- if (!cfg || !cfg.label || model.x.kind === "band" || model.x.kind === "point") return;
82
- const groups = trendGroups(model, cfg);
83
- if (groups.length === 0) return;
211
+ const usable = !!cfg && cfg.label && model.x.kind !== "band" && model.x.kind !== "point";
212
+ const groups = usable ? trendGroups(model, cfg) : [];
213
+ const morph = chromeMorph(surface, TREND_LABELS);
214
+ if (groups.length === 0 && !morph) {
215
+ stashChrome(surface, TREND_LABELS, []);
216
+ return [];
217
+ }
84
218
  const { tokens, plot } = model;
85
219
  const f = tokens.font;
86
- const font = fontString(f.size.small, f.family, f.weight.medium);
87
- for (const g of groups) {
88
- const x1 = model.x.pixel(g.xMax);
89
- const y1 = model.y.pixel(g.fit.predict(g.xMax));
90
- if (x1 == null || !Number.isFinite(x1) || !Number.isFinite(y1)) continue;
91
- const text = `R\xB2=${g.fit.r2.toFixed(2)}`;
92
- const labelW = measureText(text, font).width;
93
- const nearRight = x1 + 6 + labelW > plot.x + plot.width;
94
- const left = nearRight ? x1 - 6 : x1 + 6;
95
- const align = nearRight ? "right" : "left";
96
- const transform = nearRight ? "translateX(-100%) translateY(-50%)" : "translateY(-50%)";
97
- const top = Math.min(Math.max(y1, plot.y + f.size.small), plot.y + plot.height - 2);
98
- const color = g.color;
99
- if (surface.headless) {
100
- paintCanvasText(
101
- surface.marks.ctx,
102
- overlayTextToCanvasCmd({ left, top, text, color, size: f.size.small, align, transform }, font)
103
- );
104
- continue;
220
+ const size = f.size.small;
221
+ const font = fontString(size, f.family, f.weight.medium);
222
+ const backdrop = knownBackdrop(surface, tokens);
223
+ const pillBackground = backdrop ? rgbaToCss(backdrop) : tokens.dark ? "#000000" : "#ffffff";
224
+ const pillColor = parseColor(pillBackground);
225
+ let kept = [];
226
+ if (groups.length > 0) {
227
+ const labels = [];
228
+ const toward = contrastTarget(pillColor, tokens);
229
+ for (const g of groups) {
230
+ const color = toneForContrast(g.color, toward, pillColor, TEXT_CONTRAST_MIN);
231
+ const label = placeTrendLabel(model, g, font, size, color);
232
+ if (label) labels.push(label);
105
233
  }
106
- const el = document.createElement("div");
107
- el.textContent = text;
108
- el.style.position = "absolute";
109
- el.style.font = font;
110
- el.style.fontSize = `${f.size.small}px`;
111
- el.style.fontWeight = String(f.weight.medium);
112
- el.style.color = color;
113
- el.style.whiteSpace = "nowrap";
114
- el.style.left = `${left}px`;
115
- el.style.top = `${top}px`;
116
- el.style.transform = transform;
117
- el.style.pointerEvents = "none";
118
- surface.overlay.appendChild(el);
234
+ const placed = decollide(labels, plot);
235
+ kept = placed.kept;
236
+ surface.labels?.omit({ role: "annotation", reason: "space", required: false, count: placed.dropped, path: "/trendline/label" });
237
+ }
238
+ const drawn = morph ? visibleItems(blendByKey(morph.from, kept, morph.t, (a, b) => ({
239
+ ...b,
240
+ cx: lerp(a.cx, b.cx, morph.t),
241
+ cy: lerp(a.cy, b.cy, morph.t),
242
+ text: switchAt(a.text, b.text, morph.t),
243
+ color: mixColor(a.color, b.color, morph.t)
244
+ }))) : kept;
245
+ const entrance = chromeEntrance(surface);
246
+ const reveal = entrance == null ? 1 : revealAt(entrance, 0.8, 1);
247
+ stashChrome(surface, TREND_LABELS, reveal < 1 ? drawn.map((label) => ({ ...label, alpha: label.alpha * reveal })) : drawn);
248
+ const elements = [];
249
+ for (const label of drawn) {
250
+ const opacity = label.alpha * reveal;
251
+ if (!(opacity > CHROME_VISIBLE)) continue;
252
+ const el = addOverlayText(surface, tokens, {
253
+ left: label.cx,
254
+ top: label.cy,
255
+ text: label.text,
256
+ color: label.color,
257
+ size,
258
+ weight: f.weight.medium,
259
+ transform: "translate(-50%,-50%)",
260
+ pill: { background: pillBackground, radius: 3, padX: LABEL_PAD_X, padY: LABEL_PAD_Y },
261
+ role: "annotation",
262
+ path: "/trendline/label",
263
+ ...opacity < 1 ? { opacity } : {}
264
+ });
265
+ if (el) elements.push(el);
119
266
  }
267
+ return elements;
120
268
  }
121
269
  export {
270
+ TREND_LABELS,
122
271
  drawTrendlineLabels,
123
272
  drawTrendlines
124
273
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/charts/trendline.ts"],
4
- "sourcesContent": ["/**\n * Derived trendlines for cartesian charts \u2014 a linear line of best fit overlaid\n * on a scatter, line, or area plot via `spec.trendline`.\n *\n * The regression is computed from the already-resolved `CartesianModel` series\n * (one fit per group, or a single overall fit), and the fitted line is stroked\n * across each group's x-extent. Marks land on the marks canvas; optional `R\u00B2`\n * labels live in the HTML overlay (or are painted to canvas when headless),\n * mirroring how `annotations` render. Pure presentation \u2014 no data reshaping.\n */\n\nimport type { Surface } from '../render/surface';\nimport type { CartesianModel } from '../runtime/cartesian';\nimport type { TrendlineConfig } from '../spec/types';\nimport { linearRegression, type RegressionFit } from '../analyze/regression';\nimport { fontString, measureText } from '../render/text';\nimport { overlayTextToCanvasCmd, paintCanvasText } from '../render/overlayText';\nimport { accessor, toDate, toNumber } from '../util/data';\nimport { crisp } from '../util/math';\n\nconst DEFAULT_STROKE_WIDTH = 2;\n\ninterface ResolvedTrendline {\n method: 'linear';\n /** undefined = auto (per-group when the chart splits into series). */\n groupBy: boolean | undefined;\n label: boolean;\n color: string | undefined;\n strokeWidth: number;\n strokeDash: number[];\n}\n\ninterface TrendGroup {\n color: string;\n fit: RegressionFit;\n xMin: number;\n xMax: number;\n}\n\n/** Resolve a `trendline` field (boolean | config) to a settled config, or null. */\nfunction resolveTrendline(value: unknown): ResolvedTrendline | null {\n if (!value) return null;\n const cfg = (typeof value === 'object' ? value : {}) as TrendlineConfig;\n return {\n method: 'linear',\n groupBy: cfg.groupBy,\n label: cfg.label ?? false,\n color: cfg.color,\n strokeWidth: cfg.strokeWidth ?? DEFAULT_STROKE_WIDTH,\n strokeDash: cfg.strokeDash ?? [],\n };\n}\n\n/** Coerce an x value to its numeric domain position (epoch ms for time scales). */\nfunction xToNum(model: CartesianModel, value: unknown): number {\n if (model.x.kind === 'time') {\n const d = toDate(value);\n return d ? d.getTime() : NaN;\n }\n return toNumber(value);\n}\n\n/** Fit one regression per group (or a single overall fit) from the model rows. */\nfunction trendGroups(model: CartesianModel, cfg: ResolvedTrendline): TrendGroup[] {\n const readX = accessor(model.x.field);\n const readY = accessor(model.y.field ?? '');\n const grouped = cfg.groupBy ?? model.series.length > 1;\n const muted = cfg.color ?? model.tokens.color.textMuted;\n\n const buckets = grouped\n ? model.series.map((s) => ({ color: cfg.color ?? s.color, rows: s.rows }))\n : [{ color: muted, rows: model.series.flatMap((s) => s.rows) }];\n\n const out: TrendGroup[] = [];\n for (const bucket of buckets) {\n const xs: number[] = [];\n const ys: number[] = [];\n for (const row of bucket.rows) {\n const xn = xToNum(model, readX(row));\n const yn = toNumber(readY(row));\n if (!Number.isFinite(xn) || !Number.isFinite(yn)) continue;\n xs.push(xn);\n ys.push(yn);\n }\n const fit = linearRegression(xs, ys);\n if (!fit) continue;\n out.push({ color: bucket.color, fit, xMin: Math.min(...xs), xMax: Math.max(...xs) });\n }\n return out;\n}\n\n/**\n * Stroke the fitted line(s) on the marks canvas, clipped to the plot. Call after\n * the chart's data marks so the trendline sits on top. No-op unless the spec\n * opts in via `trendline` and the x-axis is continuous/temporal.\n */\nexport function drawTrendlines(surface: Surface, model: CartesianModel): void {\n const cfg = resolveTrendline((model.spec as { trendline?: unknown }).trendline);\n if (!cfg || model.x.kind === 'band' || model.x.kind === 'point') return;\n\n const groups = trendGroups(model, cfg);\n if (groups.length === 0) return;\n\n const ctx = surface.marks.ctx;\n const { plot } = model;\n\n ctx.save();\n ctx.beginPath();\n ctx.rect(plot.x, plot.y, plot.width, plot.height);\n ctx.clip();\n ctx.lineWidth = cfg.strokeWidth;\n ctx.setLineDash(cfg.strokeDash);\n ctx.lineCap = 'round';\n\n for (const g of groups) {\n const x0 = model.x.pixel(g.xMin);\n const x1 = model.x.pixel(g.xMax);\n const y0 = model.y.pixel(g.fit.predict(g.xMin));\n const y1 = model.y.pixel(g.fit.predict(g.xMax));\n if (\n x0 == null ||\n x1 == null ||\n !Number.isFinite(x0) ||\n !Number.isFinite(x1) ||\n !Number.isFinite(y0) ||\n !Number.isFinite(y1)\n ) {\n continue;\n }\n ctx.strokeStyle = g.color;\n ctx.beginPath();\n ctx.moveTo(x0, crisp(y0));\n ctx.lineTo(x1, crisp(y1));\n ctx.stroke();\n }\n\n ctx.restore();\n}\n\n/**\n * Append `R\u00B2` labels for each fitted line to the HTML overlay (or paint them to\n * canvas when headless). Call after `drawAnnotationLabels`. No-op unless\n * `trendline.label` is set.\n */\nexport function drawTrendlineLabels(surface: Surface, model: CartesianModel): void {\n const cfg = resolveTrendline((model.spec as { trendline?: unknown }).trendline);\n if (!cfg || !cfg.label || model.x.kind === 'band' || model.x.kind === 'point') return;\n\n const groups = trendGroups(model, cfg);\n if (groups.length === 0) return;\n\n const { tokens, plot } = model;\n const f = tokens.font;\n const font = fontString(f.size.small, f.family, f.weight.medium);\n\n for (const g of groups) {\n const x1 = model.x.pixel(g.xMax);\n const y1 = model.y.pixel(g.fit.predict(g.xMax));\n if (x1 == null || !Number.isFinite(x1) || !Number.isFinite(y1)) continue;\n\n const text = `R\\u00B2=${g.fit.r2.toFixed(2)}`;\n // Anchor just past the line end; flip to the left of it when a right-anchored\n // label of the *measured* width would spill past the plot's right edge.\n const labelW = measureText(text, font).width;\n const nearRight = x1 + 6 + labelW > plot.x + plot.width;\n const left = nearRight ? x1 - 6 : x1 + 6;\n const align: 'left' | 'right' = nearRight ? 'right' : 'left';\n const transform = nearRight ? 'translateX(-100%) translateY(-50%)' : 'translateY(-50%)';\n const top = Math.min(Math.max(y1, plot.y + f.size.small), plot.y + plot.height - 2);\n const color = g.color;\n\n if (surface.headless) {\n paintCanvasText(\n surface.marks.ctx,\n overlayTextToCanvasCmd({ left, top, text, color, size: f.size.small, align, transform }, font),\n );\n continue;\n }\n\n const el = document.createElement('div');\n el.textContent = text;\n el.style.position = 'absolute';\n el.style.font = font;\n el.style.fontSize = `${f.size.small}px`;\n el.style.fontWeight = String(f.weight.medium);\n el.style.color = color;\n el.style.whiteSpace = 'nowrap';\n el.style.left = `${left}px`;\n el.style.top = `${top}px`;\n el.style.transform = transform;\n el.style.pointerEvents = 'none';\n surface.overlay.appendChild(el);\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Derived trendlines for cartesian charts \u2014 a linear line of best fit overlaid\n * on a scatter, line, or area plot via `spec.trendline`.\n *\n * The regression is computed from the already-resolved `CartesianModel` series\n * (one fit per group, or a single overall fit), and the fitted line is stroked\n * across each group's x-extent. Marks land on the marks canvas; optional `R\u00B2`\n * labels live in the HTML overlay (or are painted to canvas when headless),\n * mirroring how `annotations` render. Pure presentation \u2014 no data reshaping.\n */\n\nimport type { Surface } from '../render/surface';\nimport type { CartesianModel } from '../runtime/cartesian';\nimport type { TrendlineConfig } from '../spec/types';\nimport type { Rect } from '../types';\nimport { linearRegression, type RegressionFit } from '../analyze/regression';\nimport { parseColor, rgbaToCss } from '../color';\nimport { fontString, measureText } from '../render/text';\nimport { CHROME_VISIBLE, chromeEntrance, chromeMorph, revealAt, stashChrome, visibleItems } from '../render/chromeMotion';\nimport { accessor, toDate, toNumber } from '../util/data';\nimport { crisp } from '../util/math';\nimport { morphedModel } from '../axes/morph';\nimport { addOverlayText } from './chrome';\nimport { spreadCenters } from './labelLayout';\nimport { contrastTarget, knownBackdrop, toneForContrast } from './legibility';\nimport { lerp, mixColor, switchAt } from './morphFrame';\n\n/**\n * Defaults that make a fit read as a derived overlay rather than another data\n * series: a lighter dashed stroke, slightly translucent.\n */\nconst DEFAULT_STROKE_WIDTH = 1.5;\nconst DEFAULT_STROKE_DASH = [6, 4];\nconst DEFAULT_OPACITY = 0.85;\n\n/** R\u00B2 badge geometry: a knockout pill in the plot background behind the text. */\nconst LABEL_PAD_X = 4;\nconst LABEL_PAD_Y = 1;\nconst LABEL_GAP = 6;\nconst TRENDLINES = 'trendlines';\n/** Stash name of the R\u00B2 badges (an entrance fades them in place; see chromeMotion's fadeChrome). */\nexport const TREND_LABELS = 'trendlines:labels';\n\n/** A fitted line as drawn, keyed by its group so a morph can carry it to the new fit. */\ninterface TrendSegment {\n key: string;\n color: string;\n x0: number;\n y0: number;\n x1: number;\n y1: number;\n alpha: number;\n /** Stroke style, kept so a line whose trendline was switched off can still fade out. */\n width: number;\n dash: number[];\n opacity: number;\n}\n\n/**\n * Pair two frames' items by key: shared ones interpolate via `mix`, new ones\n * fade in where they land, and gone ones fade out where they were (first, so\n * they paint beneath).\n */\nfunction blendByKey<T extends { key: string; alpha: number }>(from: readonly T[], to: readonly T[], t: number, mix: (a: T, b: T) => T): T[] {\n const before = new Map(from.map((item) => [item.key, item]));\n const now = new Set(to.map((item) => item.key));\n return [\n ...from.filter((item) => !now.has(item.key)).map((item) => ({ ...item, alpha: item.alpha * (1 - t) })),\n ...to.map((item) => {\n const prev = before.get(item.key);\n return prev ? { ...mix(prev, item), alpha: lerp(prev.alpha, item.alpha, t) } : { ...item, alpha: item.alpha * t };\n }),\n ];\n}\nconst TEXT_CONTRAST_MIN = 4.5;\n\ninterface ResolvedTrendline {\n method: 'linear';\n /** undefined = auto (per-group when the chart splits into series). */\n groupBy: boolean | undefined;\n label: boolean;\n color: string | undefined;\n strokeWidth: number;\n strokeDash: number[];\n /** Stroke opacity: translucent by default, opaque for an explicit color. */\n opacity: number;\n}\n\ninterface TrendGroup {\n /** The group's identity: its series key, or 'all' for a single overall fit. */\n key: string;\n color: string;\n fit: RegressionFit;\n xMin: number;\n xMax: number;\n}\n\n/** Resolve a `trendline` field (boolean | config) to a settled config, or null. */\nfunction resolveTrendline(value: unknown): ResolvedTrendline | null {\n if (!value) return null;\n const cfg = (typeof value === 'object' ? value : {}) as TrendlineConfig;\n return {\n method: 'linear',\n groupBy: cfg.groupBy,\n label: cfg.label ?? false,\n color: cfg.color,\n strokeWidth: cfg.strokeWidth ?? DEFAULT_STROKE_WIDTH,\n strokeDash: cfg.strokeDash ?? DEFAULT_STROKE_DASH,\n opacity: cfg.color === undefined ? DEFAULT_OPACITY : 1,\n };\n}\n\n/** Coerce an x value to its numeric domain position (epoch ms for time scales). */\nfunction xToNum(model: CartesianModel, value: unknown): number {\n if (model.x.kind === 'time') {\n const d = toDate(value);\n return d ? d.getTime() : NaN;\n }\n return toNumber(value);\n}\n\n/** Fit one regression per group (or a single overall fit) from the model rows. */\nfunction trendGroups(model: CartesianModel, cfg: ResolvedTrendline): TrendGroup[] {\n const readX = accessor(model.x.field);\n const readY = accessor(model.y.field ?? '');\n const grouped = cfg.groupBy ?? model.series.length > 1;\n const muted = cfg.color ?? model.tokens.color.textMuted;\n\n const buckets = grouped\n ? model.series.map((s) => ({ key: s.key, color: cfg.color ?? s.color, rows: s.rows }))\n : [{ key: 'all', color: muted, rows: model.series.flatMap((s) => s.rows) }];\n\n const out: TrendGroup[] = [];\n for (const bucket of buckets) {\n const xs: number[] = [];\n const ys: number[] = [];\n for (const row of bucket.rows) {\n const xn = xToNum(model, readX(row));\n const yn = toNumber(readY(row));\n if (!Number.isFinite(xn) || !Number.isFinite(yn)) continue;\n xs.push(xn);\n ys.push(yn);\n }\n const fit = linearRegression(xs, ys);\n if (!fit) continue;\n out.push({ key: bucket.key, color: bucket.color, fit, xMin: Math.min(...xs), xMax: Math.max(...xs) });\n }\n return out;\n}\n\n/** The pixel segment of each group's fit, skipping fits the scales can't place. */\nfunction trendSegments(model: CartesianModel, cfg: ResolvedTrendline, groups: readonly TrendGroup[]): TrendSegment[] {\n const out: TrendSegment[] = [];\n for (const g of groups) {\n const x0 = model.x.pixel(g.xMin);\n const x1 = model.x.pixel(g.xMax);\n const y0 = model.y.pixel(g.fit.predict(g.xMin));\n const y1 = model.y.pixel(g.fit.predict(g.xMax));\n if (\n x0 == null ||\n x1 == null ||\n !Number.isFinite(x0) ||\n !Number.isFinite(x1) ||\n !Number.isFinite(y0) ||\n !Number.isFinite(y1)\n ) {\n continue;\n }\n out.push({ key: g.key, color: g.color, x0, y0, x1, y1, alpha: 1, width: cfg.strokeWidth, dash: cfg.strokeDash, opacity: cfg.opacity });\n }\n return out;\n}\n\n/** Round caps would swallow the gaps of a dash pattern; keep them for solid lines and zero-length \"dot\" dashes. */\nfunction capFor(dash: readonly number[]): CanvasLineCap {\n const dashed = dash.some((n) => n > 0) && !dash.some((n) => n === 0);\n return dashed ? 'butt' : 'round';\n}\n\n/**\n * Stroke the fitted line(s) on the marks canvas, clipped to the plot. Call after\n * the chart's data marks so the trendline sits on top. No-op unless the spec\n * opts in via `trendline` and the x-axis is continuous/temporal. While a morph\n * is drawing, each group's line glides to its new fit (lines that appear or go\n * away fade); during an entrance the lines draw on once the marks are mostly in.\n */\nexport function drawTrendlines(surface: Surface, model: CartesianModel): void {\n const cfg = resolveTrendline((model.spec as { trendline?: unknown }).trendline);\n const usable = !!cfg && model.x.kind !== 'band' && model.x.kind !== 'point';\n const groups = usable ? trendGroups(model, cfg) : [];\n const segments = cfg ? trendSegments(model, cfg, groups) : [];\n const morph = chromeMorph<TrendSegment[]>(surface, TRENDLINES);\n if (morph) {\n const drawn = visibleItems(blendByKey(morph.from, segments, morph.t, (a, b) => ({\n ...b,\n x0: lerp(a.x0, b.x0, morph.t),\n y0: lerp(a.y0, b.y0, morph.t),\n x1: lerp(a.x1, b.x1, morph.t),\n y1: lerp(a.y1, b.y1, morph.t),\n color: mixColor(a.color, b.color, morph.t),\n })));\n stashChrome(surface, TRENDLINES, drawn);\n paintTrendSegments(surface.marks.ctx, morphedModel(surface, model).plot, drawn);\n return;\n }\n const entrance = chromeEntrance(surface);\n const reveal = entrance == null ? 1 : revealAt(entrance, 0.45, 1);\n // Mid-entrance, record how far the lines have drawn on, so an interrupting update fades on from there.\n stashChrome(surface, TRENDLINES, reveal < 1 ? segments.map((s) => ({ ...s, alpha: reveal })) : segments);\n if (!cfg || !usable || groups.length === 0) return;\n if (!(reveal > 0)) return;\n const ctx = surface.marks.ctx;\n const { plot } = model;\n\n ctx.save();\n ctx.beginPath();\n // Entering trendlines draw on from the left.\n ctx.rect(plot.x, plot.y, reveal < 1 ? plot.width * reveal : plot.width, plot.height);\n ctx.clip();\n ctx.lineWidth = cfg.strokeWidth;\n ctx.setLineDash(cfg.strokeDash);\n ctx.lineCap = capFor(cfg.strokeDash);\n ctx.globalAlpha = cfg.opacity;\n\n for (const s of segments) {\n ctx.strokeStyle = s.color;\n ctx.beginPath();\n ctx.moveTo(s.x0, crisp(s.y0));\n ctx.lineTo(s.x1, crisp(s.y1));\n ctx.stroke();\n }\n\n ctx.restore();\n}\n\n/** Paint morphing trendline segments, each at its own opacity, clipped to the (moving) plot. */\nfunction paintTrendSegments(ctx: CanvasRenderingContext2D, plot: Rect, segments: readonly TrendSegment[]): void {\n if (segments.length === 0) return;\n ctx.save();\n ctx.beginPath();\n ctx.rect(plot.x, plot.y, plot.width, plot.height);\n ctx.clip();\n for (const s of segments) {\n if (!(s.alpha > 0.002)) continue;\n ctx.lineWidth = s.width;\n ctx.setLineDash(s.dash);\n ctx.lineCap = capFor(s.dash);\n ctx.globalAlpha = s.opacity * Math.min(1, s.alpha);\n ctx.strokeStyle = s.color;\n ctx.beginPath();\n ctx.moveTo(s.x0, crisp(s.y0));\n ctx.lineTo(s.x1, crisp(s.y1));\n ctx.stroke();\n }\n ctx.restore();\n}\n\ninterface TrendLabel {\n /** The fit's group, so a morph can carry the badge to its new place. */\n key: string;\n text: string;\n color: string;\n /** Badge centre. */\n cx: number;\n cy: number;\n /** Badge footprint (text plus pill padding). */\n width: number;\n height: number;\n alpha: number;\n}\n\n/**\n * Place one `R\u00B2` badge per fit near its line end: past the end when there is\n * room, otherwise just inside it on the side away from the incoming line, so\n * the badge never sits on its own trendline.\n */\nfunction placeTrendLabel(\n model: CartesianModel,\n g: TrendGroup,\n font: string,\n size: number,\n color: string,\n): TrendLabel | null {\n const { plot } = model;\n const x0 = model.x.pixel(g.xMin);\n const x1 = model.x.pixel(g.xMax);\n const y0 = model.y.pixel(g.fit.predict(g.xMin));\n const y1 = model.y.pixel(g.fit.predict(g.xMax));\n if (x1 == null || !Number.isFinite(x1) || !Number.isFinite(y1)) return null;\n\n const text = `R\\u00B2=${g.fit.r2.toFixed(2)}`;\n const width = measureText(text, font).width + LABEL_PAD_X * 2;\n const height = Math.max(size + LABEL_PAD_Y * 2, size * 1.2);\n const right = plot.x + plot.width;\n let cx: number;\n let cy: number;\n if (x1 + LABEL_GAP + width <= right) {\n cx = x1 + LABEL_GAP + width / 2;\n cy = y1;\n } else {\n cx = x1 - LABEL_GAP - width / 2;\n const falling = x0 != null && Number.isFinite(x0) && Number.isFinite(y0) && y1 - y0 > 0.5;\n cy = falling ? y1 + 3 + height / 2 : y1 - 3 - height / 2;\n }\n const clampTo = (v: number, lo: number, hi: number) => (lo > hi ? (lo + hi) / 2 : Math.min(hi, Math.max(lo, v)));\n cx = clampTo(cx, plot.x + width / 2, right - width / 2);\n cy = clampTo(cy, plot.y + height / 2, plot.y + plot.height - height / 2);\n return { key: g.key, text, color, cx, cy, width, height, alpha: 1 };\n}\n\n/**\n * Stack badges that share horizontal space so none overlap: clusters of\n * horizontally overlapping badges are spread vertically within the plot, and a\n * cluster taller than the plot keeps only what fits.\n */\nfunction decollide(labels: TrendLabel[], plot: Rect): { kept: TrendLabel[]; dropped: number } {\n const ordered = [...labels].sort((a, b) => a.cx - a.width / 2 - (b.cx - b.width / 2));\n const clusters: TrendLabel[][] = [];\n let reach = -Infinity;\n for (const label of ordered) {\n const left = label.cx - label.width / 2;\n const rightEdge = label.cx + label.width / 2;\n if (clusters.length && left < reach + 2) {\n clusters[clusters.length - 1].push(label);\n reach = Math.max(reach, rightEdge);\n } else {\n clusters.push([label]);\n reach = rightEdge;\n }\n }\n const kept: TrendLabel[] = [];\n let dropped = 0;\n for (const cluster of clusters) {\n const rowH = Math.max(...cluster.map((l) => l.height)) + 2;\n const capacity = Math.max(1, Math.floor((plot.height + 2) / rowH));\n const fits = cluster.slice(0, capacity);\n dropped += cluster.length - fits.length;\n const centers = spreadCenters(fits.map((l) => l.cy), rowH, plot.y + rowH / 2 - 1, plot.y + plot.height - rowH / 2 + 1);\n fits.forEach((l, i) => kept.push({ ...l, cy: centers[i] }));\n }\n return { kept, dropped };\n}\n\n/**\n * Add `R\u00B2` badges for each fitted line to the HTML overlay (or paint them to\n * canvas when headless). Each sits on a knockout pill in the plot background,\n * in its series hue darkened/lightened just enough to read, and badges are\n * de-collided and recorded in the label ledger. Call after\n * `drawAnnotationLabels`. No-op unless `trendline.label` is set. Badges glide\n * with their lines during a morph; the returned elements (none when headless)\n * let an entrance hold them back until the lines have drawn on.\n */\nexport function drawTrendlineLabels(surface: Surface, model: CartesianModel): HTMLElement[] {\n const cfg = resolveTrendline((model.spec as { trendline?: unknown }).trendline);\n const usable = !!cfg && cfg.label && model.x.kind !== 'band' && model.x.kind !== 'point';\n const groups = usable ? trendGroups(model, cfg) : [];\n const morph = chromeMorph<TrendLabel[]>(surface, TREND_LABELS);\n if (groups.length === 0 && !morph) {\n stashChrome(surface, TREND_LABELS, []);\n return [];\n }\n\n const { tokens, plot } = model;\n const f = tokens.font;\n const size = f.size.small;\n const font = fontString(size, f.family, f.weight.medium);\n const backdrop = knownBackdrop(surface, tokens);\n const pillBackground = backdrop ? rgbaToCss(backdrop) : tokens.dark ? '#000000' : '#ffffff';\n const pillColor = parseColor(pillBackground);\n\n let kept: TrendLabel[] = [];\n if (groups.length > 0) {\n const labels: TrendLabel[] = [];\n const toward = contrastTarget(pillColor, tokens);\n for (const g of groups) {\n const color = toneForContrast(g.color, toward, pillColor, TEXT_CONTRAST_MIN);\n const label = placeTrendLabel(model, g, font, size, color);\n if (label) labels.push(label);\n }\n const placed = decollide(labels, plot);\n kept = placed.kept;\n surface.labels?.omit({ role: 'annotation', reason: 'space', required: false, count: placed.dropped, path: '/trendline/label' });\n }\n const drawn = morph\n ? visibleItems(blendByKey(morph.from, kept, morph.t, (a, b) => ({\n ...b,\n cx: lerp(a.cx, b.cx, morph.t),\n cy: lerp(a.cy, b.cy, morph.t),\n text: switchAt(a.text, b.text, morph.t),\n color: mixColor(a.color, b.color, morph.t),\n })))\n : kept;\n\n const entrance = chromeEntrance(surface);\n const reveal = entrance == null ? 1 : revealAt(entrance, 0.8, 1);\n stashChrome(surface, TREND_LABELS, reveal < 1 ? drawn.map((label) => ({ ...label, alpha: label.alpha * reveal })) : drawn);\n const elements: HTMLElement[] = [];\n for (const label of drawn) {\n const opacity = label.alpha * reveal;\n if (!(opacity > CHROME_VISIBLE)) continue;\n const el = addOverlayText(surface, tokens, {\n left: label.cx,\n top: label.cy,\n text: label.text,\n color: label.color,\n size,\n weight: f.weight.medium,\n transform: 'translate(-50%,-50%)',\n pill: { background: pillBackground, radius: 3, padX: LABEL_PAD_X, padY: LABEL_PAD_Y },\n role: 'annotation',\n path: '/trendline/label',\n ...(opacity < 1 ? { opacity } : {}),\n });\n if (el) elements.push(el);\n }\n return elements;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"waterfall.d.ts","sourceRoot":"","sources":["../../src/charts/waterfall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAQ7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAsK7C,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,aAAa,GACtB,gBAAgB,GAAG,IAAI,CAqEzB"}
1
+ {"version":3,"file":"waterfall.d.ts","sourceRoot":"","sources":["../../src/charts/waterfall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAS7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AA8O7C,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,aAAa,GACtB,gBAAgB,GAAG,IAAI,CAmGzB"}
@@ -1,3 +1,4 @@
1
+ import { FOCUS_VEIL, hoverFocusEnabled, paintCutoutVeil } from "../interaction/veil.js";
1
2
  import { roundedRect } from "../shape/index.js";
2
3
  import { RoughPen } from "../rough/index.js";
3
4
  import { drawAxesUnderlay, drawOverlay } from "../axes/index.js";
@@ -6,6 +7,58 @@ import { formatValue, formatNumber } from "../format/index.js";
6
7
  import { fontString, measureText } from "../render/text.js";
7
8
  import { buildWaterfallModel } from "../runtime/waterfall.js";
8
9
  import { rowAlpha } from "./emphasis.js";
10
+ import { toKey } from "../util/data.js";
11
+ import { lerp, lerpRect, lerpValue, mixColor, morphInput, morphKeyed, stashMorphFrame, switchAt } from "./morphFrame.js";
12
+ function waterfallMorphBars(model, emphAlpha) {
13
+ const y = model.base.y.scale;
14
+ return model.bars.map((bar) => ({
15
+ key: bar.sourceIndex === void 0 ? "\0total" : toKey(bar.stageValue),
16
+ bar,
17
+ baseY: y.map(bar.base),
18
+ endY: y.map(bar.cumulative),
19
+ emph: emphAlpha(bar),
20
+ alpha: 1
21
+ }));
22
+ }
23
+ function grownBar(item, k, alpha) {
24
+ const { rect } = item.bar;
25
+ return {
26
+ ...item,
27
+ bar: { ...item.bar, rect: { x: rect.x, width: rect.width, y: lerp(item.baseY, rect.y, k), height: rect.height * k } },
28
+ endY: lerp(item.baseY, item.endY, k),
29
+ alpha
30
+ };
31
+ }
32
+ const WATERFALL_MORPH = {
33
+ lerp: (a, b, t) => ({
34
+ key: b.key,
35
+ bar: {
36
+ ...b.bar,
37
+ rect: lerpRect(a.bar.rect, b.bar.rect, t),
38
+ color: mixColor(a.bar.color, b.bar.color, t),
39
+ delta: lerpValue(a.bar.delta, b.bar.delta, t),
40
+ displayValue: lerpValue(a.bar.displayValue, b.bar.displayValue, t),
41
+ cumulative: lerpValue(a.bar.cumulative, b.bar.cumulative, t),
42
+ kind: switchAt(a.bar.kind, b.bar.kind, t)
43
+ },
44
+ baseY: lerp(a.baseY, b.baseY, t),
45
+ endY: lerp(a.endY, b.endY, t),
46
+ emph: lerp(a.emph, b.emph, t),
47
+ alpha: lerp(a.alpha, b.alpha, t)
48
+ }),
49
+ enter: (b, t) => grownBar(b, t, t),
50
+ exit: (a, t) => grownBar(a, 1 - t, a.alpha * (1 - t))
51
+ };
52
+ function morphConnectors(items) {
53
+ const out = [];
54
+ for (let i = 0; i < items.length - 1; i++) {
55
+ const a = items[i].bar;
56
+ const b = items[i + 1].bar;
57
+ if (a.rect.width === 0 || b.rect.width === 0) continue;
58
+ out.push({ x1: a.rect.x + a.rect.width, x2: b.rect.x, y: items[i].endY });
59
+ }
60
+ return out;
61
+ }
9
62
  function drawConnectors(surface, model, alphaOf) {
10
63
  if (model.connectors.length === 0) return;
11
64
  const ctx = surface.marks.ctx;
@@ -147,11 +200,21 @@ function drawWaterfall(surface, spec, tokens, size, context) {
147
200
  surface.evidence?.data(represented, "derived");
148
201
  const emphasis = context?.emphasis ?? null;
149
202
  const stageField = waterfall.encoding.stage.field;
150
- const alphaOf = (bar) => bar.stageValue === void 0 ? 1 : rowAlpha(emphasis, { [stageField]: bar.stageValue });
203
+ const emphAlpha = (bar) => bar.stageValue === void 0 ? 1 : rowAlpha(emphasis, { [stageField]: bar.stageValue });
204
+ const finalBars = waterfallMorphBars(model, emphAlpha);
205
+ const morph = morphInput(context);
206
+ const moved = morph ? morphKeyed(morph.from, finalBars, morph.t, WATERFALL_MORPH) : null;
207
+ const alphas = /* @__PURE__ */ new Map();
208
+ for (const item of moved ? [...moved.exits, ...moved.items] : []) alphas.set(item.bar, item.emph * item.alpha);
209
+ stashMorphFrame(surface, "waterfall", moved ? [...moved.exits, ...moved.items] : finalBars);
210
+ const frame = moved ? { ...model, bars: [...moved.exits, ...moved.items].map((item) => item.bar) } : model;
211
+ const linked = moved ? { ...model, bars: moved.items.map((item) => item.bar), connectors: morphConnectors(moved.items) } : model;
212
+ const labelled = moved ? { ...model, bars: moved.items.filter((item) => item.alpha >= 0.5).map((item) => item.bar) } : model;
213
+ const alphaOf = (bar) => alphas.get(bar) ?? emphAlpha(bar);
151
214
  drawAxesUnderlay(surface, model.base);
152
- drawConnectors(surface, model, alphaOf);
153
- drawBars(surface, model, alphaOf);
154
- drawLabels(surface, model);
215
+ drawConnectors(surface, linked, alphaOf);
216
+ drawBars(surface, frame, alphaOf);
217
+ drawLabels(surface, labelled);
155
218
  drawOverlay(surface, model.base);
156
219
  if (model.bars.length === 0) {
157
220
  addOverlayText(surface, tokens, {
@@ -177,6 +240,12 @@ function drawWaterfall(surface, spec, tokens, size, context) {
177
240
  }
178
241
  return null;
179
242
  };
243
+ const veilColor = waterfall.background ?? tokens.color.background;
244
+ const focusEnabled = hoverFocusEnabled(waterfall);
245
+ const focusBar = (ictx, bar, strength) => {
246
+ if (!focusEnabled) return;
247
+ paintCutoutVeil(ictx, plot, veilColor, FOCUS_VEIL * strength, (c) => c.rect(bar.rect.x - 1, bar.rect.y - 1, bar.rect.width + 2, bar.rect.height + 2));
248
+ };
180
249
  return {
181
250
  region: { x: plot.x, y: plot.y, width: plot.width, height: plot.height },
182
251
  hitTest: (px, py) => {
@@ -190,7 +259,9 @@ function drawWaterfall(surface, spec, tokens, size, context) {
190
259
  key: bar.key,
191
260
  anchorX: bar.rect.x + bar.rect.width / 2,
192
261
  anchorY: bar.rect.y,
193
- content: { title: bar.label, rows }
262
+ content: { title: bar.label, rows },
263
+ paint: (ictx, state) => focusBar(ictx, bar, state.focus),
264
+ draw: (ictx) => focusBar(ictx, bar, 1)
194
265
  };
195
266
  },
196
267
  pick: (px, py) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/charts/waterfall.ts"],
4
- "sourcesContent": ["/**\n * Waterfall renderer.\n *\n * A custom renderer (it owns its layout): it resolves a {@link WaterfallModel},\n * paints the shared axis chrome via `drawAxesUnderlay` / `drawOverlay`, then draws\n * connector lines and one floating bar per stage. Increases, decreases, and total\n * bars are colored distinctly; per-bar value labels show the signed step (or the\n * absolute level for total bars).\n */\n\nimport type { Surface } from '../render/surface';\nimport type { Size } from '../types';\nimport type { ChartSpec, WaterfallSpec } from '../spec/types';\nimport type { ThemeTokens } from '../theme';\nimport type { InteractionModel } from '../interaction/types';\nimport { roundedRect } from '../shape';\nimport { RoughPen } from '../rough';\nimport { drawAxesUnderlay, drawOverlay } from '../axes';\nimport { addOverlayText } from './chrome';\nimport { formatValue, formatNumber } from '../format';\nimport { fontString, measureText } from '../render/text';\nimport { buildWaterfallModel, type WaterfallBar, type WaterfallModel } from '../runtime/waterfall';\nimport type { RenderContext } from './index';\nimport { rowAlpha } from './emphasis';\n\n/** Connector lines joining the top of one bar to the start of the next. */\nfunction drawConnectors(\n surface: Surface,\n model: WaterfallModel,\n alphaOf: (bar: WaterfallBar) => number,\n): void {\n if (model.connectors.length === 0) return;\n const ctx = surface.marks.ctx;\n const color = model.tokens.color.axis;\n\n if (model.sketch) {\n const pen = new RoughPen(ctx, model.sketch);\n for (let i = 0; i < model.connectors.length; i += 1) {\n const c = model.connectors[i];\n ctx.globalAlpha = alphaOf(model.bars[i]);\n pen.polyline(\n [\n { x: c.x1, y: c.y },\n { x: c.x2, y: c.y },\n ],\n { stroke: color },\n );\n ctx.globalAlpha = 1;\n }\n return;\n }\n\n ctx.save();\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.setLineDash([2, 2]);\n for (let i = 0; i < model.connectors.length; i += 1) {\n const c = model.connectors[i];\n const y = Math.round(c.y) + 0.5;\n ctx.globalAlpha = alphaOf(model.bars[i]);\n ctx.beginPath();\n ctx.moveTo(c.x1, y);\n ctx.lineTo(c.x2, y);\n ctx.stroke();\n ctx.globalAlpha = 1;\n }\n ctx.restore();\n}\n\n/** Paint the floating bars (rounded, colored by step direction / total). */\nfunction drawBars(\n surface: Surface,\n model: WaterfallModel,\n alphaOf: (bar: WaterfallBar) => number,\n): void {\n const ctx = surface.marks.ctx;\n const pen = model.sketch ? new RoughPen(ctx, model.sketch) : null;\n for (const bar of model.bars) {\n const { rect } = bar;\n if (rect.width <= 0 || rect.height <= 0) continue;\n surface.evidence?.mark(\n 'bar',\n rect.x,\n rect.y,\n rect.width,\n rect.height,\n bar.color,\n alphaOf(bar),\n undefined,\n 1,\n undefined,\n alphaOf(bar) < 1,\n );\n ctx.globalAlpha = alphaOf(bar);\n if (pen) {\n pen.rect(rect.x, rect.y, rect.width, rect.height, { stroke: bar.color, fill: bar.color });\n ctx.globalAlpha = 1;\n continue;\n }\n const r = Math.min(model.cornerRadius, rect.width / 2, rect.height / 2);\n ctx.beginPath();\n roundedRect(ctx, rect.x, rect.y, rect.width, rect.height, [r, r, r, r]);\n ctx.fillStyle = bar.color;\n ctx.fill();\n ctx.globalAlpha = 1;\n }\n}\n\n/** The signed value label for a bar (steps show \u00B1delta, totals show the level). */\nfunction barLabelText(bar: WaterfallBar, format?: string): string {\n if (bar.kind === 'total') {\n return format ? formatValue(bar.displayValue, format) : formatNumber(bar.displayValue, ',');\n }\n const body = format ? formatValue(bar.delta, format) : formatNumber(bar.delta, ',');\n // formatValue already carries a leading '-' for negatives; only add '+' for rises.\n return bar.delta > 0 ? `+${body}` : body;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n if (max < min) return min;\n return Math.max(min, Math.min(max, value));\n}\n\nfunction boxesOverlap(\n a: { x: number; y: number; width: number; height: number },\n b: { x: number; y: number; width: number; height: number },\n): boolean {\n return (\n a.x + a.width > b.x + 2 &&\n b.x + b.width > a.x + 2 &&\n a.y + a.height > b.y + 2 &&\n b.y + b.height > a.y + 2\n );\n}\n\n/** Per-bar value labels, centered just above each bar's top edge. */\nfunction drawLabels(surface: Surface, model: WaterfallModel): void {\n if (!model.showLabels) return;\n const { tokens, plot } = model;\n const size = tokens.font.size.tiny;\n const lineH = size * 1.2;\n const font = fontString(size, tokens.font.family, tokens.font.weight.medium);\n const placed: { x: number; y: number; width: number; height: number }[] = [];\n for (const bar of model.bars) {\n if (bar.rect.width <= 0 || bar.rect.height <= 0) continue;\n const text = barLabelText(bar, model.format);\n const textW = measureText(text, font).width;\n const budget = Math.min(Math.max(0, bar.rect.width + 12), Math.max(0, surface.width - 2));\n if (budget < 12 || (textW > budget && budget < textW * 0.5)) {\n surface.labels?.omit({\n role: 'data-label',\n reason: 'unreadable',\n required: false,\n example: text,\n path: '/labels',\n });\n continue;\n }\n const left = clamp(bar.rect.x + bar.rect.width / 2 - budget / 2, 1, surface.width - budget - 1);\n const above = bar.rect.y - lineH - 3;\n const top =\n above >= plot.y ? above : clamp(bar.rect.y + 2, plot.y, plot.y + plot.height - lineH);\n const box = { x: left, y: top, width: Math.min(textW, budget), height: lineH };\n if (placed.some((p) => boxesOverlap(p, box))) {\n surface.labels?.omit({\n role: 'data-label',\n reason: 'collision',\n required: false,\n example: text,\n path: '/labels',\n });\n continue;\n }\n placed.push(box);\n addOverlayText(surface, tokens, {\n left,\n top,\n width: budget,\n text,\n color: tokens.color.text,\n size,\n weight: tokens.font.weight.medium,\n align: 'center',\n maxWidth: budget,\n });\n }\n}\n\nexport function drawWaterfall(\n surface: Surface,\n spec: ChartSpec,\n tokens: ThemeTokens,\n size: Size,\n context?: RenderContext,\n): InteractionModel | void {\n surface.evidence?.begin('derived');\n const waterfall = spec as WaterfallSpec;\n const model = buildWaterfallModel(waterfall, tokens, size);\n const represented = model.bars.filter((bar) => bar.sourceIndex !== undefined).length;\n surface.evidence?.data(represented, 'derived');\n const emphasis = context?.emphasis ?? null;\n const stageField = waterfall.encoding.stage.field;\n const alphaOf = (bar: WaterfallBar): number =>\n bar.stageValue === undefined ? 1 : rowAlpha(emphasis, { [stageField]: bar.stageValue });\n\n drawAxesUnderlay(surface, model.base);\n drawConnectors(surface, model, alphaOf);\n drawBars(surface, model, alphaOf);\n drawLabels(surface, model);\n drawOverlay(surface, model.base);\n\n if (model.bars.length === 0) {\n addOverlayText(surface, tokens, {\n left: model.plot.x,\n top: model.plot.y + Math.max(0, (model.plot.height - tokens.font.size.small * 1.2) / 2),\n width: model.plot.width,\n text: 'No usable changes',\n color: tokens.color.textMuted,\n size: tokens.font.size.small,\n align: 'center',\n role: 'other',\n });\n return;\n }\n\n const { plot, format } = model;\n const valueLabel = waterfall.encoding.value.title ?? waterfall.encoding.value.field;\n const fmtNum = (v: number): string => (format ? formatValue(v, format) : formatNumber(v, ','));\n\n const hitBar = (px: number, py: number): WaterfallBar | null => {\n for (const bar of model.bars) {\n const r = bar.rect;\n if (r.width <= 0 || r.height <= 0) continue;\n if (px >= r.x && px <= r.x + r.width && py >= r.y && py <= r.y + r.height) return bar;\n }\n return null;\n };\n\n return {\n region: { x: plot.x, y: plot.y, width: plot.width, height: plot.height },\n hitTest: (px, py) => {\n const bar = hitBar(px, py);\n if (!bar) return null;\n const rows =\n bar.kind === 'total'\n ? [{ swatch: bar.color, label: valueLabel, value: fmtNum(bar.displayValue) }]\n : [\n { swatch: bar.color, label: 'change', value: barLabelText(bar, format) },\n { label: 'running total', value: fmtNum(bar.cumulative), muted: true },\n ];\n return {\n key: bar.key,\n anchorX: bar.rect.x + bar.rect.width / 2,\n anchorY: bar.rect.y,\n content: { title: bar.label, rows },\n };\n },\n pick: (px, py) => {\n const bar = hitBar(px, py);\n if (!bar || bar.stageValue === undefined) return null;\n return { kind: 'point', fields: [stageField], tuples: [[bar.stageValue]] };\n },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Waterfall renderer.\n *\n * A custom renderer (it owns its layout): it resolves a {@link WaterfallModel},\n * paints the shared axis chrome via `drawAxesUnderlay` / `drawOverlay`, then draws\n * connector lines and one floating bar per stage. Increases, decreases, and total\n * bars are colored distinctly; per-bar value labels show the signed step (or the\n * absolute level for total bars).\n */\n\nimport type { Surface } from '../render/surface';\nimport type { Size } from '../types';\nimport type { ChartSpec, WaterfallSpec } from '../spec/types';\nimport type { ThemeTokens } from '../theme';\nimport type { InteractionModel } from '../interaction/types';\nimport { FOCUS_VEIL, hoverFocusEnabled, paintCutoutVeil } from '../interaction/veil';\nimport { roundedRect } from '../shape';\nimport { RoughPen } from '../rough';\nimport { drawAxesUnderlay, drawOverlay } from '../axes';\nimport { addOverlayText } from './chrome';\nimport { formatValue, formatNumber } from '../format';\nimport { fontString, measureText } from '../render/text';\nimport { buildWaterfallModel, type WaterfallBar, type WaterfallConnector, type WaterfallModel } from '../runtime/waterfall';\nimport type { RenderContext } from './index';\nimport { rowAlpha } from './emphasis';\nimport { toKey } from '../util/data';\nimport { lerp, lerpRect, lerpValue, mixColor, morphInput, morphKeyed, stashMorphFrame, switchAt, type MorphPlan } from './morphFrame';\n\n/** A bar as drawn, keyed by stage so an update can carry it to its new level. */\ninterface WaterfallMorphBar {\n key: string;\n bar: WaterfallBar;\n /** Pixel y of the baseline edge a bar grows from / collapses onto. */\n baseY: number;\n /** Pixel y of the running level the bar ends at (where its connector leaves). */\n endY: number;\n /** Highlight alpha, carried so emphasis changes fade instead of snapping. */\n emph: number;\n /** Fade for bars arriving or leaving mid-morph. */\n alpha: number;\n}\n\nfunction waterfallMorphBars(model: WaterfallModel, emphAlpha: (bar: WaterfallBar) => number): WaterfallMorphBar[] {\n const y = model.base.y.scale;\n return model.bars.map((bar) => ({\n key: bar.sourceIndex === undefined ? '\\u0000total' : toKey(bar.stageValue),\n bar,\n baseY: y.map(bar.base),\n endY: y.map(bar.cumulative),\n emph: emphAlpha(bar),\n alpha: 1,\n }));\n}\n\n/** `item` grown to fraction `k` of its height, out of its baseline edge. */\nfunction grownBar(item: WaterfallMorphBar, k: number, alpha: number): WaterfallMorphBar {\n const { rect } = item.bar;\n return {\n ...item,\n bar: { ...item.bar, rect: { x: rect.x, width: rect.width, y: lerp(item.baseY, rect.y, k), height: rect.height * k } },\n endY: lerp(item.baseY, item.endY, k),\n alpha,\n };\n}\n\nconst WATERFALL_MORPH: MorphPlan<WaterfallMorphBar> = {\n lerp: (a, b, t) => ({\n key: b.key,\n bar: {\n ...b.bar,\n rect: lerpRect(a.bar.rect, b.bar.rect, t),\n color: mixColor(a.bar.color, b.bar.color, t),\n delta: lerpValue(a.bar.delta, b.bar.delta, t),\n displayValue: lerpValue(a.bar.displayValue, b.bar.displayValue, t),\n cumulative: lerpValue(a.bar.cumulative, b.bar.cumulative, t),\n kind: switchAt(a.bar.kind, b.bar.kind, t),\n },\n baseY: lerp(a.baseY, b.baseY, t),\n endY: lerp(a.endY, b.endY, t),\n emph: lerp(a.emph, b.emph, t),\n alpha: lerp(a.alpha, b.alpha, t),\n }),\n enter: (b, t) => grownBar(b, t, t),\n exit: (a, t) => grownBar(a, 1 - t, a.alpha * (1 - t)),\n};\n\n/** Connectors between consecutive bars of a morph frame, from their running levels. */\nfunction morphConnectors(items: readonly WaterfallMorphBar[]): WaterfallConnector[] {\n const out: WaterfallConnector[] = [];\n for (let i = 0; i < items.length - 1; i++) {\n const a = items[i].bar;\n const b = items[i + 1].bar;\n if (a.rect.width === 0 || b.rect.width === 0) continue;\n out.push({ x1: a.rect.x + a.rect.width, x2: b.rect.x, y: items[i].endY });\n }\n return out;\n}\n\n/** Connector lines joining the top of one bar to the start of the next. */\nfunction drawConnectors(\n surface: Surface,\n model: WaterfallModel,\n alphaOf: (bar: WaterfallBar) => number,\n): void {\n if (model.connectors.length === 0) return;\n const ctx = surface.marks.ctx;\n const color = model.tokens.color.axis;\n\n if (model.sketch) {\n const pen = new RoughPen(ctx, model.sketch);\n for (let i = 0; i < model.connectors.length; i += 1) {\n const c = model.connectors[i];\n ctx.globalAlpha = alphaOf(model.bars[i]);\n pen.polyline(\n [\n { x: c.x1, y: c.y },\n { x: c.x2, y: c.y },\n ],\n { stroke: color },\n );\n ctx.globalAlpha = 1;\n }\n return;\n }\n\n ctx.save();\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.setLineDash([2, 2]);\n for (let i = 0; i < model.connectors.length; i += 1) {\n const c = model.connectors[i];\n const y = Math.round(c.y) + 0.5;\n ctx.globalAlpha = alphaOf(model.bars[i]);\n ctx.beginPath();\n ctx.moveTo(c.x1, y);\n ctx.lineTo(c.x2, y);\n ctx.stroke();\n ctx.globalAlpha = 1;\n }\n ctx.restore();\n}\n\n/** Paint the floating bars (rounded, colored by step direction / total). */\nfunction drawBars(\n surface: Surface,\n model: WaterfallModel,\n alphaOf: (bar: WaterfallBar) => number,\n): void {\n const ctx = surface.marks.ctx;\n const pen = model.sketch ? new RoughPen(ctx, model.sketch) : null;\n for (const bar of model.bars) {\n const { rect } = bar;\n if (rect.width <= 0 || rect.height <= 0) continue;\n surface.evidence?.mark(\n 'bar',\n rect.x,\n rect.y,\n rect.width,\n rect.height,\n bar.color,\n alphaOf(bar),\n undefined,\n 1,\n undefined,\n alphaOf(bar) < 1,\n );\n ctx.globalAlpha = alphaOf(bar);\n if (pen) {\n pen.rect(rect.x, rect.y, rect.width, rect.height, { stroke: bar.color, fill: bar.color });\n ctx.globalAlpha = 1;\n continue;\n }\n const r = Math.min(model.cornerRadius, rect.width / 2, rect.height / 2);\n ctx.beginPath();\n roundedRect(ctx, rect.x, rect.y, rect.width, rect.height, [r, r, r, r]);\n ctx.fillStyle = bar.color;\n ctx.fill();\n ctx.globalAlpha = 1;\n }\n}\n\n/** The signed value label for a bar (steps show \u00B1delta, totals show the level). */\nfunction barLabelText(bar: WaterfallBar, format?: string): string {\n if (bar.kind === 'total') {\n return format ? formatValue(bar.displayValue, format) : formatNumber(bar.displayValue, ',');\n }\n const body = format ? formatValue(bar.delta, format) : formatNumber(bar.delta, ',');\n // formatValue already carries a leading '-' for negatives; only add '+' for rises.\n return bar.delta > 0 ? `+${body}` : body;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n if (max < min) return min;\n return Math.max(min, Math.min(max, value));\n}\n\nfunction boxesOverlap(\n a: { x: number; y: number; width: number; height: number },\n b: { x: number; y: number; width: number; height: number },\n): boolean {\n return (\n a.x + a.width > b.x + 2 &&\n b.x + b.width > a.x + 2 &&\n a.y + a.height > b.y + 2 &&\n b.y + b.height > a.y + 2\n );\n}\n\n/** Per-bar value labels, centered just above each bar's top edge. */\nfunction drawLabels(surface: Surface, model: WaterfallModel): void {\n if (!model.showLabels) return;\n const { tokens, plot } = model;\n const size = tokens.font.size.tiny;\n const lineH = size * 1.2;\n const font = fontString(size, tokens.font.family, tokens.font.weight.medium);\n const placed: { x: number; y: number; width: number; height: number }[] = [];\n for (const bar of model.bars) {\n if (bar.rect.width <= 0 || bar.rect.height <= 0) continue;\n const text = barLabelText(bar, model.format);\n const textW = measureText(text, font).width;\n const budget = Math.min(Math.max(0, bar.rect.width + 12), Math.max(0, surface.width - 2));\n if (budget < 12 || (textW > budget && budget < textW * 0.5)) {\n surface.labels?.omit({\n role: 'data-label',\n reason: 'unreadable',\n required: false,\n example: text,\n path: '/labels',\n });\n continue;\n }\n const left = clamp(bar.rect.x + bar.rect.width / 2 - budget / 2, 1, surface.width - budget - 1);\n const above = bar.rect.y - lineH - 3;\n const top =\n above >= plot.y ? above : clamp(bar.rect.y + 2, plot.y, plot.y + plot.height - lineH);\n const box = { x: left, y: top, width: Math.min(textW, budget), height: lineH };\n if (placed.some((p) => boxesOverlap(p, box))) {\n surface.labels?.omit({\n role: 'data-label',\n reason: 'collision',\n required: false,\n example: text,\n path: '/labels',\n });\n continue;\n }\n placed.push(box);\n addOverlayText(surface, tokens, {\n left,\n top,\n width: budget,\n text,\n color: tokens.color.text,\n size,\n weight: tokens.font.weight.medium,\n align: 'center',\n maxWidth: budget,\n });\n }\n}\n\nexport function drawWaterfall(\n surface: Surface,\n spec: ChartSpec,\n tokens: ThemeTokens,\n size: Size,\n context?: RenderContext,\n): InteractionModel | void {\n surface.evidence?.begin('derived');\n const waterfall = spec as WaterfallSpec;\n const model = buildWaterfallModel(waterfall, tokens, size);\n const represented = model.bars.filter((bar) => bar.sourceIndex !== undefined).length;\n surface.evidence?.data(represented, 'derived');\n const emphasis = context?.emphasis ?? null;\n const stageField = waterfall.encoding.stage.field;\n const emphAlpha = (bar: WaterfallBar): number =>\n bar.stageValue === undefined ? 1 : rowAlpha(emphasis, { [stageField]: bar.stageValue });\n // Update morph: bars travel from the ones on screen (new stages grow out of\n // their baseline, removed ones collapse onto it) over the final axes, and a\n // changed highlight fades rather than snapping.\n const finalBars = waterfallMorphBars(model, emphAlpha);\n const morph = morphInput<WaterfallMorphBar[]>(context);\n const moved = morph ? morphKeyed(morph.from, finalBars, morph.t, WATERFALL_MORPH) : null;\n const alphas = new Map<WaterfallBar, number>();\n for (const item of moved ? [...moved.exits, ...moved.items] : []) alphas.set(item.bar, item.emph * item.alpha);\n stashMorphFrame(surface, 'waterfall', moved ? [...moved.exits, ...moved.items] : finalBars);\n const frame: WaterfallModel = moved\n ? { ...model, bars: [...moved.exits, ...moved.items].map((item) => item.bar) }\n : model;\n // Connector i leaves bar i, so it reads that bar's opacity; leaving bars have none.\n const linked: WaterfallModel = moved\n ? { ...model, bars: moved.items.map((item) => item.bar), connectors: morphConnectors(moved.items) }\n : model;\n const labelled: WaterfallModel = moved\n ? { ...model, bars: moved.items.filter((item) => item.alpha >= 0.5).map((item) => item.bar) }\n : model;\n const alphaOf = (bar: WaterfallBar): number => alphas.get(bar) ?? emphAlpha(bar);\n\n drawAxesUnderlay(surface, model.base);\n drawConnectors(surface, linked, alphaOf);\n drawBars(surface, frame, alphaOf);\n drawLabels(surface, labelled);\n drawOverlay(surface, model.base);\n\n if (model.bars.length === 0) {\n addOverlayText(surface, tokens, {\n left: model.plot.x,\n top: model.plot.y + Math.max(0, (model.plot.height - tokens.font.size.small * 1.2) / 2),\n width: model.plot.width,\n text: 'No usable changes',\n color: tokens.color.textMuted,\n size: tokens.font.size.small,\n align: 'center',\n role: 'other',\n });\n return;\n }\n\n const { plot, format } = model;\n const valueLabel = waterfall.encoding.value.title ?? waterfall.encoding.value.field;\n const fmtNum = (v: number): string => (format ? formatValue(v, format) : formatNumber(v, ','));\n\n const hitBar = (px: number, py: number): WaterfallBar | null => {\n for (const bar of model.bars) {\n const r = bar.rect;\n if (r.width <= 0 || r.height <= 0) continue;\n if (px >= r.x && px <= r.x + r.width && py >= r.y && py <= r.y + r.height) return bar;\n }\n return null;\n };\n // Focus recedes every other step of the bridge; the hovered bar stays put.\n const veilColor = waterfall.background ?? tokens.color.background;\n const focusEnabled = hoverFocusEnabled(waterfall);\n const focusBar = (ictx: CanvasRenderingContext2D, bar: WaterfallBar, strength: number): void => {\n if (!focusEnabled) return;\n paintCutoutVeil(ictx, plot, veilColor, FOCUS_VEIL * strength, (c) =>\n c.rect(bar.rect.x - 1, bar.rect.y - 1, bar.rect.width + 2, bar.rect.height + 2));\n };\n\n return {\n region: { x: plot.x, y: plot.y, width: plot.width, height: plot.height },\n hitTest: (px, py) => {\n const bar = hitBar(px, py);\n if (!bar) return null;\n const rows =\n bar.kind === 'total'\n ? [{ swatch: bar.color, label: valueLabel, value: fmtNum(bar.displayValue) }]\n : [\n { swatch: bar.color, label: 'change', value: barLabelText(bar, format) },\n { label: 'running total', value: fmtNum(bar.cumulative), muted: true },\n ];\n return {\n key: bar.key,\n anchorX: bar.rect.x + bar.rect.width / 2,\n anchorY: bar.rect.y,\n content: { title: bar.label, rows },\n paint: (ictx, state) => focusBar(ictx, bar, state.focus),\n draw: (ictx) => focusBar(ictx, bar, 1),\n };\n },\n pick: (px, py) => {\n const bar = hitBar(px, py);\n if (!bar || bar.stageValue === undefined) return null;\n return { kind: 'point', fields: [stageField], tuples: [[bar.stageValue]] };\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }