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
@@ -9,6 +9,7 @@ import { addOverlayText, drawTitleBlock } from "./chrome.js";
9
9
  import { rowAlpha } from "./emphasis.js";
10
10
  import { drawCellBoundary } from "./boundary.js";
11
11
  import { drawNoData } from "./noData.js";
12
+ import { lerp, lerpRect, mixFill, morphInput, morphKeyed, rampAt, stashMorphFrame } from "./morphFrame.js";
12
13
  const DAY_MS = 864e5;
13
14
  const GAP = 2;
14
15
  const WEEKDAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
@@ -207,37 +208,60 @@ function buildCalendarLayout(grid, content, tokens) {
207
208
  staggerMonths
208
209
  };
209
210
  }
210
- function paintCalendarCells(surface, grid, layout, cscale, tokens, dateField, emphasis, pen) {
211
- const ctx = surface.marks.ctx;
211
+ const CALENDAR_MORPH = {
212
+ lerp: (a, b, t) => ({
213
+ key: b.key,
214
+ rect: lerpRect(a.rect, b.rect, t),
215
+ radius: lerp(a.radius, b.radius, t),
216
+ present: b.present,
217
+ ...mixFill(a, b, t),
218
+ emph: lerp(a.emph, b.emph, t),
219
+ alpha: lerp(a.alpha, b.alpha, t)
220
+ }),
221
+ enter: (b, t) => ({ ...b, alpha: t }),
222
+ exit: (a, t) => ({ ...a, alpha: a.alpha * (1 - t) })
223
+ };
224
+ function layoutCalendarCells(grid, layout, color, tokens, dateField, emphasis) {
212
225
  const byPosition = /* @__PURE__ */ new Map();
213
226
  const radius = Math.min(3, tokens.radius.sm, Math.min(layout.cellW, layout.cellH) / 3);
214
- ctx.save();
215
- for (const c of grid.cells) {
227
+ const cells = grid.cells.map((c) => {
216
228
  const x = layout.gridRect.x + c.week * layout.stepX;
217
229
  const y = layout.gridRect.y + c.weekday * layout.stepY;
218
- const fill = c.present && c.value !== void 0 ? rgbaToCss(cscale.map(c.value)) : tokens.color.surface;
219
- const laidCell = { ...c, x, y, w: layout.cellW, h: layout.cellH, fill };
220
- byPosition.set(`${c.week},${c.weekday}`, laidCell);
221
- const alpha = c.present ? rowAlpha(emphasis, c.row ?? { [dateField]: c.rawDate ?? c.isoDate }) : 1;
230
+ const value = c.present ? c.value : void 0;
231
+ const fill = value !== void 0 ? rgbaToCss(color.cscale.map(value)) : tokens.color.surface;
232
+ byPosition.set(`${c.week},${c.weekday}`, { ...c, x, y, w: layout.cellW, h: layout.cellH, fill });
233
+ const emph = c.present ? rowAlpha(emphasis, c.row ?? { [dateField]: c.rawDate ?? c.isoDate }) : 1;
234
+ const rect = { x, y, width: layout.cellW, height: layout.cellH };
235
+ const ramp = value !== void 0 ? { scheme: color.scheme, at: rampAt(color.domain, value) } : void 0;
236
+ return { key: c.isoDate, rect, radius, fill, present: c.present, ramp, emph, alpha: 1 };
237
+ });
238
+ return { cells, byPosition };
239
+ }
240
+ function paintCalendarCells(surface, cells, tokens, pen) {
241
+ const ctx = surface.marks.ctx;
242
+ ctx.save();
243
+ for (const cell of cells) {
244
+ const { x, y, width, height } = cell.rect;
245
+ const alpha = cell.emph * cell.alpha;
222
246
  ctx.globalAlpha = alpha;
223
247
  if (pen) {
224
- pen.rect(x, y, layout.cellW, layout.cellH, {
225
- fill,
248
+ pen.rect(x, y, width, height, {
249
+ fill: cell.fill,
226
250
  fillStyle: "solid"
227
251
  });
228
252
  } else {
229
- ctx.fillStyle = fill;
230
- drawRoundedCell(ctx, x, y, layout.cellW, layout.cellH, radius);
253
+ ctx.fillStyle = cell.fill;
254
+ drawRoundedCell(ctx, x, y, width, height, cell.radius);
231
255
  }
232
- const boundary = drawCellBoundary(ctx, x, y, layout.cellW, layout.cellH, radius, tokens, alpha, pen);
233
- if (c.present)
256
+ const boundary = drawCellBoundary(ctx, x, y, width, height, cell.radius, tokens, alpha, pen);
257
+ if (cell.present)
234
258
  surface.evidence?.mark(
235
259
  "cell",
236
260
  x,
237
261
  y,
238
- layout.cellW,
239
- layout.cellH,
240
- fill,
262
+ width,
263
+ height,
264
+ cell.fill,
241
265
  alpha,
242
266
  void 0,
243
267
  1,
@@ -248,7 +272,12 @@ function paintCalendarCells(surface, grid, layout, cscale, tokens, dateField, em
248
272
  ctx.globalAlpha = 1;
249
273
  }
250
274
  ctx.restore();
251
- return byPosition;
275
+ }
276
+ function drawLeavingDays(surface, tokens, context) {
277
+ const morph = morphInput(context);
278
+ const leaving = morph ? morphKeyed(morph.from, [], morph.t, CALENDAR_MORPH).exits : [];
279
+ stashMorphFrame(surface, "calendarHeatmap", leaving);
280
+ if (leaving.length) paintCalendarCells(surface, leaving, tokens, null);
252
281
  }
253
282
  function drawCalendarLabels(surface, tokens, content, layout) {
254
283
  const rowEnds = Array.from({ length: layout.staggerMonths ? 2 : 1 }, () => -Infinity);
@@ -350,11 +379,13 @@ function drawCalendarHeatmap(surface, spec, tokens, size, context) {
350
379
  const days = collectDays(cal);
351
380
  const grid = buildCalendarGrid(days);
352
381
  if (days.length === 0 || content.width <= 0 || content.height <= 0) {
382
+ drawLeavingDays(surface, tokens, context);
353
383
  drawNoData(surface, tokens, content, "No data", "aggregate");
354
384
  return;
355
385
  }
356
386
  const layout = buildCalendarLayout(grid, content, tokens);
357
387
  if (!layout) {
388
+ drawLeavingDays(surface, tokens, context);
358
389
  drawNoData(surface, tokens, content, "No data", "aggregate");
359
390
  return;
360
391
  }
@@ -368,7 +399,13 @@ function drawCalendarHeatmap(surface, spec, tokens, size, context) {
368
399
  days.reduce((n, d) => n + (d.sourceRows ?? 0), 0),
369
400
  "aggregate"
370
401
  );
371
- const byPosition = paintCalendarCells(surface, grid, layout, cscale, tokens, dateField, emphasis, pen);
402
+ const color = { cscale, domain: [min, max], scheme: cal.scheme ?? "teal" };
403
+ const { cells, byPosition } = layoutCalendarCells(grid, layout, color, tokens, dateField, emphasis);
404
+ const morph = morphInput(context);
405
+ const moved = morph && morphKeyed(morph.from, cells, morph.t, CALENDAR_MORPH);
406
+ const frame = moved ? [...moved.exits, ...moved.items] : cells;
407
+ stashMorphFrame(surface, "calendarHeatmap", frame);
408
+ paintCalendarCells(surface, frame, tokens, pen);
372
409
  drawCalendarLabels(surface, tokens, content, layout);
373
410
  return buildCalendarInteraction(cal, layout, byPosition);
374
411
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/charts/calendarHeatmap.ts"],
4
- "sourcesContent": ["import { sequential, sequentialColorScale, rgbaToCss } from '../color';\nimport { formatValue } from '../format';\nimport type { Surface } from '../render/surface';\nimport { fontString, measureText } from '../render/text';\nimport { RoughPen } from '../rough';\nimport { roundedRect } from '../shape';\nimport { resolveSketch } from '../spec/sketch';\nimport type { CalendarHeatmapSpec, ChartSpec } from '../spec/types';\nimport type { ThemeTokens } from '../theme';\nimport type { Datum, Rect, Size } from '../types';\nimport { accessor, extentOf, sumField, toDate, toNumber } from '../util/data';\nimport type { InteractionModel } from '../interaction/types';\nimport type { RenderContext } from './index';\nimport { addOverlayText, drawTitleBlock } from './chrome';\nimport { rowAlpha } from './emphasis';\nimport { drawCellBoundary } from './boundary';\nimport { drawNoData } from './noData';\n\nconst DAY_MS = 86_400_000;\nconst GAP = 2;\nconst WEEKDAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] as const;\nconst MONTH_LABELS = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n] as const;\n\nexport interface CalendarDayValue {\n isoDate: string;\n date: Date;\n value: number;\n row?: Datum;\n rawDate?: unknown;\n sourceRows?: number;\n}\n\nexport interface CalendarGridCell {\n isoDate: string;\n date: Date;\n week: number;\n weekday: number;\n value?: number;\n row?: Datum;\n rawDate?: unknown;\n present: boolean;\n}\n\nexport interface CalendarGrid {\n start: Date;\n end: Date;\n minDay: Date;\n maxDay: Date;\n weeks: number;\n cells: CalendarGridCell[];\n byIsoDate: Map<string, CalendarGridCell>;\n}\n\ninterface LaidOutCell extends CalendarGridCell {\n x: number;\n y: number;\n w: number;\n h: number;\n fill: string;\n}\n\nfunction pad2(n: number): string {\n return n < 10 ? `0${n}` : String(n);\n}\n\nfunction startOfLocalDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction isoDay(date: Date): string {\n return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;\n}\n\nfunction daySerial(date: Date): number {\n return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / DAY_MS;\n}\n\nfunction diffDays(a: Date, b: Date): number {\n return Math.round(daySerial(a) - daySerial(b));\n}\n\nfunction addDays(date: Date, days: number): Date {\n const d = startOfLocalDay(date);\n d.setDate(d.getDate() + days);\n return d;\n}\n\nfunction drawRoundedCell(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n w: number,\n h: number,\n radius: number,\n): void {\n ctx.beginPath();\n roundedRect(ctx, x, y, w, h, radius);\n ctx.fill();\n}\n\nfunction normalizeDays(\n days: ReadonlyMap<string, number> | readonly CalendarDayValue[],\n): CalendarDayValue[] {\n if (Array.isArray(days)) {\n return days\n .filter((d) => Number.isFinite(d.value) && toDate(d.date) !== null)\n .map((d) => ({\n isoDate: isoDay(d.date),\n date: startOfLocalDay(d.date),\n value: d.value,\n row: d.row,\n rawDate: d.rawDate,\n }));\n }\n const out: CalendarDayValue[] = [];\n const dayMap = days as ReadonlyMap<string, number>;\n for (const [isoDate, value] of dayMap) {\n const date = toDate(isoDate);\n if (!date || !Number.isFinite(value)) continue;\n out.push({ isoDate: isoDay(date), date: startOfLocalDay(date), value });\n }\n return out;\n}\n\nexport function buildCalendarGrid(\n days: ReadonlyMap<string, number> | readonly CalendarDayValue[],\n): CalendarGrid {\n const values = normalizeDays(days).sort((a, b) => daySerial(a.date) - daySerial(b.date));\n if (values.length === 0) {\n const today = startOfLocalDay(new Date(0));\n return {\n start: today,\n end: today,\n minDay: today,\n maxDay: today,\n weeks: 0,\n cells: [],\n byIsoDate: new Map(),\n };\n }\n\n const minDay = values[0].date;\n const maxDay = values[values.length - 1].date;\n const start = addDays(minDay, -minDay.getDay());\n const end = addDays(maxDay, 6 - maxDay.getDay());\n const weeks = Math.floor(diffDays(end, start) / 7) + 1;\n const byValue = new Map(values.map((d) => [d.isoDate, { value: d.value, row: d.row, rawDate: d.rawDate }]));\n const cells: CalendarGridCell[] = [];\n const byIsoDate = new Map<string, CalendarGridCell>();\n\n for (let d = start; diffDays(end, d) >= 0; d = addDays(d, 1)) {\n const isoDate = isoDay(d);\n const dayValue = byValue.get(isoDate);\n const cell: CalendarGridCell = {\n isoDate,\n date: d,\n week: Math.floor(diffDays(d, start) / 7),\n weekday: d.getDay(),\n value: dayValue?.value,\n row: dayValue?.row,\n rawDate: dayValue?.rawDate,\n present: dayValue !== undefined,\n };\n cells.push(cell);\n byIsoDate.set(isoDate, cell);\n }\n\n return { start, end, minDay, maxDay, weeks, cells, byIsoDate };\n}\n\nfunction collectDays(spec: CalendarHeatmapSpec): CalendarDayValue[] {\n const readDate = accessor(spec.encoding.date.field);\n const readColor = accessor(spec.encoding.color.field);\n const totals = new Map<string, { date: Date; rows: Datum[] }>();\n\n for (const row of spec.data ?? []) {\n const rawDate = readDate(row as Datum);\n const date = toDate(rawDate);\n const value = toNumber(readColor(row as Datum));\n if (!date || !Number.isFinite(value)) continue;\n const day = startOfLocalDay(date);\n const isoDate = isoDay(day);\n const current = totals.get(isoDate);\n if (current) current.rows.push(row);\n else totals.set(isoDate, { date: day, rows: [row] });\n }\n\n const days: CalendarDayValue[] = [];\n for (const [isoDate, { date, rows }] of totals) {\n const value = sumField(rows, spec.encoding.color.field);\n if (Number.isFinite(value))\n days.push({ isoDate, date, value, row: rows[0], rawDate: readDate(rows[0] as Datum), sourceRows: rows.length });\n }\n return days;\n}\n\nfunction monthAnchors(grid: CalendarGrid): { label: string; week: number }[] {\n const seen = new Set<string>();\n const out: { label: string; week: number }[] = [];\n for (let d = grid.minDay; diffDays(grid.maxDay, d) >= 0; d = addDays(d, 1)) {\n const key = `${d.getFullYear()}-${d.getMonth()}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push({ label: MONTH_LABELS[d.getMonth()], week: Math.floor(diffDays(d, grid.start) / 7) });\n }\n return out;\n}\n\nfunction humanDate(date: Date): string {\n return new Intl.DateTimeFormat('en-US', {\n weekday: 'short',\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n }).format(date);\n}\n\ninterface CalendarLayout {\n labelSize: number;\n labelFont: string;\n leftGutter: number;\n anchors: { label: string; week: number }[];\n monthWidths: number[];\n labelLineHeight: number;\n labelHeight: number;\n cellW: number;\n cellH: number;\n stepX: number;\n stepY: number;\n gridRect: Rect;\n staggerMonths: boolean;\n}\n\nfunction buildCalendarLayout(grid: CalendarGrid, content: Rect, tokens: ThemeTokens): CalendarLayout | null {\n const labelSize = tokens.font.size.tiny;\n const labelFont = fontString(labelSize, tokens.font.family, tokens.font.weight.normal);\n const leftGutter = Math.ceil(\n Math.max(...['Mon', 'Wed', 'Fri'].map((d) => measureText(d, labelFont).width)) +\n tokens.spacing.sm,\n );\n const anchors = monthAnchors(grid);\n const monthWidths = anchors.map((anchor) => measureText(anchor.label, labelFont).width);\n const labelLineHeight = Math.ceil(labelSize * 1.2) + 2;\n let labelHeight = Math.ceil(labelSize * 1.7) + 2;\n const availableW = Math.max(0, content.width - leftGutter);\n // Fill the container on both axes. Cells prefer to be square, but may stretch\n // (up to MAX_ASPECT:1) toward the roomier axis so a year of days uses the whole\n // extent instead of a thin centered band. No fixed cap \u2014 size scales with the card.\n const MAX_ASPECT = 1.75;\n const rawW = (availableW - GAP * Math.max(0, grid.weeks - 1)) / grid.weeks;\n const firstRawH = (Math.max(0, content.height - labelHeight) - GAP * 6) / 7;\n const firstStepX = Math.floor(Math.max(1, Math.min(rawW, firstRawH * MAX_ASPECT))) + GAP;\n const staggerMonths = anchors.some(\n (anchor, i) =>\n i > 0 && (anchor.week - anchors[i - 1].week) * firstStepX < monthWidths[i - 1] + 3,\n );\n if (staggerMonths) labelHeight += labelLineHeight;\n const availableH = Math.max(0, content.height - labelHeight);\n const rawH = (availableH - GAP * 6) / 7;\n const cellW = Math.floor(Math.max(1, Math.min(rawW, rawH * MAX_ASPECT)));\n const weekdayMinH = Math.ceil((labelSize * 1.2) / 2) - GAP;\n const cellH = Math.floor(Math.max(1, Math.min(rawH, Math.max(weekdayMinH, rawW * MAX_ASPECT))));\n\n if (!Number.isFinite(cellW) || !Number.isFinite(cellH) || cellW <= 0 || cellH <= 0) {\n return null;\n }\n\n const stepX = cellW + GAP;\n const stepY = cellH + GAP;\n const gridW = grid.weeks * cellW + Math.max(0, grid.weeks - 1) * GAP;\n const gridH = 7 * cellH + 6 * GAP;\n // Center the block in any leftover space so the labels + grid sit balanced.\n const vpad = Math.max(0, (availableH - gridH) / 2);\n const hpad = Math.max(0, (availableW - gridW) / 2);\n const gridRect: Rect = {\n x: content.x + leftGutter + hpad,\n y: content.y + labelHeight + vpad,\n width: gridW,\n height: gridH,\n };\n return { labelSize, labelFont, leftGutter, anchors, monthWidths, labelLineHeight,\n labelHeight, cellW, cellH, stepX, stepY, gridRect, staggerMonths };\n}\n\nfunction paintCalendarCells(\n surface: Surface,\n grid: CalendarGrid,\n layout: CalendarLayout,\n cscale: ReturnType<typeof sequentialColorScale>,\n tokens: ThemeTokens,\n dateField: string,\n emphasis: RenderContext['emphasis'] | null,\n pen: RoughPen | null,\n): Map<string, LaidOutCell> {\n const ctx = surface.marks.ctx;\n const byPosition = new Map<string, LaidOutCell>();\n const radius = Math.min(3, tokens.radius.sm, Math.min(layout.cellW, layout.cellH) / 3);\n\n ctx.save();\n for (const c of grid.cells) {\n const x = layout.gridRect.x + c.week * layout.stepX;\n const y = layout.gridRect.y + c.weekday * layout.stepY;\n const fill =\n c.present && c.value !== undefined ? rgbaToCss(cscale.map(c.value)) : tokens.color.surface;\n const laidCell = { ...c, x, y, w: layout.cellW, h: layout.cellH, fill };\n byPosition.set(`${c.week},${c.weekday}`, laidCell);\n\n const alpha = c.present ? rowAlpha(emphasis, c.row ?? { [dateField]: c.rawDate ?? c.isoDate }) : 1;\n ctx.globalAlpha = alpha;\n if (pen) {\n pen.rect(x, y, layout.cellW, layout.cellH, {\n fill,\n fillStyle: 'solid',\n });\n } else {\n ctx.fillStyle = fill;\n drawRoundedCell(ctx, x, y, layout.cellW, layout.cellH, radius);\n }\n const boundary = drawCellBoundary(ctx, x, y, layout.cellW, layout.cellH, radius, tokens, alpha, pen);\n if (c.present)\n surface.evidence?.mark(\n 'cell',\n x,\n y,\n layout.cellW,\n layout.cellH,\n fill,\n alpha,\n undefined,\n 1,\n undefined,\n alpha < 1,\n boundary,\n );\n ctx.globalAlpha = 1;\n }\n ctx.restore();\n return byPosition;\n}\n\nfunction drawCalendarLabels(surface: Surface, tokens: ThemeTokens, content: Rect, layout: CalendarLayout): void {\n const rowEnds = Array.from({ length: layout.staggerMonths ? 2 : 1 }, () => -Infinity);\n layout.anchors.forEach((m, i) => {\n const width = layout.monthWidths[i] + 1;\n const left = Math.max(\n content.x,\n Math.min(layout.gridRect.x + m.week * layout.stepX, content.x + content.width - width),\n );\n const row = rowEnds.findIndex((end) => left >= end + 2);\n if (row < 0) {\n surface.labels?.omit({\n role: 'axis-label',\n reason: 'collision',\n required: true,\n example: m.label,\n path: '/encoding/date',\n });\n return;\n }\n rowEnds[row] = left + width;\n addOverlayText(surface, tokens, {\n left,\n top: layout.gridRect.y - layout.labelHeight + row * layout.labelLineHeight,\n width,\n text: m.label,\n color: tokens.color.textMuted,\n size: layout.labelSize,\n role: 'axis-label',\n path: '/encoding/date',\n });\n });\n\n for (const weekday of [1, 3, 5]) {\n addOverlayText(surface, tokens, {\n left: content.x,\n top: layout.gridRect.y + weekday * layout.stepY + layout.cellH / 2,\n width: Math.max(0, layout.leftGutter - tokens.spacing.xs),\n text: WEEKDAY_LABELS[weekday],\n color: tokens.color.textMuted,\n size: layout.labelSize,\n align: 'right',\n transform: 'translateY(-50%)',\n role: 'axis-label',\n path: '/encoding/date',\n });\n }\n}\n\nfunction buildCalendarInteraction(\n cal: CalendarHeatmapSpec,\n layout: CalendarLayout,\n byPosition: ReadonlyMap<string, LaidOutCell>,\n): InteractionModel {\n const colorLabel = cal.encoding.color.title ?? cal.encoding.color.field;\n const hitCell = (px: number, py: number): LaidOutCell | null => {\n const { gridRect, stepX, stepY, cellW, cellH } = layout;\n if (px < gridRect.x || py < gridRect.y || px > gridRect.x + gridRect.width || py > gridRect.y + gridRect.height)\n return null;\n const week = Math.floor((px - gridRect.x) / stepX);\n const weekday = Math.floor((py - gridRect.y) / stepY);\n const x = gridRect.x + week * stepX;\n const y = gridRect.y + weekday * stepY;\n if (week < 0 || weekday < 0 || weekday > 6 || px > x + cellW || py > y + cellH)\n return null;\n const match = byPosition.get(`${week},${weekday}`);\n return match?.present ? match : null;\n };\n\n return {\n region: layout.gridRect,\n hitTest: (px, py) => {\n const c = hitCell(px, py);\n if (!c || c.value === undefined) return null;\n return {\n key: c.isoDate,\n anchorX: px,\n anchorY: py,\n content: {\n title: humanDate(c.date),\n rows: [\n {\n swatch: c.fill,\n label: colorLabel,\n value: formatValue(c.value, cal.encoding.color.format),\n },\n ],\n },\n };\n },\n pick: (px, py) => {\n const c = hitCell(px, py);\n if (!c) return null;\n return { kind: 'point', fields: [cal.encoding.date.field], tuples: [[c.rawDate ?? c.isoDate]] };\n },\n };\n}\n\nexport function drawCalendarHeatmap(\n surface: Surface,\n spec: ChartSpec,\n tokens: ThemeTokens,\n size: Size,\n context?: RenderContext,\n): InteractionModel | void {\n surface.evidence?.begin('aggregate');\n surface.evidence?.data(0, 'aggregate');\n const cal = spec as CalendarHeatmapSpec;\n const ctx = surface.marks.ctx;\n const emphasis = context?.emphasis ?? null;\n const dateField = cal.encoding.date.field;\n const content = drawTitleBlock(surface, tokens, size, cal.title);\n const days = collectDays(cal);\n const grid = buildCalendarGrid(days);\n\n if (days.length === 0 || content.width <= 0 || content.height <= 0) {\n drawNoData(surface, tokens, content, 'No data', 'aggregate');\n return;\n }\n\n const layout = buildCalendarLayout(grid, content, tokens);\n if (!layout) {\n drawNoData(surface, tokens, content, 'No data', 'aggregate');\n return;\n }\n\n const values = days.map((d) => d.value);\n const [min, max] = extentOf(values)!;\n const interp = sequential(cal.scheme ?? 'teal');\n const cscale = sequentialColorScale({ domain: [min, max], interpolator: interp });\n const sketch = resolveSketch(spec);\n const pen = sketch ? new RoughPen(ctx, sketch) : null;\n surface.evidence?.data(\n days.reduce((n, d) => n + (d.sourceRows ?? 0), 0),\n 'aggregate',\n );\n\n const byPosition = paintCalendarCells(surface, grid, layout, cscale, tokens, dateField, emphasis, pen);\n drawCalendarLabels(surface, tokens, content, layout);\n return buildCalendarInteraction(cal, layout, byPosition);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { sequential, sequentialColorScale, rgbaToCss } from '../color';\nimport { formatValue } from '../format';\nimport type { Surface } from '../render/surface';\nimport { fontString, measureText } from '../render/text';\nimport { RoughPen } from '../rough';\nimport { roundedRect } from '../shape';\nimport { resolveSketch } from '../spec/sketch';\nimport type { CalendarHeatmapSpec, ChartSpec } from '../spec/types';\nimport type { ThemeTokens } from '../theme';\nimport type { Datum, Rect, Size } from '../types';\nimport { accessor, extentOf, sumField, toDate, toNumber } from '../util/data';\nimport type { InteractionModel } from '../interaction/types';\nimport type { RenderContext } from './index';\nimport { addOverlayText, drawTitleBlock } from './chrome';\nimport { rowAlpha } from './emphasis';\nimport { drawCellBoundary } from './boundary';\nimport { drawNoData } from './noData';\nimport { lerp, lerpRect, mixFill, morphInput, morphKeyed, rampAt, stashMorphFrame, type MorphPlan, type RampFill } from './morphFrame';\n\nconst DAY_MS = 86_400_000;\nconst GAP = 2;\nconst WEEKDAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] as const;\nconst MONTH_LABELS = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n] as const;\n\nexport interface CalendarDayValue {\n isoDate: string;\n date: Date;\n value: number;\n row?: Datum;\n rawDate?: unknown;\n sourceRows?: number;\n}\n\nexport interface CalendarGridCell {\n isoDate: string;\n date: Date;\n week: number;\n weekday: number;\n value?: number;\n row?: Datum;\n rawDate?: unknown;\n present: boolean;\n}\n\nexport interface CalendarGrid {\n start: Date;\n end: Date;\n minDay: Date;\n maxDay: Date;\n weeks: number;\n cells: CalendarGridCell[];\n byIsoDate: Map<string, CalendarGridCell>;\n}\n\ninterface LaidOutCell extends CalendarGridCell {\n x: number;\n y: number;\n w: number;\n h: number;\n fill: string;\n}\n\nfunction pad2(n: number): string {\n return n < 10 ? `0${n}` : String(n);\n}\n\nfunction startOfLocalDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction isoDay(date: Date): string {\n return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;\n}\n\nfunction daySerial(date: Date): number {\n return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / DAY_MS;\n}\n\nfunction diffDays(a: Date, b: Date): number {\n return Math.round(daySerial(a) - daySerial(b));\n}\n\nfunction addDays(date: Date, days: number): Date {\n const d = startOfLocalDay(date);\n d.setDate(d.getDate() + days);\n return d;\n}\n\nfunction drawRoundedCell(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n w: number,\n h: number,\n radius: number,\n): void {\n ctx.beginPath();\n roundedRect(ctx, x, y, w, h, radius);\n ctx.fill();\n}\n\nfunction normalizeDays(\n days: ReadonlyMap<string, number> | readonly CalendarDayValue[],\n): CalendarDayValue[] {\n if (Array.isArray(days)) {\n return days\n .filter((d) => Number.isFinite(d.value) && toDate(d.date) !== null)\n .map((d) => ({\n isoDate: isoDay(d.date),\n date: startOfLocalDay(d.date),\n value: d.value,\n row: d.row,\n rawDate: d.rawDate,\n }));\n }\n const out: CalendarDayValue[] = [];\n const dayMap = days as ReadonlyMap<string, number>;\n for (const [isoDate, value] of dayMap) {\n const date = toDate(isoDate);\n if (!date || !Number.isFinite(value)) continue;\n out.push({ isoDate: isoDay(date), date: startOfLocalDay(date), value });\n }\n return out;\n}\n\nexport function buildCalendarGrid(\n days: ReadonlyMap<string, number> | readonly CalendarDayValue[],\n): CalendarGrid {\n const values = normalizeDays(days).sort((a, b) => daySerial(a.date) - daySerial(b.date));\n if (values.length === 0) {\n const today = startOfLocalDay(new Date(0));\n return {\n start: today,\n end: today,\n minDay: today,\n maxDay: today,\n weeks: 0,\n cells: [],\n byIsoDate: new Map(),\n };\n }\n\n const minDay = values[0].date;\n const maxDay = values[values.length - 1].date;\n const start = addDays(minDay, -minDay.getDay());\n const end = addDays(maxDay, 6 - maxDay.getDay());\n const weeks = Math.floor(diffDays(end, start) / 7) + 1;\n const byValue = new Map(values.map((d) => [d.isoDate, { value: d.value, row: d.row, rawDate: d.rawDate }]));\n const cells: CalendarGridCell[] = [];\n const byIsoDate = new Map<string, CalendarGridCell>();\n\n for (let d = start; diffDays(end, d) >= 0; d = addDays(d, 1)) {\n const isoDate = isoDay(d);\n const dayValue = byValue.get(isoDate);\n const cell: CalendarGridCell = {\n isoDate,\n date: d,\n week: Math.floor(diffDays(d, start) / 7),\n weekday: d.getDay(),\n value: dayValue?.value,\n row: dayValue?.row,\n rawDate: dayValue?.rawDate,\n present: dayValue !== undefined,\n };\n cells.push(cell);\n byIsoDate.set(isoDate, cell);\n }\n\n return { start, end, minDay, maxDay, weeks, cells, byIsoDate };\n}\n\nfunction collectDays(spec: CalendarHeatmapSpec): CalendarDayValue[] {\n const readDate = accessor(spec.encoding.date.field);\n const readColor = accessor(spec.encoding.color.field);\n const totals = new Map<string, { date: Date; rows: Datum[] }>();\n\n for (const row of spec.data ?? []) {\n const rawDate = readDate(row as Datum);\n const date = toDate(rawDate);\n const value = toNumber(readColor(row as Datum));\n if (!date || !Number.isFinite(value)) continue;\n const day = startOfLocalDay(date);\n const isoDate = isoDay(day);\n const current = totals.get(isoDate);\n if (current) current.rows.push(row);\n else totals.set(isoDate, { date: day, rows: [row] });\n }\n\n const days: CalendarDayValue[] = [];\n for (const [isoDate, { date, rows }] of totals) {\n const value = sumField(rows, spec.encoding.color.field);\n if (Number.isFinite(value))\n days.push({ isoDate, date, value, row: rows[0], rawDate: readDate(rows[0] as Datum), sourceRows: rows.length });\n }\n return days;\n}\n\nfunction monthAnchors(grid: CalendarGrid): { label: string; week: number }[] {\n const seen = new Set<string>();\n const out: { label: string; week: number }[] = [];\n for (let d = grid.minDay; diffDays(grid.maxDay, d) >= 0; d = addDays(d, 1)) {\n const key = `${d.getFullYear()}-${d.getMonth()}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push({ label: MONTH_LABELS[d.getMonth()], week: Math.floor(diffDays(d, grid.start) / 7) });\n }\n return out;\n}\n\nfunction humanDate(date: Date): string {\n return new Intl.DateTimeFormat('en-US', {\n weekday: 'short',\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n }).format(date);\n}\n\ninterface CalendarLayout {\n labelSize: number;\n labelFont: string;\n leftGutter: number;\n anchors: { label: string; week: number }[];\n monthWidths: number[];\n labelLineHeight: number;\n labelHeight: number;\n cellW: number;\n cellH: number;\n stepX: number;\n stepY: number;\n gridRect: Rect;\n staggerMonths: boolean;\n}\n\nfunction buildCalendarLayout(grid: CalendarGrid, content: Rect, tokens: ThemeTokens): CalendarLayout | null {\n const labelSize = tokens.font.size.tiny;\n const labelFont = fontString(labelSize, tokens.font.family, tokens.font.weight.normal);\n const leftGutter = Math.ceil(\n Math.max(...['Mon', 'Wed', 'Fri'].map((d) => measureText(d, labelFont).width)) +\n tokens.spacing.sm,\n );\n const anchors = monthAnchors(grid);\n const monthWidths = anchors.map((anchor) => measureText(anchor.label, labelFont).width);\n const labelLineHeight = Math.ceil(labelSize * 1.2) + 2;\n let labelHeight = Math.ceil(labelSize * 1.7) + 2;\n const availableW = Math.max(0, content.width - leftGutter);\n // Fill the container on both axes. Cells prefer to be square, but may stretch\n // (up to MAX_ASPECT:1) toward the roomier axis so a year of days uses the whole\n // extent instead of a thin centered band. No fixed cap \u2014 size scales with the card.\n const MAX_ASPECT = 1.75;\n const rawW = (availableW - GAP * Math.max(0, grid.weeks - 1)) / grid.weeks;\n const firstRawH = (Math.max(0, content.height - labelHeight) - GAP * 6) / 7;\n const firstStepX = Math.floor(Math.max(1, Math.min(rawW, firstRawH * MAX_ASPECT))) + GAP;\n const staggerMonths = anchors.some(\n (anchor, i) =>\n i > 0 && (anchor.week - anchors[i - 1].week) * firstStepX < monthWidths[i - 1] + 3,\n );\n if (staggerMonths) labelHeight += labelLineHeight;\n const availableH = Math.max(0, content.height - labelHeight);\n const rawH = (availableH - GAP * 6) / 7;\n const cellW = Math.floor(Math.max(1, Math.min(rawW, rawH * MAX_ASPECT)));\n const weekdayMinH = Math.ceil((labelSize * 1.2) / 2) - GAP;\n const cellH = Math.floor(Math.max(1, Math.min(rawH, Math.max(weekdayMinH, rawW * MAX_ASPECT))));\n\n if (!Number.isFinite(cellW) || !Number.isFinite(cellH) || cellW <= 0 || cellH <= 0) {\n return null;\n }\n\n const stepX = cellW + GAP;\n const stepY = cellH + GAP;\n const gridW = grid.weeks * cellW + Math.max(0, grid.weeks - 1) * GAP;\n const gridH = 7 * cellH + 6 * GAP;\n // Center the block in any leftover space so the labels + grid sit balanced.\n const vpad = Math.max(0, (availableH - gridH) / 2);\n const hpad = Math.max(0, (availableW - gridW) / 2);\n const gridRect: Rect = {\n x: content.x + leftGutter + hpad,\n y: content.y + labelHeight + vpad,\n width: gridW,\n height: gridH,\n };\n return { labelSize, labelFont, leftGutter, anchors, monthWidths, labelLineHeight,\n labelHeight, cellW, cellH, stepX, stepY, gridRect, staggerMonths };\n}\n\n/** A day as drawn, keyed by date so an update can carry it to its new color and place. */\ninterface CalendarMorphCell extends RampFill {\n key: string;\n rect: Rect;\n radius: number;\n /** Whether the day has a value (the rest pad out the grid's weeks). */\n present: boolean;\n /** Highlight alpha, carried so emphasis changes fade instead of snapping. */\n emph: number;\n /** Fade for days arriving or leaving mid-morph. */\n alpha: number;\n}\n\nconst CALENDAR_MORPH: MorphPlan<CalendarMorphCell> = {\n lerp: (a, b, t) => ({\n key: b.key,\n rect: lerpRect(a.rect, b.rect, t),\n radius: lerp(a.radius, b.radius, t),\n present: b.present,\n ...mixFill(a, b, t),\n emph: lerp(a.emph, b.emph, t),\n alpha: lerp(a.alpha, b.alpha, t),\n }),\n enter: (b, t) => ({ ...b, alpha: t }),\n exit: (a, t) => ({ ...a, alpha: a.alpha * (1 - t) }),\n};\n\nfunction layoutCalendarCells(\n grid: CalendarGrid,\n layout: CalendarLayout,\n color: { cscale: ReturnType<typeof sequentialColorScale>; domain: [number, number]; scheme: string },\n tokens: ThemeTokens,\n dateField: string,\n emphasis: RenderContext['emphasis'] | null,\n): { cells: CalendarMorphCell[]; byPosition: Map<string, LaidOutCell> } {\n const byPosition = new Map<string, LaidOutCell>();\n const radius = Math.min(3, tokens.radius.sm, Math.min(layout.cellW, layout.cellH) / 3);\n const cells = grid.cells.map((c): CalendarMorphCell => {\n const x = layout.gridRect.x + c.week * layout.stepX;\n const y = layout.gridRect.y + c.weekday * layout.stepY;\n const value = c.present ? c.value : undefined;\n const fill = value !== undefined ? rgbaToCss(color.cscale.map(value)) : tokens.color.surface;\n byPosition.set(`${c.week},${c.weekday}`, { ...c, x, y, w: layout.cellW, h: layout.cellH, fill });\n const emph = c.present ? rowAlpha(emphasis, c.row ?? { [dateField]: c.rawDate ?? c.isoDate }) : 1;\n const rect = { x, y, width: layout.cellW, height: layout.cellH };\n const ramp = value !== undefined ? { scheme: color.scheme, at: rampAt(color.domain, value) } : undefined;\n return { key: c.isoDate, rect, radius, fill, present: c.present, ramp, emph, alpha: 1 };\n });\n return { cells, byPosition };\n}\n\n/** Paint day cells at their highlight \u00D7 fade alpha. */\nfunction paintCalendarCells(\n surface: Surface,\n cells: readonly CalendarMorphCell[],\n tokens: ThemeTokens,\n pen: RoughPen | null,\n): void {\n const ctx = surface.marks.ctx;\n ctx.save();\n for (const cell of cells) {\n const { x, y, width, height } = cell.rect;\n const alpha = cell.emph * cell.alpha;\n ctx.globalAlpha = alpha;\n if (pen) {\n pen.rect(x, y, width, height, {\n fill: cell.fill,\n fillStyle: 'solid',\n });\n } else {\n ctx.fillStyle = cell.fill;\n drawRoundedCell(ctx, x, y, width, height, cell.radius);\n }\n const boundary = drawCellBoundary(ctx, x, y, width, height, cell.radius, tokens, alpha, pen);\n if (cell.present)\n surface.evidence?.mark(\n 'cell',\n x,\n y,\n width,\n height,\n cell.fill,\n alpha,\n undefined,\n 1,\n undefined,\n alpha < 1,\n boundary,\n );\n ctx.globalAlpha = 1;\n }\n ctx.restore();\n}\n\n/** No grid to draw: days on screen fade out, and a later update fades its days in. */\nfunction drawLeavingDays(surface: Surface, tokens: ThemeTokens, context?: RenderContext): void {\n const morph = morphInput<CalendarMorphCell[]>(context);\n const leaving = morph ? morphKeyed(morph.from, [], morph.t, CALENDAR_MORPH).exits : [];\n stashMorphFrame(surface, 'calendarHeatmap', leaving);\n if (leaving.length) paintCalendarCells(surface, leaving, tokens, null);\n}\n\nfunction drawCalendarLabels(surface: Surface, tokens: ThemeTokens, content: Rect, layout: CalendarLayout): void {\n const rowEnds = Array.from({ length: layout.staggerMonths ? 2 : 1 }, () => -Infinity);\n layout.anchors.forEach((m, i) => {\n const width = layout.monthWidths[i] + 1;\n const left = Math.max(\n content.x,\n Math.min(layout.gridRect.x + m.week * layout.stepX, content.x + content.width - width),\n );\n const row = rowEnds.findIndex((end) => left >= end + 2);\n if (row < 0) {\n surface.labels?.omit({\n role: 'axis-label',\n reason: 'collision',\n required: true,\n example: m.label,\n path: '/encoding/date',\n });\n return;\n }\n rowEnds[row] = left + width;\n addOverlayText(surface, tokens, {\n left,\n top: layout.gridRect.y - layout.labelHeight + row * layout.labelLineHeight,\n width,\n text: m.label,\n color: tokens.color.textMuted,\n size: layout.labelSize,\n role: 'axis-label',\n path: '/encoding/date',\n });\n });\n\n for (const weekday of [1, 3, 5]) {\n addOverlayText(surface, tokens, {\n left: content.x,\n top: layout.gridRect.y + weekday * layout.stepY + layout.cellH / 2,\n width: Math.max(0, layout.leftGutter - tokens.spacing.xs),\n text: WEEKDAY_LABELS[weekday],\n color: tokens.color.textMuted,\n size: layout.labelSize,\n align: 'right',\n transform: 'translateY(-50%)',\n role: 'axis-label',\n path: '/encoding/date',\n });\n }\n}\n\nfunction buildCalendarInteraction(\n cal: CalendarHeatmapSpec,\n layout: CalendarLayout,\n byPosition: ReadonlyMap<string, LaidOutCell>,\n): InteractionModel {\n const colorLabel = cal.encoding.color.title ?? cal.encoding.color.field;\n const hitCell = (px: number, py: number): LaidOutCell | null => {\n const { gridRect, stepX, stepY, cellW, cellH } = layout;\n if (px < gridRect.x || py < gridRect.y || px > gridRect.x + gridRect.width || py > gridRect.y + gridRect.height)\n return null;\n const week = Math.floor((px - gridRect.x) / stepX);\n const weekday = Math.floor((py - gridRect.y) / stepY);\n const x = gridRect.x + week * stepX;\n const y = gridRect.y + weekday * stepY;\n if (week < 0 || weekday < 0 || weekday > 6 || px > x + cellW || py > y + cellH)\n return null;\n const match = byPosition.get(`${week},${weekday}`);\n return match?.present ? match : null;\n };\n\n return {\n region: layout.gridRect,\n hitTest: (px, py) => {\n const c = hitCell(px, py);\n if (!c || c.value === undefined) return null;\n return {\n key: c.isoDate,\n anchorX: px,\n anchorY: py,\n content: {\n title: humanDate(c.date),\n rows: [\n {\n swatch: c.fill,\n label: colorLabel,\n value: formatValue(c.value, cal.encoding.color.format),\n },\n ],\n },\n };\n },\n pick: (px, py) => {\n const c = hitCell(px, py);\n if (!c) return null;\n return { kind: 'point', fields: [cal.encoding.date.field], tuples: [[c.rawDate ?? c.isoDate]] };\n },\n };\n}\n\nexport function drawCalendarHeatmap(\n surface: Surface,\n spec: ChartSpec,\n tokens: ThemeTokens,\n size: Size,\n context?: RenderContext,\n): InteractionModel | void {\n surface.evidence?.begin('aggregate');\n surface.evidence?.data(0, 'aggregate');\n const cal = spec as CalendarHeatmapSpec;\n const ctx = surface.marks.ctx;\n const emphasis = context?.emphasis ?? null;\n const dateField = cal.encoding.date.field;\n const content = drawTitleBlock(surface, tokens, size, cal.title);\n const days = collectDays(cal);\n const grid = buildCalendarGrid(days);\n\n if (days.length === 0 || content.width <= 0 || content.height <= 0) {\n drawLeavingDays(surface, tokens, context);\n drawNoData(surface, tokens, content, 'No data', 'aggregate');\n return;\n }\n\n const layout = buildCalendarLayout(grid, content, tokens);\n if (!layout) {\n drawLeavingDays(surface, tokens, context);\n drawNoData(surface, tokens, content, 'No data', 'aggregate');\n return;\n }\n\n const values = days.map((d) => d.value);\n const [min, max] = extentOf(values)!;\n const interp = sequential(cal.scheme ?? 'teal');\n const cscale = sequentialColorScale({ domain: [min, max], interpolator: interp });\n const sketch = resolveSketch(spec);\n const pen = sketch ? new RoughPen(ctx, sketch) : null;\n surface.evidence?.data(\n days.reduce((n, d) => n + (d.sourceRows ?? 0), 0),\n 'aggregate',\n );\n\n const color = { cscale, domain: [min, max] as [number, number], scheme: cal.scheme ?? 'teal' };\n const { cells, byPosition } = layoutCalendarCells(grid, layout, color, tokens, dateField, emphasis);\n // Update morph: days recolor (and glide when the grid re-flows) from the ones\n // on screen, days entering or leaving the range fade, and a changed highlight\n // fades rather than snapping. Month and weekday labels are the final frame's.\n const morph = morphInput<CalendarMorphCell[]>(context);\n const moved = morph && morphKeyed(morph.from, cells, morph.t, CALENDAR_MORPH);\n const frame = moved ? [...moved.exits, ...moved.items] : cells;\n stashMorphFrame(surface, 'calendarHeatmap', frame);\n paintCalendarCells(surface, frame, tokens, pen);\n drawCalendarLabels(surface, tokens, content, layout);\n return buildCalendarInteraction(cal, layout, byPosition);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Candlestick renderer.
3
+ *
4
+ * A custom renderer (it owns its layout): it resolves a {@link CandlestickModel},
5
+ * paints the shared axis chrome via `drawAxesUnderlay` / `drawOverlay`, then one
6
+ * candle per period — a crisp wick from the low to the high and a solid body from
7
+ * the open to the close in the up / down color, or with `style: 'ohlc'` a bar
8
+ * with an open tick on the left and a close tick on the right. Candles are
9
+ * batched by paint (one path per color), so a thousand periods stay cheap, and
10
+ * each one is recorded as render evidence. Hover focuses the period under the
11
+ * pointer; updates carry every candle to its new prices.
12
+ */
13
+ import type { Surface } from '../render/surface.js';
14
+ import type { Size } from '../types.js';
15
+ import type { ChartSpec } from '../spec/types.js';
16
+ import type { ThemeTokens } from '../theme/index.js';
17
+ import type { InteractionModel } from '../interaction/types.js';
18
+ import type { RenderContext } from './index.js';
19
+ /** A candle as drawn, keyed by period so an update can carry it to its new prices. */
20
+ export interface CandleMark {
21
+ key: string;
22
+ /** Pixel center (the wick / OHLC stem). */
23
+ cx: number;
24
+ /** Body width (or the OHLC tick span). */
25
+ width: number;
26
+ /** Wick / OHLC stroke width. */
27
+ stroke: number;
28
+ /** Pixel y of the high (top) and low (bottom) extremes. */
29
+ top: number;
30
+ bottom: number;
31
+ openY: number;
32
+ closeY: number;
33
+ color: string;
34
+ /** Highlight alpha, carried so emphasis changes fade instead of snapping. */
35
+ emph: number;
36
+ /** Fade for candles opening in, or leaving mid-morph. */
37
+ alpha: number;
38
+ }
39
+ export declare function drawCandlestick(surface: Surface, spec: ChartSpec, tokens: ThemeTokens, size: Size, context?: RenderContext): InteractionModel | void;
40
+ //# sourceMappingURL=candlestick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candlestick.d.ts","sourceRoot":"","sources":["../../src/charts/candlestick.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAQ,IAAI,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAA0B,gBAAgB,EAAc,MAAM,sBAAsB,CAAC;AAUjG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;CACf;AA0VD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,aAAa,GACtB,gBAAgB,GAAG,IAAI,CAqCzB"}
@@ -0,0 +1,325 @@
1
+ import { FOCUS_VEIL, hoverFocusEnabled, paintCutoutVeil } from "../interaction/veil.js";
2
+ import { RoughPen } from "../rough/index.js";
3
+ import { drawAxesUnderlay, drawOverlay } from "../axes/index.js";
4
+ import { formatNumber, formatValue } from "../format/index.js";
5
+ import { staggerProgress } from "../animation/stagger.js";
6
+ import { buildCandlestickModel } from "../runtime/candlestick.js";
7
+ import { drawAnnotationLabels, drawAnnotations } from "./annotations.js";
8
+ import { drawNoData } from "./noData.js";
9
+ import { rowAlpha } from "./emphasis.js";
10
+ import { lerp, mixColor, morphInput, morphKeyed, stashMorphFrame } from "./morphFrame.js";
11
+ const ENTRANCE_STAGGER = 0.4;
12
+ const clamp01 = (value) => value < 0 ? 0 : value > 1 ? 1 : value;
13
+ const easeOut = (value) => 1 - (1 - value) * (1 - value);
14
+ function markOf(candle, emph) {
15
+ return {
16
+ key: candle.key,
17
+ cx: candle.cx,
18
+ width: candle.width,
19
+ stroke: candle.stroke,
20
+ top: candle.top,
21
+ bottom: candle.bottom,
22
+ openY: candle.openY,
23
+ closeY: candle.closeY,
24
+ color: candle.color,
25
+ emph,
26
+ alpha: 1
27
+ };
28
+ }
29
+ function heldMark(mark, k, alpha) {
30
+ const mid = (mark.openY + mark.closeY) / 2;
31
+ return {
32
+ ...mark,
33
+ top: lerp(mid, mark.top, k),
34
+ bottom: lerp(mid, mark.bottom, k),
35
+ openY: lerp(mid, mark.openY, k),
36
+ closeY: lerp(mid, mark.closeY, k),
37
+ alpha
38
+ };
39
+ }
40
+ const CANDLE_MORPH = {
41
+ lerp: (a, b, t) => ({
42
+ key: b.key,
43
+ cx: lerp(a.cx, b.cx, t),
44
+ width: lerp(a.width, b.width, t),
45
+ stroke: lerp(a.stroke, b.stroke, t),
46
+ top: lerp(a.top, b.top, t),
47
+ bottom: lerp(a.bottom, b.bottom, t),
48
+ openY: lerp(a.openY, b.openY, t),
49
+ closeY: lerp(a.closeY, b.closeY, t),
50
+ // A candle whose direction flips passes through a doji while its color turns.
51
+ color: mixColor(a.color, b.color, t),
52
+ emph: lerp(a.emph, b.emph, t),
53
+ alpha: lerp(a.alpha, b.alpha, t)
54
+ }),
55
+ enter: (b, t) => heldMark(b, t, t),
56
+ exit: (a, t) => heldMark(a, 1 - t, a.alpha * (1 - t))
57
+ };
58
+ function openingMark(mark, k) {
59
+ if (k >= 1) return mark;
60
+ const body = easeOut(clamp01(k / 0.6));
61
+ const wick = easeOut(clamp01((k - 0.3) / 0.7));
62
+ const closeY = lerp(mark.openY, mark.closeY, body);
63
+ return {
64
+ ...mark,
65
+ closeY,
66
+ top: lerp(Math.min(mark.openY, closeY), mark.top, wick),
67
+ bottom: lerp(Math.max(mark.openY, closeY), mark.bottom, wick),
68
+ alpha: mark.alpha * clamp01(k / 0.2)
69
+ };
70
+ }
71
+ function enteringMarks(marks, t, wave) {
72
+ const n = marks.length;
73
+ const rank = new Array(n).fill(0);
74
+ if (wave) {
75
+ marks.map((_, i) => i).sort((a, b) => marks[a].cx - marks[b].cx || a - b).forEach((index, order) => {
76
+ rank[index] = order;
77
+ });
78
+ }
79
+ return marks.map((mark, i) => openingMark(mark, wave ? staggerProgress(t, rank[i], n, ENTRANCE_STAGGER) : t));
80
+ }
81
+ function ohlcTick(width) {
82
+ return Math.max(2, Math.round(width / 2));
83
+ }
84
+ function bodyOf(mark) {
85
+ const y0 = Math.min(mark.openY, mark.closeY);
86
+ const y1 = Math.max(mark.openY, mark.closeY);
87
+ return { y: y0, height: Math.max(1, y1 - y0) };
88
+ }
89
+ function halfExtent(mark, ohlc) {
90
+ return ohlc ? mark.stroke / 2 + ohlcTick(mark.width) : Math.max(mark.width, mark.stroke) / 2;
91
+ }
92
+ function paintGroups(marks) {
93
+ const groups = /* @__PURE__ */ new Map();
94
+ for (const mark of marks) {
95
+ const opacity = mark.emph * mark.alpha;
96
+ if (!(opacity > 0)) continue;
97
+ const key = `${mark.color}${opacity}${mark.stroke}`;
98
+ let group = groups.get(key);
99
+ if (!group) groups.set(key, group = { color: mark.color, opacity, stroke: mark.stroke, marks: [] });
100
+ group.marks.push(mark);
101
+ }
102
+ return [...groups.values()];
103
+ }
104
+ function paintCandleGroup(ctx, group) {
105
+ let wicks = false;
106
+ ctx.beginPath();
107
+ for (const mark of group.marks) {
108
+ const body = bodyOf(mark);
109
+ if (mark.top < body.y) {
110
+ ctx.moveTo(mark.cx, mark.top);
111
+ ctx.lineTo(mark.cx, body.y);
112
+ wicks = true;
113
+ }
114
+ if (mark.bottom > body.y + body.height) {
115
+ ctx.moveTo(mark.cx, body.y + body.height);
116
+ ctx.lineTo(mark.cx, mark.bottom);
117
+ wicks = true;
118
+ }
119
+ }
120
+ if (wicks) {
121
+ ctx.lineWidth = group.stroke;
122
+ ctx.strokeStyle = group.color;
123
+ ctx.stroke();
124
+ }
125
+ ctx.beginPath();
126
+ for (const mark of group.marks) {
127
+ const body = bodyOf(mark);
128
+ ctx.rect(mark.cx - mark.width / 2, body.y, mark.width, body.height);
129
+ }
130
+ ctx.fillStyle = group.color;
131
+ ctx.fill();
132
+ }
133
+ function paintOhlcGroup(ctx, group) {
134
+ ctx.beginPath();
135
+ for (const mark of group.marks) {
136
+ const half = mark.stroke / 2;
137
+ const tick = ohlcTick(mark.width);
138
+ ctx.moveTo(mark.cx, Math.min(mark.top, mark.openY, mark.closeY));
139
+ ctx.lineTo(mark.cx, Math.max(mark.bottom, mark.openY, mark.closeY));
140
+ ctx.moveTo(mark.cx - half - tick, mark.openY);
141
+ ctx.lineTo(mark.cx - half, mark.openY);
142
+ ctx.moveTo(mark.cx + half, mark.closeY);
143
+ ctx.lineTo(mark.cx + half + tick, mark.closeY);
144
+ }
145
+ ctx.lineWidth = group.stroke;
146
+ ctx.strokeStyle = group.color;
147
+ ctx.stroke();
148
+ }
149
+ function paintSketchCandles(ctx, model, marks) {
150
+ const pen = new RoughPen(ctx, model.sketch ?? {});
151
+ for (const mark of marks) {
152
+ const opacity = mark.emph * mark.alpha;
153
+ if (!(opacity > 0)) continue;
154
+ ctx.globalAlpha = opacity;
155
+ const stroke = { stroke: mark.color };
156
+ if (model.style === "ohlc") {
157
+ const half = mark.stroke / 2;
158
+ const tick = ohlcTick(mark.width);
159
+ pen.polyline([{ x: mark.cx, y: Math.min(mark.top, mark.openY, mark.closeY) }, { x: mark.cx, y: Math.max(mark.bottom, mark.openY, mark.closeY) }], stroke);
160
+ pen.polyline([{ x: mark.cx - half - tick, y: mark.openY }, { x: mark.cx - half, y: mark.openY }], stroke);
161
+ pen.polyline([{ x: mark.cx + half, y: mark.closeY }, { x: mark.cx + half + tick, y: mark.closeY }], stroke);
162
+ continue;
163
+ }
164
+ const body = bodyOf(mark);
165
+ if (mark.bottom - mark.top >= 1) pen.polyline([{ x: mark.cx, y: mark.top }, { x: mark.cx, y: mark.bottom }], stroke);
166
+ pen.rect(mark.cx - mark.width / 2, body.y, mark.width, body.height, { stroke: mark.color, fill: mark.color });
167
+ }
168
+ ctx.globalAlpha = 1;
169
+ }
170
+ function paintCandles(surface, model, marks) {
171
+ if (marks.length === 0) return;
172
+ const ctx = surface.marks.ctx;
173
+ const { plot } = model;
174
+ const clip = { x: plot.x - 1, y: plot.y - 1, width: plot.width + 2, height: plot.height + 2 };
175
+ const ohlc = model.style === "ohlc";
176
+ ctx.save();
177
+ ctx.beginPath();
178
+ ctx.rect(clip.x, clip.y, clip.width, clip.height);
179
+ ctx.clip();
180
+ ctx.lineCap = "butt";
181
+ if (model.sketch) paintSketchCandles(ctx, model, marks);
182
+ else {
183
+ for (const group of paintGroups(marks)) {
184
+ ctx.globalAlpha = group.opacity;
185
+ if (ohlc) paintOhlcGroup(ctx, group);
186
+ else paintCandleGroup(ctx, group);
187
+ }
188
+ ctx.globalAlpha = 1;
189
+ }
190
+ ctx.restore();
191
+ const evidence = surface.evidence;
192
+ if (!evidence) return;
193
+ for (const mark of marks) {
194
+ const opacity = mark.emph * mark.alpha;
195
+ if (!(opacity > 0)) continue;
196
+ const half = halfExtent(mark, ohlc);
197
+ const top = Math.min(mark.top, mark.openY, mark.closeY);
198
+ const bottom = Math.max(mark.bottom, mark.openY, mark.closeY, top + 1);
199
+ evidence.mark("box", mark.cx - half, top, half * 2, bottom - top, mark.color, opacity, clip, 1, void 0, mark.emph < 1);
200
+ }
201
+ }
202
+ function tooltipEnabled(spec) {
203
+ const tooltip = spec.tooltip;
204
+ return !(tooltip === false || tooltip && typeof tooltip === "object" && tooltip.show === false);
205
+ }
206
+ function formatPrice(value, format) {
207
+ return format ? formatValue(value, format) : formatNumber(value, ",");
208
+ }
209
+ function changeText(candle, format) {
210
+ const change = candle.close - candle.open;
211
+ const sign = change > 0 ? "+" : change < 0 ? "-" : "";
212
+ const magnitude = formatPrice(Math.abs(change), format);
213
+ const pct = candle.open !== 0 ? change / Math.abs(candle.open) * 100 : NaN;
214
+ const share = Number.isFinite(pct) ? ` (${sign}${Math.abs(pct).toFixed(2)}%)` : "";
215
+ return `${sign}${magnitude}${share}`;
216
+ }
217
+ function tooltipRows(candle, spec, format) {
218
+ const enc = spec.encoding;
219
+ return [
220
+ { label: enc.open.title ?? "Open", value: formatPrice(candle.open, format) },
221
+ { label: enc.high.title ?? "High", value: formatPrice(candle.high, format) },
222
+ { label: enc.low.title ?? "Low", value: formatPrice(candle.low, format) },
223
+ { label: enc.close.title ?? "Close", value: formatPrice(candle.close, format) },
224
+ { swatch: candle.color, label: "Change", value: changeText(candle, format), strong: true }
225
+ ];
226
+ }
227
+ const FOCUS_FRAMES = /* @__PURE__ */ new WeakMap();
228
+ function buildCandlestickInteraction(model, spec, tokens) {
229
+ const { plot, candles } = model;
230
+ if (candles.length === 0) return void 0;
231
+ const xField = spec.encoding.x.field;
232
+ const ohlc = model.style === "ohlc";
233
+ const order = candles.map((_, i) => i).sort((a, b) => candles[a].cx - candles[b].cx || a - b);
234
+ const centers = order.map((i) => candles[i].cx);
235
+ const nearest = (px) => {
236
+ let lo = 0;
237
+ let hi = centers.length - 1;
238
+ while (lo < hi) {
239
+ const mid = lo + hi >> 1;
240
+ if (centers[mid] < px) lo = mid + 1;
241
+ else hi = mid;
242
+ }
243
+ if (lo > 0 && Math.abs(centers[lo - 1] - px) <= Math.abs(centers[lo] - px)) lo -= 1;
244
+ return candles[order[lo]];
245
+ };
246
+ const within = (px, py) => px >= plot.x - 6 && px <= plot.x + plot.width + 6 && py >= plot.y - 6 && py <= plot.y + plot.height + 6;
247
+ const showTooltip = tooltipEnabled(spec);
248
+ const focusEnabled = hoverFocusEnabled(spec);
249
+ const veilColor = spec.background ?? tokens.color.background;
250
+ const cutout = (candle) => {
251
+ const half = Math.max(3, halfExtent(markOf(candle, 1), ohlc) + 2);
252
+ const top = Math.min(candle.top, candle.openY, candle.closeY) - 3;
253
+ const bottom = Math.max(candle.bottom, candle.openY, candle.closeY) + 4;
254
+ const x0 = Math.max(plot.x, candle.cx - half);
255
+ const x1 = Math.min(plot.x + plot.width, candle.cx + half);
256
+ const y0 = Math.max(plot.y, top);
257
+ const y1 = Math.min(plot.y + plot.height, bottom);
258
+ return { x: x0, y: y0, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };
259
+ };
260
+ const focus = (ictx, hover, candle, state) => {
261
+ if (!focusEnabled) return;
262
+ const target = cutout(candle);
263
+ const previous = state.from ? FOCUS_FRAMES.get(state.from) : void 0;
264
+ const rect = previous ? {
265
+ x: lerp(previous.x, target.x, state.glide),
266
+ y: lerp(previous.y, target.y, state.glide),
267
+ width: lerp(previous.width, target.width, state.glide),
268
+ height: lerp(previous.height, target.height, state.glide)
269
+ } : target;
270
+ FOCUS_FRAMES.set(hover, rect);
271
+ paintCutoutVeil(ictx, plot, veilColor, FOCUS_VEIL * state.focus, (c) => c.rect(rect.x, rect.y, rect.width, rect.height));
272
+ };
273
+ return {
274
+ region: { x: plot.x, y: plot.y, width: plot.width, height: plot.height },
275
+ hitTest: (px, py) => {
276
+ if (!showTooltip || !within(px, py)) return null;
277
+ const candle = nearest(px);
278
+ const hover = {
279
+ key: `candle:${candle.sourceIndex}`,
280
+ anchorX: candle.cx,
281
+ anchorY: Math.max(plot.y, Math.min(candle.top, plot.y + plot.height)),
282
+ content: { title: candle.label, rows: tooltipRows(candle, spec, model.priceFormat) }
283
+ };
284
+ hover.paint = (ictx, state) => focus(ictx, hover, candle, state);
285
+ hover.draw = (ictx) => focus(ictx, hover, candle, { focus: 1, glide: 1 });
286
+ return hover;
287
+ },
288
+ pick: (px, py) => {
289
+ if (!within(px, py)) return null;
290
+ return { kind: "point", fields: [xField], tuples: [[nearest(px).x]] };
291
+ }
292
+ };
293
+ }
294
+ function drawCandlestick(surface, spec, tokens, size, context) {
295
+ surface.evidence?.begin("rows");
296
+ const candlestick = spec;
297
+ const model = buildCandlestickModel(candlestick, tokens, size);
298
+ surface.evidence?.data(model.candles.length, "rows");
299
+ const emphasis = context?.emphasis ?? null;
300
+ const finals = model.candles.map((candle) => markOf(candle, rowAlpha(emphasis, candle.row)));
301
+ const morph = morphInput(context);
302
+ const moved = morph ? morphKeyed(morph.from, finals, morph.t, CANDLE_MORPH) : null;
303
+ const progress = context?.entranceProgress;
304
+ const frame = moved ? [...moved.exits, ...moved.items] : progress != null && progress < 1 ? enteringMarks(finals, Math.max(0, progress), finals.length >= 3 && !model.sketch) : finals;
305
+ stashMorphFrame(surface, "candlestick", frame);
306
+ drawAxesUnderlay(surface, model.base);
307
+ paintCandles(surface, model, frame);
308
+ if (model.candles.length === 0) {
309
+ drawOverlay(surface, model.base);
310
+ const rows = Array.isArray(candlestick.data) ? candlestick.data.length : 0;
311
+ drawNoData(surface, tokens, model.plot, rows === 0 ? "No data" : "No usable OHLC data");
312
+ return;
313
+ }
314
+ drawAnnotations(surface, model.base);
315
+ drawOverlay(surface, model.base);
316
+ if (model.thinnedTicks > 0 && candlestick.axes?.x?.show !== false && candlestick.axes?.x?.labels !== false) {
317
+ surface.labels?.omit({ role: "axis-label", reason: "collision", required: false, count: model.thinnedTicks, axis: "x", path: "/axes/x" });
318
+ }
319
+ drawAnnotationLabels(surface, model.base);
320
+ return buildCandlestickInteraction(model, candlestick, tokens);
321
+ }
322
+ export {
323
+ drawCandlestick
324
+ };
325
+ //# sourceMappingURL=candlestick.js.map