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.
- package/dist/animation/animate.d.ts +6 -1
- package/dist/animation/animate.d.ts.map +1 -1
- package/dist/animation/animate.js +2 -2
- package/dist/animation/animate.js.map +2 -2
- package/dist/animation/env.d.ts +5 -0
- package/dist/animation/env.d.ts.map +1 -1
- package/dist/animation/env.js +4 -0
- package/dist/animation/env.js.map +2 -2
- package/dist/animation/index.d.ts +1 -0
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/animation/index.js +1 -0
- package/dist/animation/index.js.map +2 -2
- package/dist/animation/stagger.d.ts +13 -0
- package/dist/animation/stagger.d.ts.map +1 -0
- package/dist/animation/stagger.js +13 -0
- package/dist/animation/stagger.js.map +7 -0
- package/dist/axes/draw.d.ts +5 -1
- package/dist/axes/draw.d.ts.map +1 -1
- package/dist/axes/draw.js +13 -1
- package/dist/axes/draw.js.map +2 -2
- package/dist/axes/morph.d.ts +111 -0
- package/dist/axes/morph.d.ts.map +1 -0
- package/dist/axes/morph.js +207 -0
- package/dist/axes/morph.js.map +7 -0
- package/dist/axes/overlay.d.ts.map +1 -1
- package/dist/axes/overlay.js +88 -25
- package/dist/axes/overlay.js.map +2 -2
- package/dist/axes/text.d.ts +2 -0
- package/dist/axes/text.d.ts.map +1 -1
- package/dist/axes/text.js +1 -0
- package/dist/axes/text.js.map +2 -2
- package/dist/axes/tickLabels.d.ts +2 -0
- package/dist/axes/tickLabels.d.ts.map +1 -1
- package/dist/axes/tickLabels.js +2 -2
- package/dist/axes/tickLabels.js.map +2 -2
- package/dist/charts/annotations.d.ts +9 -3
- package/dist/charts/annotations.d.ts.map +1 -1
- package/dist/charts/annotations.js +82 -27
- package/dist/charts/annotations.js.map +2 -2
- package/dist/charts/area.d.ts +37 -1
- package/dist/charts/area.d.ts.map +1 -1
- package/dist/charts/area.js +35 -17
- package/dist/charts/area.js.map +2 -2
- package/dist/charts/bar.d.ts +53 -0
- package/dist/charts/bar.d.ts.map +1 -1
- package/dist/charts/bar.js +117 -83
- package/dist/charts/bar.js.map +2 -2
- package/dist/charts/box.d.ts +31 -0
- package/dist/charts/box.d.ts.map +1 -1
- package/dist/charts/box.js +93 -70
- package/dist/charts/box.js.map +2 -2
- package/dist/charts/bubbleMap.d.ts +20 -0
- package/dist/charts/bubbleMap.d.ts.map +1 -0
- package/dist/charts/bubbleMap.js +951 -0
- package/dist/charts/bubbleMap.js.map +7 -0
- package/dist/charts/bullet.d.ts.map +1 -1
- package/dist/charts/bullet.js +69 -51
- package/dist/charts/bullet.js.map +2 -2
- package/dist/charts/calendarHeatmap.d.ts.map +1 -1
- package/dist/charts/calendarHeatmap.js +56 -19
- package/dist/charts/calendarHeatmap.js.map +2 -2
- package/dist/charts/candlestick.d.ts +40 -0
- package/dist/charts/candlestick.d.ts.map +1 -0
- package/dist/charts/candlestick.js +325 -0
- package/dist/charts/candlestick.js.map +7 -0
- package/dist/charts/choropleth.d.ts.map +1 -1
- package/dist/charts/choropleth.js +46 -67
- package/dist/charts/choropleth.js.map +2 -2
- package/dist/charts/combo.d.ts.map +1 -1
- package/dist/charts/combo.js +127 -27
- package/dist/charts/combo.js.map +3 -3
- package/dist/charts/dumbbell.d.ts.map +1 -1
- package/dist/charts/dumbbell.js +100 -31
- package/dist/charts/dumbbell.js.map +2 -2
- package/dist/charts/funnel.d.ts.map +1 -1
- package/dist/charts/funnel.js +93 -28
- package/dist/charts/funnel.js.map +3 -3
- package/dist/charts/gantt.d.ts +22 -0
- package/dist/charts/gantt.d.ts.map +1 -0
- package/dist/charts/gantt.js +564 -0
- package/dist/charts/gantt.js.map +7 -0
- package/dist/charts/gauge.d.ts.map +1 -1
- package/dist/charts/gauge.js +30 -4
- package/dist/charts/gauge.js.map +2 -2
- package/dist/charts/geo.d.ts +87 -0
- package/dist/charts/geo.d.ts.map +1 -0
- package/dist/charts/geo.js +140 -0
- package/dist/charts/geo.js.map +7 -0
- package/dist/charts/geometry.d.ts +20 -0
- package/dist/charts/geometry.d.ts.map +1 -0
- package/dist/charts/geometry.js +25 -0
- package/dist/charts/geometry.js.map +7 -0
- package/dist/charts/heatmap.d.ts.map +1 -1
- package/dist/charts/heatmap.js +58 -49
- package/dist/charts/heatmap.js.map +2 -2
- package/dist/charts/histogram.d.ts +1 -1
- package/dist/charts/histogram.d.ts.map +1 -1
- package/dist/charts/histogram.js +49 -31
- package/dist/charts/histogram.js.map +2 -2
- package/dist/charts/index.d.ts +35 -1
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +12 -0
- package/dist/charts/index.js.map +2 -2
- package/dist/charts/legibility.d.ts +38 -0
- package/dist/charts/legibility.d.ts.map +1 -0
- package/dist/charts/legibility.js +72 -0
- package/dist/charts/legibility.js.map +7 -0
- package/dist/charts/line.d.ts +39 -1
- package/dist/charts/line.d.ts.map +1 -1
- package/dist/charts/line.js +48 -16
- package/dist/charts/line.js.map +2 -2
- package/dist/charts/matrix.d.ts +2 -1
- package/dist/charts/matrix.d.ts.map +1 -1
- package/dist/charts/matrix.js +481 -319
- package/dist/charts/matrix.js.map +3 -3
- package/dist/charts/morphFrame.d.ts +83 -0
- package/dist/charts/morphFrame.d.ts.map +1 -0
- package/dist/charts/morphFrame.js +128 -0
- package/dist/charts/morphFrame.js.map +7 -0
- package/dist/charts/pie.d.ts.map +1 -1
- package/dist/charts/pie.js +89 -26
- package/dist/charts/pie.js.map +2 -2
- package/dist/charts/pieState.d.ts +27 -0
- package/dist/charts/pieState.d.ts.map +1 -0
- package/dist/charts/pieState.js +55 -0
- package/dist/charts/pieState.js.map +7 -0
- package/dist/charts/radar.d.ts +62 -0
- package/dist/charts/radar.d.ts.map +1 -0
- package/dist/charts/radar.js +912 -0
- package/dist/charts/radar.js.map +7 -0
- package/dist/charts/radialBar.d.ts +60 -0
- package/dist/charts/radialBar.d.ts.map +1 -0
- package/dist/charts/radialBar.js +708 -0
- package/dist/charts/radialBar.js.map +7 -0
- package/dist/charts/sankey.d.ts.map +1 -1
- package/dist/charts/sankey.js +223 -57
- package/dist/charts/sankey.js.map +3 -3
- package/dist/charts/scatter.d.ts +31 -0
- package/dist/charts/scatter.d.ts.map +1 -1
- package/dist/charts/scatter.js +48 -12
- package/dist/charts/scatter.js.map +2 -2
- package/dist/charts/slope.d.ts.map +1 -1
- package/dist/charts/slope.js +55 -3
- package/dist/charts/slope.js.map +2 -2
- package/dist/charts/sunburst.d.ts +14 -0
- package/dist/charts/sunburst.d.ts.map +1 -0
- package/dist/charts/sunburst.js +885 -0
- package/dist/charts/sunburst.js.map +7 -0
- package/dist/charts/table.d.ts +2 -1
- package/dist/charts/table.d.ts.map +1 -1
- package/dist/charts/table.js +214 -224
- package/dist/charts/table.js.map +3 -3
- package/dist/charts/tableView/canvas.d.ts +16 -1
- package/dist/charts/tableView/canvas.d.ts.map +1 -1
- package/dist/charts/tableView/canvas.js +361 -252
- package/dist/charts/tableView/canvas.js.map +3 -3
- package/dist/charts/tableView/conditional.d.ts +66 -40
- package/dist/charts/tableView/conditional.d.ts.map +1 -1
- package/dist/charts/tableView/conditional.js +163 -256
- package/dist/charts/tableView/conditional.js.map +3 -3
- package/dist/charts/tableView/dom.d.ts +26 -2
- package/dist/charts/tableView/dom.d.ts.map +1 -1
- package/dist/charts/tableView/dom.js +609 -269
- package/dist/charts/tableView/dom.js.map +3 -3
- package/dist/charts/tableView/layout.d.ts +22 -0
- package/dist/charts/tableView/layout.d.ts.map +1 -0
- package/dist/charts/tableView/layout.js +246 -0
- package/dist/charts/tableView/layout.js.map +7 -0
- package/dist/charts/tableView/sketch.d.ts +3 -2
- package/dist/charts/tableView/sketch.d.ts.map +1 -1
- package/dist/charts/tableView/sketch.js +2 -2
- package/dist/charts/tableView/sketch.js.map +2 -2
- package/dist/charts/tableView/sketchBar.d.ts +7 -0
- package/dist/charts/tableView/sketchBar.d.ts.map +1 -0
- package/dist/charts/tableView/sketchBar.js +32 -0
- package/dist/charts/tableView/sketchBar.js.map +7 -0
- package/dist/charts/tableView/sort.d.ts +9 -0
- package/dist/charts/tableView/sort.d.ts.map +1 -0
- package/dist/charts/tableView/sort.js +70 -0
- package/dist/charts/tableView/sort.js.map +7 -0
- package/dist/charts/tableView/sparkline.d.ts +30 -0
- package/dist/charts/tableView/sparkline.d.ts.map +1 -0
- package/dist/charts/tableView/sparkline.js +63 -0
- package/dist/charts/tableView/sparkline.js.map +7 -0
- package/dist/charts/tableView/style.d.ts +67 -0
- package/dist/charts/tableView/style.d.ts.map +1 -0
- package/dist/charts/tableView/style.js +131 -0
- package/dist/charts/tableView/style.js.map +7 -0
- package/dist/charts/tableView/types.d.ts +69 -46
- package/dist/charts/tableView/types.d.ts.map +1 -1
- package/dist/charts/tableView/types.js +22 -87
- package/dist/charts/tableView/types.js.map +2 -2
- package/dist/charts/tableView.d.ts +10 -5
- package/dist/charts/tableView.d.ts.map +1 -1
- package/dist/charts/tableView.js +12 -8
- package/dist/charts/tableView.js.map +2 -2
- package/dist/charts/treemap.d.ts.map +1 -1
- package/dist/charts/treemap.js +177 -68
- package/dist/charts/treemap.js.map +3 -3
- package/dist/charts/trendline.d.ts +13 -5
- package/dist/charts/trendline.d.ts.map +1 -1
- package/dist/charts/trendline.js +205 -56
- package/dist/charts/trendline.js.map +2 -2
- package/dist/charts/waterfall.d.ts.map +1 -1
- package/dist/charts/waterfall.js +76 -5
- package/dist/charts/waterfall.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interaction/cartesian.d.ts +4 -1
- package/dist/interaction/cartesian.d.ts.map +1 -1
- package/dist/interaction/cartesian.js +104 -34
- package/dist/interaction/cartesian.js.map +2 -2
- package/dist/interaction/controller.d.ts +29 -1
- package/dist/interaction/controller.d.ts.map +1 -1
- package/dist/interaction/controller.js +131 -8
- package/dist/interaction/controller.js.map +2 -2
- package/dist/interaction/focus.d.ts +23 -0
- package/dist/interaction/focus.d.ts.map +1 -0
- package/dist/interaction/focus.js +65 -0
- package/dist/interaction/focus.js.map +7 -0
- package/dist/interaction/tooltip.d.ts +6 -0
- package/dist/interaction/tooltip.d.ts.map +1 -1
- package/dist/interaction/tooltip.js +41 -16
- package/dist/interaction/tooltip.js.map +2 -2
- package/dist/interaction/types.d.ts +19 -0
- package/dist/interaction/types.d.ts.map +1 -1
- package/dist/interaction/veil.d.ts +25 -0
- package/dist/interaction/veil.d.ts.map +1 -0
- package/dist/interaction/veil.js +35 -0
- package/dist/interaction/veil.js.map +7 -0
- package/dist/pivot/pivot.d.ts +18 -0
- package/dist/pivot/pivot.d.ts.map +1 -1
- package/dist/pivot/pivot.js +46 -34
- package/dist/pivot/pivot.js.map +2 -2
- package/dist/render/chromeMotion.d.ts +65 -0
- package/dist/render/chromeMotion.d.ts.map +1 -0
- package/dist/render/chromeMotion.js +79 -0
- package/dist/render/chromeMotion.js.map +7 -0
- package/dist/render/pathSample.d.ts +24 -0
- package/dist/render/pathSample.d.ts.map +1 -0
- package/dist/render/pathSample.js +76 -0
- package/dist/render/pathSample.js.map +7 -0
- package/dist/render/transient.d.ts +18 -0
- package/dist/render/transient.d.ts.map +1 -0
- package/dist/render/transient.js +19 -0
- package/dist/render/transient.js.map +7 -0
- package/dist/runtime/candlestick.d.ts +112 -0
- package/dist/runtime/candlestick.d.ts.map +1 -0
- package/dist/runtime/candlestick.js +531 -0
- package/dist/runtime/candlestick.js.map +7 -0
- package/dist/runtime/cartesian.d.ts +14 -3
- package/dist/runtime/cartesian.d.ts.map +1 -1
- package/dist/runtime/cartesian.js.map +2 -2
- package/dist/runtime/chartAnimation.d.ts +54 -7
- package/dist/runtime/chartAnimation.d.ts.map +1 -1
- package/dist/runtime/chartAnimation.js +146 -28
- package/dist/runtime/chartAnimation.js.map +2 -2
- package/dist/runtime/chartRuntime.d.ts +42 -0
- package/dist/runtime/chartRuntime.d.ts.map +1 -1
- package/dist/runtime/chartRuntime.js +196 -30
- package/dist/runtime/chartRuntime.js.map +2 -2
- package/dist/runtime/dashboardDom.d.ts.map +1 -1
- package/dist/runtime/dashboardDom.js +45 -0
- package/dist/runtime/dashboardDom.js.map +2 -2
- package/dist/runtime/facet.d.ts +1 -1
- package/dist/runtime/facet.d.ts.map +1 -1
- package/dist/runtime/gantt.d.ts +137 -0
- package/dist/runtime/gantt.d.ts.map +1 -0
- package/dist/runtime/gantt.js +647 -0
- package/dist/runtime/gantt.js.map +7 -0
- package/dist/runtime/morph.d.ts +33 -0
- package/dist/runtime/morph.d.ts.map +1 -0
- package/dist/runtime/morph.js +332 -0
- package/dist/runtime/morph.js.map +7 -0
- package/dist/runtime/radar.d.ts +75 -0
- package/dist/runtime/radar.d.ts.map +1 -0
- package/dist/runtime/radar.js +201 -0
- package/dist/runtime/radar.js.map +7 -0
- package/dist/runtime/radialBar.d.ts +81 -0
- package/dist/runtime/radialBar.d.ts.map +1 -0
- package/dist/runtime/radialBar.js +125 -0
- package/dist/runtime/radialBar.js.map +7 -0
- package/dist/runtime/report/constants.d.ts +2 -2
- package/dist/runtime/report/constants.d.ts.map +1 -1
- package/dist/runtime/runtimeShared.d.ts +3 -3
- package/dist/runtime/runtimeShared.d.ts.map +1 -1
- package/dist/runtime/sunburst.d.ts +74 -0
- package/dist/runtime/sunburst.d.ts.map +1 -0
- package/dist/runtime/sunburst.js +150 -0
- package/dist/runtime/sunburst.js.map +7 -0
- package/dist/runtime/wire.d.ts.map +1 -1
- package/dist/runtime/wire.js +3 -1
- package/dist/runtime/wire.js.map +2 -2
- package/dist/shape/arc.d.ts +15 -2
- package/dist/shape/arc.d.ts.map +1 -1
- package/dist/shape/arc.js +204 -32
- package/dist/shape/arc.js.map +2 -2
- package/dist/spec/chartDefinitions.d.ts +6 -0
- package/dist/spec/chartDefinitions.d.ts.map +1 -1
- package/dist/spec/chartDefinitions.js +7 -1
- package/dist/spec/chartDefinitions.js.map +2 -2
- package/dist/spec/templates.d.ts.map +1 -1
- package/dist/spec/templates.js +177 -2
- package/dist/spec/templates.js.map +2 -2
- package/dist/spec/types.d.ts +258 -6
- package/dist/spec/types.d.ts.map +1 -1
- package/dist/spec/types.js +6 -0
- package/dist/spec/types.js.map +2 -2
- package/dist/spec/validate/charts.js +147 -15
- package/dist/spec/validate/charts.js.map +2 -2
- package/dist/spec/validate/common.d.ts +6 -0
- package/dist/spec/validate/common.d.ts.map +1 -1
- package/dist/spec/validate/overlays.js +2 -2
- package/dist/spec/validate/overlays.js.map +2 -2
- package/dist/spec/validate.data.d.ts.map +1 -1
- package/dist/spec/validate.data.js +8 -3
- package/dist/spec/validate.data.js.map +2 -2
- 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
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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
|
|
219
|
-
const
|
|
220
|
-
byPosition.set(`${c.week},${c.weekday}`,
|
|
221
|
-
const
|
|
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,
|
|
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,
|
|
253
|
+
ctx.fillStyle = cell.fill;
|
|
254
|
+
drawRoundedCell(ctx, x, y, width, height, cell.radius);
|
|
231
255
|
}
|
|
232
|
-
const boundary = drawCellBoundary(ctx, x, y,
|
|
233
|
-
if (
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
|
|
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
|