@voila.dev/chart 5.0.114 → 5.0.118

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 (86) hide show
  1. package/dist/canvas/paint-scene.d.ts.map +1 -1
  2. package/dist/canvas/paint-scene.js +3 -1
  3. package/dist/canvas/paint-scene.js.map +2 -2
  4. package/dist/contour/density.d.ts.map +1 -1
  5. package/dist/contour/density.js +2 -0
  6. package/dist/contour/density.js.map +2 -2
  7. package/dist/core/marks/area.d.ts.map +1 -1
  8. package/dist/core/marks/area.js +27 -11
  9. package/dist/core/marks/area.js.map +2 -2
  10. package/dist/core/marks/bar.d.ts.map +1 -1
  11. package/dist/core/marks/bar.js +2 -0
  12. package/dist/core/marks/bar.js.map +2 -2
  13. package/dist/core/marks/line.d.ts.map +1 -1
  14. package/dist/core/marks/line.js +13 -4
  15. package/dist/core/marks/line.js.map +2 -2
  16. package/dist/core/marks/polar.d.ts.map +1 -1
  17. package/dist/core/marks/polar.js +22 -5
  18. package/dist/core/marks/polar.js.map +2 -2
  19. package/dist/core/marks/shared.d.ts +8 -1
  20. package/dist/core/marks/shared.d.ts.map +1 -1
  21. package/dist/core/marks/shared.js.map +2 -2
  22. package/dist/core/marks/stats/difference.d.ts.map +1 -1
  23. package/dist/core/marks/stats/difference.js +20 -7
  24. package/dist/core/marks/stats/difference.js.map +2 -2
  25. package/dist/core/motion/arc-motion.d.ts +9 -0
  26. package/dist/core/motion/arc-motion.d.ts.map +1 -0
  27. package/dist/core/motion/arc-motion.js +67 -0
  28. package/dist/core/motion/arc-motion.js.map +7 -0
  29. package/dist/core/motion/enter-exit.d.ts +6 -0
  30. package/dist/core/motion/enter-exit.d.ts.map +1 -0
  31. package/dist/core/motion/enter-exit.js +20 -0
  32. package/dist/core/motion/enter-exit.js.map +7 -0
  33. package/dist/core/motion/leaving.d.ts +12 -0
  34. package/dist/core/motion/leaving.d.ts.map +1 -0
  35. package/dist/core/motion/leaving.js +33 -0
  36. package/dist/core/motion/leaving.js.map +7 -0
  37. package/dist/core/motion/motion-store.d.ts +4 -1
  38. package/dist/core/motion/motion-store.d.ts.map +1 -1
  39. package/dist/core/motion/motion-store.js +251 -100
  40. package/dist/core/motion/motion-store.js.map +3 -3
  41. package/dist/core/motion/points-motion.d.ts +6 -0
  42. package/dist/core/motion/points-motion.d.ts.map +1 -0
  43. package/dist/core/motion/points-motion.js +140 -0
  44. package/dist/core/motion/points-motion.js.map +7 -0
  45. package/dist/core/motion/strings.d.ts +18 -0
  46. package/dist/core/motion/strings.d.ts.map +1 -0
  47. package/dist/core/motion/strings.js +34 -0
  48. package/dist/core/motion/strings.js.map +7 -0
  49. package/dist/core/types.d.ts +85 -0
  50. package/dist/core/types.d.ts.map +1 -1
  51. package/dist/geo/geo.d.ts.map +1 -1
  52. package/dist/geo/geo.js +2 -0
  53. package/dist/geo/geo.js.map +2 -2
  54. package/dist/react/chart.d.ts.map +1 -1
  55. package/dist/react/chart.js +6 -2
  56. package/dist/react/chart.js.map +2 -2
  57. package/dist/react/svg-scene.d.ts.map +1 -1
  58. package/dist/react/svg-scene.js +3 -1
  59. package/dist/react/svg-scene.js.map +2 -2
  60. package/dist/react/use-animated-scene.d.ts.map +1 -1
  61. package/dist/react/use-animated-scene.js +14 -2
  62. package/dist/react/use-animated-scene.js.map +2 -2
  63. package/dist/voronoi/voronoi.d.ts.map +1 -1
  64. package/dist/voronoi/voronoi.js +2 -0
  65. package/dist/voronoi/voronoi.js.map +2 -2
  66. package/package.json +2 -2
  67. package/src/canvas/paint-scene.ts +5 -1
  68. package/src/contour/density.ts +2 -0
  69. package/src/core/marks/area.ts +28 -13
  70. package/src/core/marks/bar.ts +2 -0
  71. package/src/core/marks/line.ts +15 -8
  72. package/src/core/marks/polar.ts +22 -5
  73. package/src/core/marks/shared.ts +8 -0
  74. package/src/core/marks/stats/difference.ts +28 -8
  75. package/src/core/motion/arc-motion.ts +113 -0
  76. package/src/core/motion/enter-exit.ts +44 -0
  77. package/src/core/motion/leaving.ts +55 -0
  78. package/src/core/motion/motion-store.ts +332 -109
  79. package/src/core/motion/points-motion.ts +190 -0
  80. package/src/core/motion/strings.ts +68 -0
  81. package/src/core/types.ts +98 -0
  82. package/src/geo/geo.ts +2 -0
  83. package/src/react/chart.tsx +8 -2
  84. package/src/react/svg-scene.tsx +5 -1
  85. package/src/react/use-animated-scene.ts +18 -2
  86. package/src/voronoi/voronoi.ts +2 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/react/chart.tsx"],
4
- "sourcesContent": ["import * as React from \"react\";\nimport { compileChart } from \"#/core/compile.ts\";\nimport type { ChartDefinition } from \"#/core/define-chart.ts\";\nimport { type ChartAnimation, chartTiming } from \"#/core/motion/timing.ts\";\nimport { findNearest, focusStops } from \"#/core/nearest.ts\";\nimport { toNumber } from \"#/core/scales/continuous.ts\";\nimport type { ChartPoint } from \"#/core/types.ts\";\nimport {\n\ttype ChartBrush,\n\ttype ChartBrushRange,\n\tordered,\n} from \"#/react/brush-contract.ts\";\nimport { ChartDataTable } from \"#/react/chart-data-table.tsx\";\nimport { ChartColorRamp, ChartLegend } from \"#/react/chart-legend.tsx\";\nimport {\n\tChartTooltip,\n\tChartTooltipContent,\n\ttype ChartTooltipRenderProps,\n} from \"#/react/chart-tooltip.tsx\";\nimport { FocusOverlay } from \"#/react/focus-overlay.tsx\";\nimport { messagesFor } from \"#/react/messages.ts\";\nimport type { ChartRenderer } from \"#/react/renderer.ts\";\nimport { SR_ONLY } from \"#/react/sr-only.ts\";\nimport { SvgRenderer } from \"#/react/svg-renderer.tsx\";\nimport { useAnimatedScene } from \"#/react/use-animated-scene.ts\";\nimport { useChartFocus } from \"#/react/use-chart-focus.ts\";\nimport { useChartWidth } from \"#/react/use-chart-width.ts\";\nimport { useChartZoom } from \"#/react/use-chart-zoom.ts\";\nimport { useTextMeasurer } from \"#/react/use-text-measurer.ts\";\n\nconst DEFAULT_HEIGHT = 300;\nconst DEFAULT_INITIAL_WIDTH = 640;\n\n/** Focus ring, forced colours and reduced motion: what inline styles cannot say. */\nconst CHART_CSS = `\n[data-slot=\"chart-surface\"]:focus-visible{outline:2px solid var(--ring,Highlight);outline-offset:2px;border-radius:4px}\n@media (forced-colors:active){\n[data-slot=\"chart-svg\"] [data-role=\"axis\"],[data-slot=\"chart-svg\"] [data-role=\"axis-title\"]{fill:CanvasText}\n[data-slot=\"chart-svg\"] [data-role=\"grid\"]{stroke:GrayText}\n[data-slot=\"chart-focus-ring\"]{stroke:Highlight}\n[data-slot=\"chart-tooltip\"]{border-color:CanvasText}\n}\n@media (prefers-reduced-motion:no-preference){\n[data-slot=\"chart-tooltip\"]{transition:transform 80ms ease-out}\n}\n`;\n\n// `title` would put a native tooltip on the whole chart; the props carry the\n// accessible name as `ariaLabel` instead.\ninterface Props\n\textends Omit<React.ComponentProps<\"div\">, \"children\" | \"onSelect\" | \"title\"> {\n\treadonly definition: ChartDefinition;\n\t/** What the chart shows, in a sentence: the screen reader's name for it. Required. */\n\treadonly ariaLabel: string;\n\t/** The takeaway, when the label alone does not carry it. */\n\treadonly ariaDescription?: string;\n\t/** Pixels. Ignored when `aspectRatio` is set. */\n\treadonly height?: number;\n\t/** Height as a share of the width: `0.5` is twice as wide as tall. */\n\treadonly aspectRatio?: number;\n\t/** Width used on the server and for the first client render, before the container is measured. */\n\treadonly initialWidth?: number;\n\treadonly renderer?: ChartRenderer;\n\t/** `false` turns the tooltip off; a function replaces its content. */\n\treadonly tooltip?:\n\t\t| boolean\n\t\t| ((props: ChartTooltipRenderProps) => React.ReactNode);\n\t/** Shown by default when there is more than one series. */\n\treadonly legend?: boolean;\n\t/** The visually hidden table of the values. On by default; turn it off only when the page shows the same table. */\n\treadonly dataTable?: boolean;\n\t/**\n\t * How a data update moves: `true` (a spring with no bounce), a perceived\n\t * duration in milliseconds, or `{ duration, bounce, stagger }` and\n\t * `{ type: \"tween\", easing }`. `false` snaps; reduced motion always does.\n\t */\n\treadonly animate?: boolean | number | ChartAnimation;\n\t/**\n\t * Zoom a continuous x: the wheel while the chart has focus, + and \u2212,\n\t * Shift and the arrows to pan, a drag to pan when zoomed, 0 to reset.\n\t */\n\treadonly zoom?: boolean;\n\t/** Select a range of x by dragging or from the keyboard: `brushX({ onBrush })` from `@voila.dev/chart/brush`. */\n\treadonly brush?: ChartBrush;\n\treadonly onFocusChange?: (point: ChartPoint | null) => void;\n\treadonly onSelect?: (point: ChartPoint | null) => void;\n}\n\n/** A pointer that moved less than this between down and up clicked rather than dragged. */\nconst DRAG_THRESHOLD = 3;\nconst ZOOM_STEP = 0.8;\nconst PAN_SHARE = 0.1;\n\n/**\n * Draws a definition. Everything a reader needs beyond the picture comes with\n * it: keyboard focus on every value, a live region that reads the focused\n * value, a legend that toggles series, and the values as a hidden table.\n */\nexport function Chart({\n\tdefinition,\n\tariaLabel,\n\tariaDescription,\n\theight = DEFAULT_HEIGHT,\n\taspectRatio,\n\tinitialWidth = DEFAULT_INITIAL_WIDTH,\n\trenderer: Renderer = SvgRenderer,\n\ttooltip = true,\n\tlegend,\n\tdataTable = true,\n\tanimate = true,\n\tzoom = false,\n\tbrush: brushBehavior,\n\tonFocusChange,\n\tonSelect,\n\tstyle,\n\t...props\n}: Props) {\n\tconst chartId = React.useId().replaceAll(\":\", \"\");\n\tconst { ref, width, node } = useChartWidth(initialWidth);\n\tconst measureText = useTextMeasurer(node);\n\tconst [hidden, setHidden] = React.useState<ReadonlySet<string>>(\n\t\t() => new Set(),\n\t);\n\tconst chartHeight =\n\t\taspectRatio === undefined ? height : Math.round(width * aspectRatio);\n\n\tconst baseScene = React.useMemo(\n\t\t() =>\n\t\t\tcompileChart(definition, {\n\t\t\t\twidth,\n\t\t\t\theight: chartHeight,\n\t\t\t\tmeasureText,\n\t\t\t\thiddenSeries: hidden,\n\t\t\t}),\n\t\t[definition, width, chartHeight, measureText, hidden],\n\t);\n\tconst baseX = baseScene.scales.x;\n\tconst fullX = React.useMemo<readonly [number, number] | undefined>(\n\t\t() =>\n\t\t\tbaseX && baseX.kind !== \"band\" && baseX.kind !== \"point\"\n\t\t\t\t? [toNumber(baseX.domain[0]), toNumber(baseX.domain[1])]\n\t\t\t\t: undefined,\n\t\t[baseX],\n\t);\n\tconst viewport = useChartZoom(zoom ? fullX : undefined, definition);\n\tconst scene = React.useMemo(\n\t\t() =>\n\t\t\tviewport.domain === null\n\t\t\t\t? baseScene\n\t\t\t\t: compileChart(definition, {\n\t\t\t\t\t\twidth,\n\t\t\t\t\t\theight: chartHeight,\n\t\t\t\t\t\tmeasureText,\n\t\t\t\t\t\thiddenSeries: hidden,\n\t\t\t\t\t\txDomain: viewport.domain,\n\t\t\t\t\t}),\n\t\t[\n\t\t\tbaseScene,\n\t\t\tviewport.domain,\n\t\t\tdefinition,\n\t\t\twidth,\n\t\t\tchartHeight,\n\t\t\tmeasureText,\n\t\t\thidden,\n\t\t],\n\t);\n\tconst [brush, setBrush] = React.useState<ChartBrushRange | null>(null);\n\tconst [zoomAnnouncement, setZoomAnnouncement] = React.useState(\"\");\n\tconst drag = React.useRef<{\n\t\treadonly startX: number;\n\t\treadonly domain: readonly [number, number] | null;\n\t\tmoved: boolean;\n\t} | null>(null);\n\tconst stops = React.useMemo(() => focusStops(scene), [scene]);\n\tconst animationTrigger = React.useMemo(\n\t\t() => [definition, hidden],\n\t\t[definition, hidden],\n\t);\n\t// An inline `animate={{ \u2026 }}` object is read by value, not identity.\n\tconst animateKey = JSON.stringify(animate);\n\tconst timing = React.useMemo(\n\t\t() => chartTiming(JSON.parse(animateKey)),\n\t\t[animateKey],\n\t);\n\tconst shown = useAnimatedScene(scene, animationTrigger, timing);\n\tconst messages = messagesFor(scene.locale);\n\tconst { active, handlers } = useChartFocus({\n\t\tstops,\n\t\torder: scene.focusOrder,\n\t\thitTest: (x, y) => findNearest(scene, stops, x, y),\n\t\tonFocusChange,\n\t\tonSelect,\n\t});\n\n\tconst xScale = scene.scales.x;\n\tconst xContinuous =\n\t\txScale !== undefined && xScale.kind !== \"band\" && xScale.kind !== \"point\";\n\n\tfunction updateBrush(range: ChartBrushRange | null) {\n\t\tsetBrush(range);\n\t\tbrushBehavior?.onBrush(range);\n\t}\n\n\tfunction localX(event: React.PointerEvent<HTMLElement>): number {\n\t\treturn event.clientX - event.currentTarget.getBoundingClientRect().left;\n\t}\n\n\tfunction onPointerDown(event: React.PointerEvent<HTMLDivElement>) {\n\t\tif (!brushBehavior && !(zoom && viewport.domain !== null)) {\n\t\t\treturn;\n\t\t}\n\t\tevent.currentTarget.setPointerCapture(event.pointerId);\n\t\t// A drag selects a range, not a value: the hover tooltip gives way.\n\t\thandlers.onPointerLeave();\n\t\tdrag.current = {\n\t\t\tstartX: localX(event),\n\t\t\tdomain: viewport.domain,\n\t\t\tmoved: false,\n\t\t};\n\t}\n\n\tfunction onPointerMove(event: React.PointerEvent<HTMLDivElement>) {\n\t\tconst current = drag.current;\n\t\tif (current === null || xScale === undefined) {\n\t\t\thandlers.onPointerMove(event);\n\t\t\treturn;\n\t\t}\n\t\tconst x = localX(event);\n\t\tif (Math.abs(x - current.startX) > DRAG_THRESHOLD) {\n\t\t\tcurrent.moved = true;\n\t\t}\n\t\tif (!current.moved) return;\n\t\tif (brushBehavior) {\n\t\t\tupdateBrush(\n\t\t\t\tordered(xScale, [xScale.invert(current.startX), xScale.invert(x)]),\n\t\t\t);\n\t\t} else if (current.domain !== null) {\n\t\t\tconst [from, to] = current.domain;\n\t\t\tconst perPixel = (to - from) / Math.max(1, scene.plot.width);\n\t\t\tviewport.pan(\n\t\t\t\t(current.startX - x) * perPixel -\n\t\t\t\t\t((viewport.domain?.[0] ?? from) - from),\n\t\t\t);\n\t\t}\n\t}\n\n\tfunction onPointerUp(event: React.PointerEvent<HTMLDivElement>) {\n\t\tif (\n\t\t\tdrag.current !== null &&\n\t\t\tevent.currentTarget.hasPointerCapture(event.pointerId)\n\t\t) {\n\t\t\tevent.currentTarget.releasePointerCapture(event.pointerId);\n\t\t}\n\t}\n\n\tfunction onClick(event: React.MouseEvent<HTMLDivElement>) {\n\t\tconst dragged = drag.current?.moved === true;\n\t\tdrag.current = null;\n\t\tif (!dragged) {\n\t\t\thandlers.onClick(event);\n\t\t}\n\t}\n\n\tfunction announceViewport(next: readonly [number, number] | null) {\n\t\tif (xScale === undefined) return;\n\t\tconst [from, to] = next ?? fullX ?? [0, 0];\n\t\tconst asValue = (value: number) =>\n\t\t\txScale.kind === \"time\" ? new Date(value) : value;\n\t\tsetZoomAnnouncement(\n\t\t\tmessages.showing(\n\t\t\t\txScale.format(asValue(from)),\n\t\t\t\txScale.format(asValue(to)),\n\t\t\t),\n\t\t);\n\t}\n\n\tfunction onKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {\n\t\tif (zoom && xContinuous && fullX !== undefined) {\n\t\t\tconst focused = active?.point.xValue;\n\t\t\tconst [from, to] = viewport.domain ?? fullX;\n\t\t\tconst center =\n\t\t\t\tfocused === undefined ? (from + to) / 2 : toNumber(focused);\n\t\t\tif (event.key === \"+\" || event.key === \"=\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tviewport.zoomAround(center, ZOOM_STEP);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (event.key === \"-\" || event.key === \"_\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tviewport.zoomAround(center, 1 / ZOOM_STEP);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (event.key === \"0\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tviewport.reset();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tevent.shiftKey &&\n\t\t\t\t(event.key === \"ArrowLeft\" || event.key === \"ArrowRight\")\n\t\t\t) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tviewport.pan(\n\t\t\t\t\t(event.key === \"ArrowRight\" ? 1 : -1) * viewport.span() * PAN_SHARE,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\thandlers.onKeyDown(event);\n\t}\n\n\t// Moving the focus says the new value; the last zoom's message gives way.\n\tconst activeKey = active?.point.key;\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: clears on a change of focused point only\n\tReact.useEffect(() => {\n\t\tsetZoomAnnouncement(\"\");\n\t}, [activeKey]);\n\n\tconst viewportKey = viewport.domain?.join(\":\") ?? \"full\";\n\tconst previousViewport = React.useRef(viewportKey);\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: announce a change of viewport, once\n\tReact.useEffect(() => {\n\t\tif (previousViewport.current !== viewportKey) {\n\t\t\tpreviousViewport.current = viewportKey;\n\t\t\tannounceViewport(viewport.domain);\n\t\t}\n\t}, [viewportKey]);\n\n\t// The wheel zooms only while the chart has focus: scrolling past a chart\n\t// must keep scrolling the page.\n\tconst zoomRef = React.useRef({\n\t\tviewport,\n\t\txScale,\n\t\tenabled: zoom && xContinuous,\n\t});\n\tzoomRef.current = { viewport, xScale, enabled: zoom && xContinuous };\n\tReact.useEffect(() => {\n\t\tif (node === null) return;\n\t\tfunction onWheel(event: WheelEvent) {\n\t\t\tconst { viewport: current, xScale: scale, enabled } = zoomRef.current;\n\t\t\tif (\n\t\t\t\t!enabled ||\n\t\t\t\tscale === undefined ||\n\t\t\t\tdocument.activeElement !== node ||\n\t\t\t\tnode === null\n\t\t\t)\n\t\t\t\treturn;\n\t\t\tevent.preventDefault();\n\t\t\tconst x = event.clientX - node.getBoundingClientRect().left;\n\t\t\tcurrent.zoomAround(\n\t\t\t\ttoNumber(scale.invert(x)),\n\t\t\t\tevent.deltaY > 0 ? 1 / ZOOM_STEP : ZOOM_STEP,\n\t\t\t);\n\t\t}\n\t\tnode.addEventListener(\"wheel\", onWheel, { passive: false });\n\t\treturn () => node.removeEventListener(\"wheel\", onWheel);\n\t}, [node]);\n\n\tconst announcement =\n\t\tactive?.source === \"keyboard\"\n\t\t\t? `${active.point.title} : ${(active.stop.points.length > 1\n\t\t\t\t\t? active.stop.points\n\t\t\t\t\t: [active.point]\n\t\t\t\t)\n\t\t\t\t\t.map((point) => `${point.seriesLabel ?? scene.yLabel} ${point.value}`)\n\t\t\t\t\t.join(\", \")}`\n\t\t\t: \"\";\n\tconst showLegend = legend ?? scene.legend.length > 1;\n\tconst descriptionId = `${chartId}-description`;\n\tconst renderTooltip =\n\t\ttypeof tooltip === \"function\" ? tooltip : ChartTooltipContent;\n\n\tfunction toggle(key: string) {\n\t\tsetHidden((current) => {\n\t\t\tconst next = new Set(current);\n\t\t\tif (!next.delete(key)) {\n\t\t\t\tnext.add(key);\n\t\t\t}\n\t\t\treturn next;\n\t\t});\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"chart\"\n\t\t\tstyle={{ display: \"grid\", gap: 8, ...style }}\n\t\t\t{...props}\n\t\t>\n\t\t\t<style href=\"voila-chart\" precedence=\"default\">\n\t\t\t\t{CHART_CSS}\n\t\t\t</style>\n\t\t\t<div\n\t\t\t\tdata-slot=\"chart-frame\"\n\t\t\t\tstyle={{ position: \"relative\", height: chartHeight }}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tref={ref}\n\t\t\t\t\trole=\"img\"\n\t\t\t\t\taria-roledescription={messages.roleDescription}\n\t\t\t\t\taria-label={ariaLabel}\n\t\t\t\t\taria-describedby={descriptionId}\n\t\t\t\t\t// The chart is one focus stop; the arrow keys move inside it, the way\n\t\t\t\t\t// a reader walks the values. role=\"img\" keeps it a single named\n\t\t\t\t\t// graphic, the live region speaks the focused value.\n\t\t\t\t\t// biome-ignore lint/a11y/noNoninteractiveTabindex: keyboard exploration of the values\n\t\t\t\t\ttabIndex={0}\n\t\t\t\t\tdata-slot=\"chart-surface\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\tinset: 0,\n\t\t\t\t\t\ttouchAction: brushBehavior || zoom ? \"none\" : \"pan-y\",\n\t\t\t\t\t\t// Dragging across the plot brushes or pans; without this it\n\t\t\t\t\t\t// also selected the tick labels and the tooltip as text.\n\t\t\t\t\t\t...(brushBehavior || zoom\n\t\t\t\t\t\t\t? { userSelect: \"none\", WebkitUserSelect: \"none\" }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}}\n\t\t\t\t\t{...handlers}\n\t\t\t\t\tonKeyDown={onKeyDown}\n\t\t\t\t\tonPointerDown={onPointerDown}\n\t\t\t\t\tonPointerMove={onPointerMove}\n\t\t\t\t\tonPointerUp={onPointerUp}\n\t\t\t\t\tonClick={onClick}\n\t\t\t\t>\n\t\t\t\t\t<Renderer scene={shown} chartId={chartId} />\n\t\t\t\t\t<FocusOverlay scene={scene} active={active} theme={scene.theme} />\n\t\t\t\t\t{tooltip !== false && active !== null ? (\n\t\t\t\t\t\t<ChartTooltip\n\t\t\t\t\t\t\tactive={active}\n\t\t\t\t\t\t\tscene={scene}\n\t\t\t\t\t\t\trender={renderTooltip}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t\t{brushBehavior && xScale ? (\n\t\t\t\t\t<brushBehavior.Layer\n\t\t\t\t\t\tscale={xScale}\n\t\t\t\t\t\tplot={scene.plot}\n\t\t\t\t\t\trange={brush}\n\t\t\t\t\t\tmessages={messages}\n\t\t\t\t\t\tonChange={updateBrush}\n\t\t\t\t\t/>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t\t<div id={descriptionId} style={SR_ONLY}>\n\t\t\t\t{ariaDescription ? `${ariaDescription} ` : \"\"}\n\t\t\t\t{messages.keyboardHint}\n\t\t\t\t{zoom && xContinuous ? messages.zoomHint : \"\"}\n\t\t\t</div>\n\t\t\t<div aria-live=\"polite\" aria-atomic=\"true\" style={SR_ONLY}>\n\t\t\t\t{zoomAnnouncement || announcement}\n\t\t\t</div>\n\t\t\t{viewport.domain !== null ? (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-slot=\"chart-reset-zoom\"\n\t\t\t\t\tonClick={() => viewport.reset()}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tjustifySelf: \"end\",\n\t\t\t\t\t\tborder:\n\t\t\t\t\t\t\t\"1px solid var(--border, color-mix(in oklab, currentColor 20%, transparent))\",\n\t\t\t\t\t\tborderRadius: 6,\n\t\t\t\t\t\tbackground: \"transparent\",\n\t\t\t\t\t\tcolor: \"inherit\",\n\t\t\t\t\t\tfont: \"inherit\",\n\t\t\t\t\t\tfontSize: 12,\n\t\t\t\t\t\tpadding: \"2px 8px\",\n\t\t\t\t\t\tcursor: \"pointer\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{messages.resetZoom}\n\t\t\t\t</button>\n\t\t\t) : null}\n\t\t\t{showLegend && scene.legend.length > 0 ? (\n\t\t\t\t<ChartLegend\n\t\t\t\t\titems={scene.legend}\n\t\t\t\t\thidden={hidden}\n\t\t\t\t\tlabel={messages.legend}\n\t\t\t\t\tonToggle={toggle}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t\t{scene.colorRamp && legend !== false ? (\n\t\t\t\t<ChartColorRamp ramp={scene.colorRamp} label={messages.legend} />\n\t\t\t) : null}\n\t\t\t{dataTable ? (\n\t\t\t\t<ChartDataTable\n\t\t\t\t\tscene={scene}\n\t\t\t\t\tstops={stops}\n\t\t\t\t\tcaption={messages.dataTable(ariaLabel)}\n\t\t\t\t\tseriesHeading={messages.series}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t</div>\n\t);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from \"react\";\nimport { compileChart } from \"#/core/compile.ts\";\nimport type { ChartDefinition } from \"#/core/define-chart.ts\";\nimport { type ChartAnimation, chartTiming } from \"#/core/motion/timing.ts\";\nimport { findNearest, focusStops } from \"#/core/nearest.ts\";\nimport { toNumber } from \"#/core/scales/continuous.ts\";\nimport type { ChartPoint } from \"#/core/types.ts\";\nimport {\n\ttype ChartBrush,\n\ttype ChartBrushRange,\n\tordered,\n} from \"#/react/brush-contract.ts\";\nimport { ChartDataTable } from \"#/react/chart-data-table.tsx\";\nimport { ChartColorRamp, ChartLegend } from \"#/react/chart-legend.tsx\";\nimport {\n\tChartTooltip,\n\tChartTooltipContent,\n\ttype ChartTooltipRenderProps,\n} from \"#/react/chart-tooltip.tsx\";\nimport { FocusOverlay } from \"#/react/focus-overlay.tsx\";\nimport { messagesFor } from \"#/react/messages.ts\";\nimport type { ChartRenderer } from \"#/react/renderer.ts\";\nimport { SR_ONLY } from \"#/react/sr-only.ts\";\nimport { SvgRenderer } from \"#/react/svg-renderer.tsx\";\nimport { useAnimatedScene } from \"#/react/use-animated-scene.ts\";\nimport { useChartFocus } from \"#/react/use-chart-focus.ts\";\nimport { useChartWidth } from \"#/react/use-chart-width.ts\";\nimport { useChartZoom } from \"#/react/use-chart-zoom.ts\";\nimport { useTextMeasurer } from \"#/react/use-text-measurer.ts\";\n\nconst DEFAULT_HEIGHT = 300;\nconst DEFAULT_INITIAL_WIDTH = 640;\n\n/** Focus ring, forced colours and reduced motion: what inline styles cannot say. */\nconst CHART_CSS = `\n[data-slot=\"chart-surface\"]:focus-visible{outline:2px solid var(--ring,Highlight);outline-offset:2px;border-radius:4px}\n@media (forced-colors:active){\n[data-slot=\"chart-svg\"] [data-role=\"axis\"],[data-slot=\"chart-svg\"] [data-role=\"axis-title\"]{fill:CanvasText}\n[data-slot=\"chart-svg\"] [data-role=\"grid\"]{stroke:GrayText}\n[data-slot=\"chart-focus-ring\"]{stroke:Highlight}\n[data-slot=\"chart-tooltip\"]{border-color:CanvasText}\n}\n@media (prefers-reduced-motion:no-preference){\n[data-slot=\"chart-tooltip\"]{transition:transform 80ms ease-out}\n}\n`;\n\n// `title` would put a native tooltip on the whole chart; the props carry the\n// accessible name as `ariaLabel` instead.\ninterface Props\n\textends Omit<React.ComponentProps<\"div\">, \"children\" | \"onSelect\" | \"title\"> {\n\treadonly definition: ChartDefinition;\n\t/** What the chart shows, in a sentence: the screen reader's name for it. Required. */\n\treadonly ariaLabel: string;\n\t/** The takeaway, when the label alone does not carry it. */\n\treadonly ariaDescription?: string;\n\t/** Pixels. Ignored when `aspectRatio` is set. */\n\treadonly height?: number;\n\t/** Height as a share of the width: `0.5` is twice as wide as tall. */\n\treadonly aspectRatio?: number;\n\t/** Width used on the server and for the first client render, before the container is measured. */\n\treadonly initialWidth?: number;\n\treadonly renderer?: ChartRenderer;\n\t/** `false` turns the tooltip off; a function replaces its content. */\n\treadonly tooltip?:\n\t\t| boolean\n\t\t| ((props: ChartTooltipRenderProps) => React.ReactNode);\n\t/** Shown by default when there is more than one series. */\n\treadonly legend?: boolean;\n\t/** The visually hidden table of the values. On by default; turn it off only when the page shows the same table. */\n\treadonly dataTable?: boolean;\n\t/**\n\t * How a data update moves: `true` (a spring with no bounce), a perceived\n\t * duration in milliseconds, or `{ duration, bounce, stagger }` and\n\t * `{ type: \"tween\", easing }`. `false` snaps; reduced motion always does.\n\t */\n\treadonly animate?: boolean | number | ChartAnimation;\n\t/**\n\t * Zoom a continuous x: the wheel while the chart has focus, + and \u2212,\n\t * Shift and the arrows to pan, a drag to pan when zoomed, 0 to reset.\n\t */\n\treadonly zoom?: boolean;\n\t/** Select a range of x by dragging or from the keyboard: `brushX({ onBrush })` from `@voila.dev/chart/brush`. */\n\treadonly brush?: ChartBrush;\n\treadonly onFocusChange?: (point: ChartPoint | null) => void;\n\treadonly onSelect?: (point: ChartPoint | null) => void;\n}\n\n/** A pointer that moved less than this between down and up clicked rather than dragged. */\nconst DRAG_THRESHOLD = 3;\nconst ZOOM_STEP = 0.8;\nconst PAN_SHARE = 0.1;\n\n/**\n * Draws a definition. Everything a reader needs beyond the picture comes with\n * it: keyboard focus on every value, a live region that reads the focused\n * value, a legend that toggles series, and the values as a hidden table.\n */\nexport function Chart({\n\tdefinition,\n\tariaLabel,\n\tariaDescription,\n\theight = DEFAULT_HEIGHT,\n\taspectRatio,\n\tinitialWidth = DEFAULT_INITIAL_WIDTH,\n\trenderer: Renderer = SvgRenderer,\n\ttooltip = true,\n\tlegend,\n\tdataTable = true,\n\tanimate = true,\n\tzoom = false,\n\tbrush: brushBehavior,\n\tonFocusChange,\n\tonSelect,\n\tstyle,\n\t...props\n}: Props) {\n\tconst chartId = React.useId().replaceAll(\":\", \"\");\n\tconst { ref, width, node } = useChartWidth(initialWidth);\n\tconst measureText = useTextMeasurer(node);\n\tconst [hidden, setHidden] = React.useState<ReadonlySet<string>>(\n\t\t() => new Set(),\n\t);\n\tconst chartHeight =\n\t\taspectRatio === undefined ? height : Math.round(width * aspectRatio);\n\n\tconst baseScene = React.useMemo(\n\t\t() =>\n\t\t\tcompileChart(definition, {\n\t\t\t\twidth,\n\t\t\t\theight: chartHeight,\n\t\t\t\tmeasureText,\n\t\t\t\thiddenSeries: hidden,\n\t\t\t}),\n\t\t[definition, width, chartHeight, measureText, hidden],\n\t);\n\tconst baseX = baseScene.scales.x;\n\tconst fullX = React.useMemo<readonly [number, number] | undefined>(\n\t\t() =>\n\t\t\tbaseX && baseX.kind !== \"band\" && baseX.kind !== \"point\"\n\t\t\t\t? [toNumber(baseX.domain[0]), toNumber(baseX.domain[1])]\n\t\t\t\t: undefined,\n\t\t[baseX],\n\t);\n\tconst viewport = useChartZoom(zoom ? fullX : undefined, definition);\n\tconst scene = React.useMemo(\n\t\t() =>\n\t\t\tviewport.domain === null\n\t\t\t\t? baseScene\n\t\t\t\t: compileChart(definition, {\n\t\t\t\t\t\twidth,\n\t\t\t\t\t\theight: chartHeight,\n\t\t\t\t\t\tmeasureText,\n\t\t\t\t\t\thiddenSeries: hidden,\n\t\t\t\t\t\txDomain: viewport.domain,\n\t\t\t\t\t}),\n\t\t[\n\t\t\tbaseScene,\n\t\t\tviewport.domain,\n\t\t\tdefinition,\n\t\t\twidth,\n\t\t\tchartHeight,\n\t\t\tmeasureText,\n\t\t\thidden,\n\t\t],\n\t);\n\tconst [brush, setBrush] = React.useState<ChartBrushRange | null>(null);\n\tconst [zoomAnnouncement, setZoomAnnouncement] = React.useState(\"\");\n\tconst drag = React.useRef<{\n\t\treadonly startX: number;\n\t\treadonly domain: readonly [number, number] | null;\n\t\tmoved: boolean;\n\t} | null>(null);\n\tconst stops = React.useMemo(() => focusStops(scene), [scene]);\n\t// A step zoom from the keyboard moves like a data update; the wheel and a\n\t// drag are direct manipulation, so they snap.\n\tconst [keyboardZooms, setKeyboardZooms] = React.useState(0);\n\tconst animationTrigger = React.useMemo(\n\t\t() => [definition, hidden, keyboardZooms],\n\t\t[definition, hidden, keyboardZooms],\n\t);\n\t// An inline `animate={{ \u2026 }}` object is read by value, not identity.\n\tconst animateKey = JSON.stringify(animate);\n\tconst timing = React.useMemo(\n\t\t() => chartTiming(JSON.parse(animateKey)),\n\t\t[animateKey],\n\t);\n\tconst shown = useAnimatedScene(scene, animationTrigger, timing);\n\tconst messages = messagesFor(scene.locale);\n\tconst { active, handlers } = useChartFocus({\n\t\tstops,\n\t\torder: scene.focusOrder,\n\t\thitTest: (x, y) => findNearest(scene, stops, x, y),\n\t\tonFocusChange,\n\t\tonSelect,\n\t});\n\n\tconst xScale = scene.scales.x;\n\tconst xContinuous =\n\t\txScale !== undefined && xScale.kind !== \"band\" && xScale.kind !== \"point\";\n\n\tfunction updateBrush(range: ChartBrushRange | null) {\n\t\tsetBrush(range);\n\t\tbrushBehavior?.onBrush(range);\n\t}\n\n\tfunction localX(event: React.PointerEvent<HTMLElement>): number {\n\t\treturn event.clientX - event.currentTarget.getBoundingClientRect().left;\n\t}\n\n\tfunction onPointerDown(event: React.PointerEvent<HTMLDivElement>) {\n\t\tif (!brushBehavior && !(zoom && viewport.domain !== null)) {\n\t\t\treturn;\n\t\t}\n\t\tevent.currentTarget.setPointerCapture(event.pointerId);\n\t\t// A drag selects a range, not a value: the hover tooltip gives way.\n\t\thandlers.onPointerLeave();\n\t\tdrag.current = {\n\t\t\tstartX: localX(event),\n\t\t\tdomain: viewport.domain,\n\t\t\tmoved: false,\n\t\t};\n\t}\n\n\tfunction onPointerMove(event: React.PointerEvent<HTMLDivElement>) {\n\t\tconst current = drag.current;\n\t\tif (current === null || xScale === undefined) {\n\t\t\thandlers.onPointerMove(event);\n\t\t\treturn;\n\t\t}\n\t\tconst x = localX(event);\n\t\tif (Math.abs(x - current.startX) > DRAG_THRESHOLD) {\n\t\t\tcurrent.moved = true;\n\t\t}\n\t\tif (!current.moved) return;\n\t\tif (brushBehavior) {\n\t\t\tupdateBrush(\n\t\t\t\tordered(xScale, [xScale.invert(current.startX), xScale.invert(x)]),\n\t\t\t);\n\t\t} else if (current.domain !== null) {\n\t\t\tconst [from, to] = current.domain;\n\t\t\tconst perPixel = (to - from) / Math.max(1, scene.plot.width);\n\t\t\tviewport.pan(\n\t\t\t\t(current.startX - x) * perPixel -\n\t\t\t\t\t((viewport.domain?.[0] ?? from) - from),\n\t\t\t);\n\t\t}\n\t}\n\n\tfunction onPointerUp(event: React.PointerEvent<HTMLDivElement>) {\n\t\tif (\n\t\t\tdrag.current !== null &&\n\t\t\tevent.currentTarget.hasPointerCapture(event.pointerId)\n\t\t) {\n\t\t\tevent.currentTarget.releasePointerCapture(event.pointerId);\n\t\t}\n\t}\n\n\tfunction onClick(event: React.MouseEvent<HTMLDivElement>) {\n\t\tconst dragged = drag.current?.moved === true;\n\t\tdrag.current = null;\n\t\tif (!dragged) {\n\t\t\thandlers.onClick(event);\n\t\t}\n\t}\n\n\tfunction announceViewport(next: readonly [number, number] | null) {\n\t\tif (xScale === undefined) return;\n\t\tconst [from, to] = next ?? fullX ?? [0, 0];\n\t\tconst asValue = (value: number) =>\n\t\t\txScale.kind === \"time\" ? new Date(value) : value;\n\t\tsetZoomAnnouncement(\n\t\t\tmessages.showing(\n\t\t\t\txScale.format(asValue(from)),\n\t\t\t\txScale.format(asValue(to)),\n\t\t\t),\n\t\t);\n\t}\n\n\tfunction onKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {\n\t\tif (zoom && xContinuous && fullX !== undefined) {\n\t\t\tconst focused = active?.point.xValue;\n\t\t\tconst [from, to] = viewport.domain ?? fullX;\n\t\t\tconst center =\n\t\t\t\tfocused === undefined ? (from + to) / 2 : toNumber(focused);\n\t\t\tif (event.key === \"+\" || event.key === \"=\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tviewport.zoomAround(center, ZOOM_STEP);\n\t\t\t\tsetKeyboardZooms((count) => count + 1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (event.key === \"-\" || event.key === \"_\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tviewport.zoomAround(center, 1 / ZOOM_STEP);\n\t\t\t\tsetKeyboardZooms((count) => count + 1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (event.key === \"0\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tviewport.reset();\n\t\t\t\tsetKeyboardZooms((count) => count + 1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tevent.shiftKey &&\n\t\t\t\t(event.key === \"ArrowLeft\" || event.key === \"ArrowRight\")\n\t\t\t) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tviewport.pan(\n\t\t\t\t\t(event.key === \"ArrowRight\" ? 1 : -1) * viewport.span() * PAN_SHARE,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\thandlers.onKeyDown(event);\n\t}\n\n\t// Moving the focus says the new value; the last zoom's message gives way.\n\tconst activeKey = active?.point.key;\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: clears on a change of focused point only\n\tReact.useEffect(() => {\n\t\tsetZoomAnnouncement(\"\");\n\t}, [activeKey]);\n\n\tconst viewportKey = viewport.domain?.join(\":\") ?? \"full\";\n\tconst previousViewport = React.useRef(viewportKey);\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: announce a change of viewport, once\n\tReact.useEffect(() => {\n\t\tif (previousViewport.current !== viewportKey) {\n\t\t\tpreviousViewport.current = viewportKey;\n\t\t\tannounceViewport(viewport.domain);\n\t\t}\n\t}, [viewportKey]);\n\n\t// The wheel zooms only while the chart has focus: scrolling past a chart\n\t// must keep scrolling the page.\n\tconst zoomRef = React.useRef({\n\t\tviewport,\n\t\txScale,\n\t\tenabled: zoom && xContinuous,\n\t});\n\tzoomRef.current = { viewport, xScale, enabled: zoom && xContinuous };\n\tReact.useEffect(() => {\n\t\tif (node === null) return;\n\t\tfunction onWheel(event: WheelEvent) {\n\t\t\tconst { viewport: current, xScale: scale, enabled } = zoomRef.current;\n\t\t\tif (\n\t\t\t\t!enabled ||\n\t\t\t\tscale === undefined ||\n\t\t\t\tdocument.activeElement !== node ||\n\t\t\t\tnode === null\n\t\t\t)\n\t\t\t\treturn;\n\t\t\tevent.preventDefault();\n\t\t\tconst x = event.clientX - node.getBoundingClientRect().left;\n\t\t\tcurrent.zoomAround(\n\t\t\t\ttoNumber(scale.invert(x)),\n\t\t\t\tevent.deltaY > 0 ? 1 / ZOOM_STEP : ZOOM_STEP,\n\t\t\t);\n\t\t}\n\t\tnode.addEventListener(\"wheel\", onWheel, { passive: false });\n\t\treturn () => node.removeEventListener(\"wheel\", onWheel);\n\t}, [node]);\n\n\tconst announcement =\n\t\tactive?.source === \"keyboard\"\n\t\t\t? `${active.point.title} : ${(active.stop.points.length > 1\n\t\t\t\t\t? active.stop.points\n\t\t\t\t\t: [active.point]\n\t\t\t\t)\n\t\t\t\t\t.map((point) => `${point.seriesLabel ?? scene.yLabel} ${point.value}`)\n\t\t\t\t\t.join(\", \")}`\n\t\t\t: \"\";\n\tconst showLegend = legend ?? scene.legend.length > 1;\n\tconst descriptionId = `${chartId}-description`;\n\tconst renderTooltip =\n\t\ttypeof tooltip === \"function\" ? tooltip : ChartTooltipContent;\n\n\tfunction toggle(key: string) {\n\t\tsetHidden((current) => {\n\t\t\tconst next = new Set(current);\n\t\t\tif (!next.delete(key)) {\n\t\t\t\tnext.add(key);\n\t\t\t}\n\t\t\treturn next;\n\t\t});\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"chart\"\n\t\t\tstyle={{ display: \"grid\", gap: 8, ...style }}\n\t\t\t{...props}\n\t\t>\n\t\t\t<style href=\"voila-chart\" precedence=\"default\">\n\t\t\t\t{CHART_CSS}\n\t\t\t</style>\n\t\t\t<div\n\t\t\t\tdata-slot=\"chart-frame\"\n\t\t\t\tstyle={{ position: \"relative\", height: chartHeight }}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tref={ref}\n\t\t\t\t\trole=\"img\"\n\t\t\t\t\taria-roledescription={messages.roleDescription}\n\t\t\t\t\taria-label={ariaLabel}\n\t\t\t\t\taria-describedby={descriptionId}\n\t\t\t\t\t// The chart is one focus stop; the arrow keys move inside it, the way\n\t\t\t\t\t// a reader walks the values. role=\"img\" keeps it a single named\n\t\t\t\t\t// graphic, the live region speaks the focused value.\n\t\t\t\t\t// biome-ignore lint/a11y/noNoninteractiveTabindex: keyboard exploration of the values\n\t\t\t\t\ttabIndex={0}\n\t\t\t\t\tdata-slot=\"chart-surface\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\tinset: 0,\n\t\t\t\t\t\ttouchAction: brushBehavior || zoom ? \"none\" : \"pan-y\",\n\t\t\t\t\t\t// Dragging across the plot brushes or pans; without this it\n\t\t\t\t\t\t// also selected the tick labels and the tooltip as text.\n\t\t\t\t\t\t...(brushBehavior || zoom\n\t\t\t\t\t\t\t? { userSelect: \"none\", WebkitUserSelect: \"none\" }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}}\n\t\t\t\t\t{...handlers}\n\t\t\t\t\tonKeyDown={onKeyDown}\n\t\t\t\t\tonPointerDown={onPointerDown}\n\t\t\t\t\tonPointerMove={onPointerMove}\n\t\t\t\t\tonPointerUp={onPointerUp}\n\t\t\t\t\tonClick={onClick}\n\t\t\t\t>\n\t\t\t\t\t<Renderer scene={shown} chartId={chartId} />\n\t\t\t\t\t<FocusOverlay scene={scene} active={active} theme={scene.theme} />\n\t\t\t\t\t{tooltip !== false && active !== null ? (\n\t\t\t\t\t\t<ChartTooltip\n\t\t\t\t\t\t\tactive={active}\n\t\t\t\t\t\t\tscene={scene}\n\t\t\t\t\t\t\trender={renderTooltip}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t\t{brushBehavior && xScale ? (\n\t\t\t\t\t<brushBehavior.Layer\n\t\t\t\t\t\tscale={xScale}\n\t\t\t\t\t\tplot={scene.plot}\n\t\t\t\t\t\trange={brush}\n\t\t\t\t\t\tmessages={messages}\n\t\t\t\t\t\tonChange={updateBrush}\n\t\t\t\t\t/>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t\t<div id={descriptionId} style={SR_ONLY}>\n\t\t\t\t{ariaDescription ? `${ariaDescription} ` : \"\"}\n\t\t\t\t{messages.keyboardHint}\n\t\t\t\t{zoom && xContinuous ? messages.zoomHint : \"\"}\n\t\t\t</div>\n\t\t\t<div aria-live=\"polite\" aria-atomic=\"true\" style={SR_ONLY}>\n\t\t\t\t{zoomAnnouncement || announcement}\n\t\t\t</div>\n\t\t\t{viewport.domain !== null ? (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-slot=\"chart-reset-zoom\"\n\t\t\t\t\tonClick={() => viewport.reset()}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tjustifySelf: \"end\",\n\t\t\t\t\t\tborder:\n\t\t\t\t\t\t\t\"1px solid var(--border, color-mix(in oklab, currentColor 20%, transparent))\",\n\t\t\t\t\t\tborderRadius: 6,\n\t\t\t\t\t\tbackground: \"transparent\",\n\t\t\t\t\t\tcolor: \"inherit\",\n\t\t\t\t\t\tfont: \"inherit\",\n\t\t\t\t\t\tfontSize: 12,\n\t\t\t\t\t\tpadding: \"2px 8px\",\n\t\t\t\t\t\tcursor: \"pointer\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{messages.resetZoom}\n\t\t\t\t</button>\n\t\t\t) : null}\n\t\t\t{showLegend && scene.legend.length > 0 ? (\n\t\t\t\t<ChartLegend\n\t\t\t\t\titems={scene.legend}\n\t\t\t\t\thidden={hidden}\n\t\t\t\t\tlabel={messages.legend}\n\t\t\t\t\tonToggle={toggle}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t\t{scene.colorRamp && legend !== false ? (\n\t\t\t\t<ChartColorRamp ramp={scene.colorRamp} label={messages.legend} />\n\t\t\t) : null}\n\t\t\t{dataTable ? (\n\t\t\t\t<ChartDataTable\n\t\t\t\t\tscene={scene}\n\t\t\t\t\tstops={stops}\n\t\t\t\t\tcaption={messages.dataTable(ariaLabel)}\n\t\t\t\t\tseriesHeading={messages.series}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t</div>\n\t);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"svg-scene.d.ts","sourceRoot":"","sources":["../../src/react/svg-scene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,OAAO,KAAK,EAEX,UAAU,EAGV,MAAM,iBAAiB,CAAC;AAgKzB,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,EAC7B,KAAK,EACL,UAAU,GACV,EAAE;IACF,KAAK,EAAE,UAAU,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACnB,qBAgCA"}
1
+ {"version":3,"file":"svg-scene.d.ts","sourceRoot":"","sources":["../../src/react/svg-scene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,OAAO,KAAK,EAEX,UAAU,EAGV,MAAM,iBAAiB,CAAC;AAoKzB,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,EAC7B,KAAK,EACL,UAAU,GACV,EAAE;IACF,KAAK,EAAE,UAAU,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACnB,qBAgCA"}
@@ -29,7 +29,9 @@ function paintProps(paint, prefix) {
29
29
  fill: paint.hatch && paint.fill ? `url(#${hatchId(prefix, paint.fill)})` : paint.fill ?? "none",
30
30
  stroke: paint.stroke,
31
31
  strokeWidth: paint.strokeWidth,
32
- strokeDasharray: paint.strokeDasharray,
32
+ strokeDasharray: paint.drawn ? "1 1" : paint.strokeDasharray,
33
+ // Measured in path lengths: the dash shows the first `fraction` of the line.
34
+ ...paint.drawn ? { pathLength: 1, strokeDashoffset: 1 - paint.drawn.fraction } : {},
33
35
  strokeLinecap: paint.strokeLinecap,
34
36
  strokeLinejoin: paint.strokeLinejoin,
35
37
  opacity: paint.opacity,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/react/svg-scene.tsx"],
4
- "sourcesContent": ["import type * as React from \"react\";\nimport { roundedBarPath } from \"#/core/paths.ts\";\nimport type {\n\tChartPaint,\n\tChartScene,\n\tChartTextPaint,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nconst BASELINE: Record<\n\tNonNullable<ChartTextPaint[\"baseline\"]>,\n\tReact.SVGAttributes<SVGTextElement>[\"dominantBaseline\"]\n> = {\n\ttop: \"text-before-edge\",\n\tmiddle: \"central\",\n\tbottom: \"text-after-edge\",\n\talphabetic: \"alphabetic\",\n};\n\nconst HATCH_STEP = 6;\n\n/** A stable id per colour (djb2), the same on the server and in the browser. */\nfunction hatchId(prefix: string, color: string): string {\n\tlet hash = 5381;\n\tfor (const character of color) {\n\t\thash = ((hash << 5) + hash + character.charCodeAt(0)) | 0;\n\t}\n\treturn `${prefix}-hatch-${(hash >>> 0).toString(36)}`;\n}\n\nfunction hatchColors(\n\tnodes: ReadonlyArray<SceneNode>,\n\tinto = new Set<string>(),\n): Set<string> {\n\tfor (const node of nodes) {\n\t\tif (node.kind === \"group\") {\n\t\t\thatchColors(node.children, into);\n\t\t} else if (node.paint.hatch && node.paint.fill) {\n\t\t\tinto.add(node.paint.fill);\n\t\t}\n\t}\n\treturn into;\n}\n\nfunction paintProps(\n\tpaint: ChartPaint,\n\tprefix: string,\n): React.SVGAttributes<SVGElement> {\n\treturn {\n\t\tfill:\n\t\t\tpaint.hatch && paint.fill\n\t\t\t\t? `url(#${hatchId(prefix, paint.fill)})`\n\t\t\t\t: (paint.fill ?? \"none\"),\n\t\tstroke: paint.stroke,\n\t\tstrokeWidth: paint.strokeWidth,\n\t\tstrokeDasharray: paint.strokeDasharray,\n\t\tstrokeLinecap: paint.strokeLinecap,\n\t\tstrokeLinejoin: paint.strokeLinejoin,\n\t\topacity: paint.opacity,\n\t\tfillOpacity: paint.fillOpacity,\n\t\tstrokeOpacity: paint.strokeOpacity,\n\t};\n}\n\nfunction SceneElement({\n\tnode,\n\tclipPrefix,\n}: {\n\tnode: SceneNode;\n\tclipPrefix: string;\n}) {\n\tconst data = { \"data-role\": node.role, \"data-series\": node.series };\n\tswitch (node.kind) {\n\t\tcase \"group\": {\n\t\t\tconst clipId = node.clip ? `${clipPrefix}-${node.key}` : undefined;\n\t\t\treturn (\n\t\t\t\t<g\n\t\t\t\t\t{...data}\n\t\t\t\t\ttransform={\n\t\t\t\t\t\tnode.translate\n\t\t\t\t\t\t\t? `translate(${node.translate.x} ${node.translate.y})`\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t}\n\t\t\t\t\tclipPath={clipId ? `url(#${clipId})` : undefined}\n\t\t\t\t>\n\t\t\t\t\t{node.clip && clipId ? (\n\t\t\t\t\t\t<clipPath id={clipId}>\n\t\t\t\t\t\t\t<rect {...node.clip} />\n\t\t\t\t\t\t</clipPath>\n\t\t\t\t\t) : null}\n\t\t\t\t\t{node.children.map((child) => (\n\t\t\t\t\t\t<SceneElement\n\t\t\t\t\t\t\tkey={child.key}\n\t\t\t\t\t\t\tnode={child}\n\t\t\t\t\t\t\tclipPrefix={clipPrefix}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</g>\n\t\t\t);\n\t\t}\n\t\tcase \"rect\":\n\t\t\treturn node.corners ? (\n\t\t\t\t<path\n\t\t\t\t\t{...data}\n\t\t\t\t\td={roundedBarPath({ ...node, radius: node.corners })}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<rect\n\t\t\t\t\t{...data}\n\t\t\t\t\tx={node.x}\n\t\t\t\t\ty={node.y}\n\t\t\t\t\twidth={node.width}\n\t\t\t\t\theight={node.height}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"path\":\n\t\t\treturn (\n\t\t\t\t<path {...data} d={node.d} {...paintProps(node.paint, clipPrefix)} />\n\t\t\t);\n\t\tcase \"circle\":\n\t\t\treturn (\n\t\t\t\t<circle\n\t\t\t\t\t{...data}\n\t\t\t\t\tcx={node.cx}\n\t\t\t\t\tcy={node.cy}\n\t\t\t\t\tr={node.r}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"line\":\n\t\t\treturn (\n\t\t\t\t<line\n\t\t\t\t\t{...data}\n\t\t\t\t\tx1={node.x1}\n\t\t\t\t\ty1={node.y1}\n\t\t\t\t\tx2={node.x2}\n\t\t\t\t\ty2={node.y2}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"text\":\n\t\t\treturn (\n\t\t\t\t<text\n\t\t\t\t\t{...data}\n\t\t\t\t\tx={node.x}\n\t\t\t\t\ty={node.y}\n\t\t\t\t\ttransform={\n\t\t\t\t\t\tnode.rotate\n\t\t\t\t\t\t\t? `rotate(${node.rotate} ${node.x} ${node.y})`\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t}\n\t\t\t\t\tfontSize={node.paint.fontSize}\n\t\t\t\t\tfontWeight={node.paint.fontWeight}\n\t\t\t\t\ttextAnchor={node.paint.textAnchor}\n\t\t\t\t\tdominantBaseline={\n\t\t\t\t\t\tnode.paint.baseline ? BASELINE[node.paint.baseline] : undefined\n\t\t\t\t\t}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t>\n\t\t\t\t\t{node.text}\n\t\t\t\t</text>\n\t\t\t);\n\t}\n}\n\n/** The scene's nodes as SVG elements, keyed so React reconciles a data update in place. */\nexport function SvgSceneNodes({\n\tscene,\n\tclipPrefix,\n}: {\n\tscene: ChartScene;\n\tclipPrefix: string;\n}) {\n\tconst hatches = [...hatchColors(scene.nodes)];\n\treturn (\n\t\t<>\n\t\t\t{hatches.length > 0 ? (\n\t\t\t\t<defs>\n\t\t\t\t\t{hatches.map((color) => (\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\tkey={color}\n\t\t\t\t\t\t\tid={hatchId(clipPrefix, color)}\n\t\t\t\t\t\t\twidth={HATCH_STEP}\n\t\t\t\t\t\t\theight={HATCH_STEP}\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t\tpatternTransform=\"rotate(45)\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<line\n\t\t\t\t\t\t\t\tx1={0}\n\t\t\t\t\t\t\t\ty1={0}\n\t\t\t\t\t\t\t\tx2={0}\n\t\t\t\t\t\t\t\ty2={HATCH_STEP}\n\t\t\t\t\t\t\t\tstroke={color}\n\t\t\t\t\t\t\t\tstrokeWidth={1.5}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t))}\n\t\t\t\t</defs>\n\t\t\t) : null}\n\t\t\t{scene.nodes.map((node) => (\n\t\t\t\t<SceneElement key={node.key} node={node} clipPrefix={clipPrefix} />\n\t\t\t))}\n\t\t</>\n\t);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type * as React from \"react\";\nimport { roundedBarPath } from \"#/core/paths.ts\";\nimport type {\n\tChartPaint,\n\tChartScene,\n\tChartTextPaint,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nconst BASELINE: Record<\n\tNonNullable<ChartTextPaint[\"baseline\"]>,\n\tReact.SVGAttributes<SVGTextElement>[\"dominantBaseline\"]\n> = {\n\ttop: \"text-before-edge\",\n\tmiddle: \"central\",\n\tbottom: \"text-after-edge\",\n\talphabetic: \"alphabetic\",\n};\n\nconst HATCH_STEP = 6;\n\n/** A stable id per colour (djb2), the same on the server and in the browser. */\nfunction hatchId(prefix: string, color: string): string {\n\tlet hash = 5381;\n\tfor (const character of color) {\n\t\thash = ((hash << 5) + hash + character.charCodeAt(0)) | 0;\n\t}\n\treturn `${prefix}-hatch-${(hash >>> 0).toString(36)}`;\n}\n\nfunction hatchColors(\n\tnodes: ReadonlyArray<SceneNode>,\n\tinto = new Set<string>(),\n): Set<string> {\n\tfor (const node of nodes) {\n\t\tif (node.kind === \"group\") {\n\t\t\thatchColors(node.children, into);\n\t\t} else if (node.paint.hatch && node.paint.fill) {\n\t\t\tinto.add(node.paint.fill);\n\t\t}\n\t}\n\treturn into;\n}\n\nfunction paintProps(\n\tpaint: ChartPaint,\n\tprefix: string,\n): React.SVGAttributes<SVGElement> {\n\treturn {\n\t\tfill:\n\t\t\tpaint.hatch && paint.fill\n\t\t\t\t? `url(#${hatchId(prefix, paint.fill)})`\n\t\t\t\t: (paint.fill ?? \"none\"),\n\t\tstroke: paint.stroke,\n\t\tstrokeWidth: paint.strokeWidth,\n\t\tstrokeDasharray: paint.drawn ? \"1 1\" : paint.strokeDasharray,\n\t\t// Measured in path lengths: the dash shows the first `fraction` of the line.\n\t\t...(paint.drawn\n\t\t\t? { pathLength: 1, strokeDashoffset: 1 - paint.drawn.fraction }\n\t\t\t: {}),\n\t\tstrokeLinecap: paint.strokeLinecap,\n\t\tstrokeLinejoin: paint.strokeLinejoin,\n\t\topacity: paint.opacity,\n\t\tfillOpacity: paint.fillOpacity,\n\t\tstrokeOpacity: paint.strokeOpacity,\n\t};\n}\n\nfunction SceneElement({\n\tnode,\n\tclipPrefix,\n}: {\n\tnode: SceneNode;\n\tclipPrefix: string;\n}) {\n\tconst data = { \"data-role\": node.role, \"data-series\": node.series };\n\tswitch (node.kind) {\n\t\tcase \"group\": {\n\t\t\tconst clipId = node.clip ? `${clipPrefix}-${node.key}` : undefined;\n\t\t\treturn (\n\t\t\t\t<g\n\t\t\t\t\t{...data}\n\t\t\t\t\ttransform={\n\t\t\t\t\t\tnode.translate\n\t\t\t\t\t\t\t? `translate(${node.translate.x} ${node.translate.y})`\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t}\n\t\t\t\t\tclipPath={clipId ? `url(#${clipId})` : undefined}\n\t\t\t\t>\n\t\t\t\t\t{node.clip && clipId ? (\n\t\t\t\t\t\t<clipPath id={clipId}>\n\t\t\t\t\t\t\t<rect {...node.clip} />\n\t\t\t\t\t\t</clipPath>\n\t\t\t\t\t) : null}\n\t\t\t\t\t{node.children.map((child) => (\n\t\t\t\t\t\t<SceneElement\n\t\t\t\t\t\t\tkey={child.key}\n\t\t\t\t\t\t\tnode={child}\n\t\t\t\t\t\t\tclipPrefix={clipPrefix}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</g>\n\t\t\t);\n\t\t}\n\t\tcase \"rect\":\n\t\t\treturn node.corners ? (\n\t\t\t\t<path\n\t\t\t\t\t{...data}\n\t\t\t\t\td={roundedBarPath({ ...node, radius: node.corners })}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<rect\n\t\t\t\t\t{...data}\n\t\t\t\t\tx={node.x}\n\t\t\t\t\ty={node.y}\n\t\t\t\t\twidth={node.width}\n\t\t\t\t\theight={node.height}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"path\":\n\t\t\treturn (\n\t\t\t\t<path {...data} d={node.d} {...paintProps(node.paint, clipPrefix)} />\n\t\t\t);\n\t\tcase \"circle\":\n\t\t\treturn (\n\t\t\t\t<circle\n\t\t\t\t\t{...data}\n\t\t\t\t\tcx={node.cx}\n\t\t\t\t\tcy={node.cy}\n\t\t\t\t\tr={node.r}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"line\":\n\t\t\treturn (\n\t\t\t\t<line\n\t\t\t\t\t{...data}\n\t\t\t\t\tx1={node.x1}\n\t\t\t\t\ty1={node.y1}\n\t\t\t\t\tx2={node.x2}\n\t\t\t\t\ty2={node.y2}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"text\":\n\t\t\treturn (\n\t\t\t\t<text\n\t\t\t\t\t{...data}\n\t\t\t\t\tx={node.x}\n\t\t\t\t\ty={node.y}\n\t\t\t\t\ttransform={\n\t\t\t\t\t\tnode.rotate\n\t\t\t\t\t\t\t? `rotate(${node.rotate} ${node.x} ${node.y})`\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t}\n\t\t\t\t\tfontSize={node.paint.fontSize}\n\t\t\t\t\tfontWeight={node.paint.fontWeight}\n\t\t\t\t\ttextAnchor={node.paint.textAnchor}\n\t\t\t\t\tdominantBaseline={\n\t\t\t\t\t\tnode.paint.baseline ? BASELINE[node.paint.baseline] : undefined\n\t\t\t\t\t}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t>\n\t\t\t\t\t{node.text}\n\t\t\t\t</text>\n\t\t\t);\n\t}\n}\n\n/** The scene's nodes as SVG elements, keyed so React reconciles a data update in place. */\nexport function SvgSceneNodes({\n\tscene,\n\tclipPrefix,\n}: {\n\tscene: ChartScene;\n\tclipPrefix: string;\n}) {\n\tconst hatches = [...hatchColors(scene.nodes)];\n\treturn (\n\t\t<>\n\t\t\t{hatches.length > 0 ? (\n\t\t\t\t<defs>\n\t\t\t\t\t{hatches.map((color) => (\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\tkey={color}\n\t\t\t\t\t\t\tid={hatchId(clipPrefix, color)}\n\t\t\t\t\t\t\twidth={HATCH_STEP}\n\t\t\t\t\t\t\theight={HATCH_STEP}\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t\tpatternTransform=\"rotate(45)\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<line\n\t\t\t\t\t\t\t\tx1={0}\n\t\t\t\t\t\t\t\ty1={0}\n\t\t\t\t\t\t\t\tx2={0}\n\t\t\t\t\t\t\t\ty2={HATCH_STEP}\n\t\t\t\t\t\t\t\tstroke={color}\n\t\t\t\t\t\t\t\tstrokeWidth={1.5}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t))}\n\t\t\t\t</defs>\n\t\t\t) : null}\n\t\t\t{scene.nodes.map((node) => (\n\t\t\t\t<SceneElement key={node.key} node={node} clipPrefix={clipPrefix} />\n\t\t\t))}\n\t\t</>\n\t);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-animated-scene.d.ts","sourceRoot":"","sources":["../../src/react/use-animated-scene.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,WAAW,GAAG,IAAI,GACxB,UAAU,CAoCZ"}
1
+ {"version":3,"file":"use-animated-scene.d.ts","sourceRoot":"","sources":["../../src/react/use-animated-scene.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,WAAW,GAAG,IAAI,GACxB,UAAU,CAoDZ"}
@@ -6,6 +6,8 @@ import {
6
6
  function useAnimatedScene(scene, trigger, timing) {
7
7
  const [shown, setShown] = React.useState(scene);
8
8
  const triggerRef = React.useRef(trigger);
9
+ const introduced = React.useRef(false);
10
+ const introStart = React.useRef(null);
9
11
  const storeRef = React.useRef(null);
10
12
  React.useLayoutEffect(() => {
11
13
  const changed = triggerRef.current !== trigger;
@@ -14,15 +16,25 @@ function useAnimatedScene(scene, trigger, timing) {
14
16
  storeRef.current = { store: createMotionStore(scene, timing), timing };
15
17
  }
16
18
  const store = storeRef.current?.store;
17
- if (!changed || !timing || !store || shouldReduceMotion()) {
19
+ const still = !timing || !store || shouldReduceMotion();
20
+ let intro = false;
21
+ if (!still && !changed && !introduced.current) {
22
+ const start = introStart.current ?? performance.now();
23
+ if (introStart.current !== null) store.snap(scene);
24
+ intro = store.introduce(start);
25
+ if (intro) introStart.current = start;
26
+ }
27
+ if (still || !changed && !intro) {
28
+ introduced.current = true;
18
29
  store?.snap(scene);
19
30
  setShown(scene);
20
31
  return;
21
32
  }
22
- store.retarget(scene, performance.now());
33
+ if (changed) store.retarget(scene, performance.now());
23
34
  function tick() {
24
35
  const now = performance.now();
25
36
  if (store?.settled(now)) {
37
+ introduced.current = true;
26
38
  cancelFrame(tick);
27
39
  setShown(scene);
28
40
  return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/react/use-animated-scene.ts"],
4
- "sourcesContent": ["import { cancelFrame, frame, shouldReduceMotion } from \"@voila.dev/motion\";\nimport * as React from \"react\";\nimport {\n\tcreateMotionStore,\n\ttype MotionStore,\n} from \"#/core/motion/motion-store.ts\";\nimport type { ChartTiming } from \"#/core/motion/timing.ts\";\nimport type { ChartScene } from \"#/core/types.ts\";\n\n/**\n * The scene to draw this frame. A data change (`trigger` changed: a new\n * definition, a series toggled) moves from what is on screen to the new\n * scene on springs; one landing mid-flight keeps every node's speed. A\n * resize snaps, since watching bars slide while a window is dragged helps no\n * one. Reduced motion always snaps.\n */\nexport function useAnimatedScene(\n\tscene: ChartScene,\n\ttrigger: unknown,\n\ttiming: ChartTiming | null,\n): ChartScene {\n\tconst [shown, setShown] = React.useState(scene);\n\tconst triggerRef = React.useRef(trigger);\n\tconst storeRef = React.useRef<{\n\t\tstore: MotionStore;\n\t\ttiming: ChartTiming;\n\t} | null>(null);\n\n\tReact.useLayoutEffect(() => {\n\t\tconst changed = triggerRef.current !== trigger;\n\t\ttriggerRef.current = trigger;\n\t\tif (timing && storeRef.current?.timing !== timing) {\n\t\t\tstoreRef.current = { store: createMotionStore(scene, timing), timing };\n\t\t}\n\t\tconst store = storeRef.current?.store;\n\t\tif (!changed || !timing || !store || shouldReduceMotion()) {\n\t\t\tstore?.snap(scene);\n\t\t\tsetShown(scene);\n\t\t\treturn;\n\t\t}\n\t\tstore.retarget(scene, performance.now());\n\t\tfunction tick() {\n\t\t\tconst now = performance.now();\n\t\t\tif (store?.settled(now)) {\n\t\t\t\tcancelFrame(tick);\n\t\t\t\tsetShown(scene);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsetShown(store?.frame(now) ?? scene);\n\t\t}\n\t\ttick();\n\t\tframe.update(tick, true);\n\t\treturn () => cancelFrame(tick);\n\t}, [scene, trigger, timing]);\n\n\treturn shown;\n}\n"],
5
- "mappings": "AAAA,SAAS,aAAa,OAAO,0BAA0B;AACvD,YAAY,WAAW;AACvB;AAAA,EACC;AAAA,OAEM;AAWA,SAAS,iBACf,OACA,SACA,QACa;AACb,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,KAAK;AAC9C,QAAM,aAAa,MAAM,OAAO,OAAO;AACvC,QAAM,WAAW,MAAM,OAGb,IAAI;AAEd,QAAM,gBAAgB,MAAM;AAC3B,UAAM,UAAU,WAAW,YAAY;AACvC,eAAW,UAAU;AACrB,QAAI,UAAU,SAAS,SAAS,WAAW,QAAQ;AAClD,eAAS,UAAU,EAAE,OAAO,kBAAkB,OAAO,MAAM,GAAG,OAAO;AAAA,IACtE;AACA,UAAM,QAAQ,SAAS,SAAS;AAChC,QAAI,CAAC,WAAW,CAAC,UAAU,CAAC,SAAS,mBAAmB,GAAG;AAC1D,aAAO,KAAK,KAAK;AACjB,eAAS,KAAK;AACd;AAAA,IACD;AACA,UAAM,SAAS,OAAO,YAAY,IAAI,CAAC;AACvC,aAAS,OAAO;AACf,YAAM,MAAM,YAAY,IAAI;AAC5B,UAAI,OAAO,QAAQ,GAAG,GAAG;AACxB,oBAAY,IAAI;AAChB,iBAAS,KAAK;AACd;AAAA,MACD;AACA,eAAS,OAAO,MAAM,GAAG,KAAK,KAAK;AAAA,IACpC;AACA,SAAK;AACL,UAAM,OAAO,MAAM,IAAI;AACvB,WAAO,MAAM,YAAY,IAAI;AAAA,EAC9B,GAAG,CAAC,OAAO,SAAS,MAAM,CAAC;AAE3B,SAAO;AACR;",
4
+ "sourcesContent": ["import { cancelFrame, frame, shouldReduceMotion } from \"@voila.dev/motion\";\nimport * as React from \"react\";\nimport {\n\tcreateMotionStore,\n\ttype MotionStore,\n} from \"#/core/motion/motion-store.ts\";\nimport type { ChartTiming } from \"#/core/motion/timing.ts\";\nimport type { ChartScene } from \"#/core/types.ts\";\n\n/**\n * The scene to draw this frame. A data change (`trigger` changed: a new\n * definition, a series toggled) moves from what is on screen to the new\n * scene on springs; one landing mid-flight keeps every node's speed. A\n * resize snaps, since watching bars slide while a window is dragged helps no\n * one. Reduced motion always snaps.\n */\nexport function useAnimatedScene(\n\tscene: ChartScene,\n\ttrigger: unknown,\n\ttiming: ChartTiming | null,\n): ChartScene {\n\tconst [shown, setShown] = React.useState(scene);\n\tconst triggerRef = React.useRef(trigger);\n\t// The first render may play once: lines that `enter: \"draw\"` trace themselves in.\n\tconst introduced = React.useRef(false);\n\tconst introStart = React.useRef<number | null>(null);\n\tconst storeRef = React.useRef<{\n\t\tstore: MotionStore;\n\t\ttiming: ChartTiming;\n\t} | null>(null);\n\n\tReact.useLayoutEffect(() => {\n\t\tconst changed = triggerRef.current !== trigger;\n\t\ttriggerRef.current = trigger;\n\t\tif (timing && storeRef.current?.timing !== timing) {\n\t\t\tstoreRef.current = { store: createMotionStore(scene, timing), timing };\n\t\t}\n\t\tconst store = storeRef.current?.store;\n\t\tconst still = !timing || !store || shouldReduceMotion();\n\t\tlet intro = false;\n\t\tif (!still && !changed && !introduced.current) {\n\t\t\tconst start = introStart.current ?? performance.now();\n\t\t\t// A re-render mid-intro (the width measured after mount, fonts loaded): the\n\t\t\t// new geometry, drawn from where the intro already is, never from scratch.\n\t\t\tif (introStart.current !== null) store.snap(scene);\n\t\t\tintro = store.introduce(start);\n\t\t\tif (intro) introStart.current = start;\n\t\t}\n\t\tif (still || (!changed && !intro)) {\n\t\t\tintroduced.current = true;\n\t\t\tstore?.snap(scene);\n\t\t\tsetShown(scene);\n\t\t\treturn;\n\t\t}\n\t\tif (changed) store.retarget(scene, performance.now());\n\t\tfunction tick() {\n\t\t\tconst now = performance.now();\n\t\t\tif (store?.settled(now)) {\n\t\t\t\t// Only once it has played: StrictMode's rerun of the effect starts it again.\n\t\t\t\tintroduced.current = true;\n\t\t\t\tcancelFrame(tick);\n\t\t\t\tsetShown(scene);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsetShown(store?.frame(now) ?? scene);\n\t\t}\n\t\ttick();\n\t\tframe.update(tick, true);\n\t\treturn () => cancelFrame(tick);\n\t}, [scene, trigger, timing]);\n\n\treturn shown;\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa,OAAO,0BAA0B;AACvD,YAAY,WAAW;AACvB;AAAA,EACC;AAAA,OAEM;AAWA,SAAS,iBACf,OACA,SACA,QACa;AACb,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,KAAK;AAC9C,QAAM,aAAa,MAAM,OAAO,OAAO;AAEvC,QAAM,aAAa,MAAM,OAAO,KAAK;AACrC,QAAM,aAAa,MAAM,OAAsB,IAAI;AACnD,QAAM,WAAW,MAAM,OAGb,IAAI;AAEd,QAAM,gBAAgB,MAAM;AAC3B,UAAM,UAAU,WAAW,YAAY;AACvC,eAAW,UAAU;AACrB,QAAI,UAAU,SAAS,SAAS,WAAW,QAAQ;AAClD,eAAS,UAAU,EAAE,OAAO,kBAAkB,OAAO,MAAM,GAAG,OAAO;AAAA,IACtE;AACA,UAAM,QAAQ,SAAS,SAAS;AAChC,UAAM,QAAQ,CAAC,UAAU,CAAC,SAAS,mBAAmB;AACtD,QAAI,QAAQ;AACZ,QAAI,CAAC,SAAS,CAAC,WAAW,CAAC,WAAW,SAAS;AAC9C,YAAM,QAAQ,WAAW,WAAW,YAAY,IAAI;AAGpD,UAAI,WAAW,YAAY,KAAM,OAAM,KAAK,KAAK;AACjD,cAAQ,MAAM,UAAU,KAAK;AAC7B,UAAI,MAAO,YAAW,UAAU;AAAA,IACjC;AACA,QAAI,SAAU,CAAC,WAAW,CAAC,OAAQ;AAClC,iBAAW,UAAU;AACrB,aAAO,KAAK,KAAK;AACjB,eAAS,KAAK;AACd;AAAA,IACD;AACA,QAAI,QAAS,OAAM,SAAS,OAAO,YAAY,IAAI,CAAC;AACpD,aAAS,OAAO;AACf,YAAM,MAAM,YAAY,IAAI;AAC5B,UAAI,OAAO,QAAQ,GAAG,GAAG;AAExB,mBAAW,UAAU;AACrB,oBAAY,IAAI;AAChB,iBAAS,KAAK;AACd;AAAA,MACD;AACA,eAAS,OAAO,MAAM,GAAG,KAAK,KAAK;AAAA,IACpC;AACA,SAAK;AACL,UAAM,OAAO,MAAM,IAAI;AACvB,WAAO,MAAM,YAAY,IAAI;AAAA,EAC9B,GAAG,CAAC,OAAO,SAAS,MAAM,CAAC;AAE3B,SAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"voronoi.d.ts","sourceRoot":"","sources":["../../src/voronoi/voronoi.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,aAAa,EACb,SAAS,EACT,UAAU,EAEV,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,cAAc,CAAC,MAAM;IACrC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACzD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAC7B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,GAC7B,SAAS,CAwDX"}
1
+ {"version":3,"file":"voronoi.d.ts","sourceRoot":"","sources":["../../src/voronoi/voronoi.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,aAAa,EACb,SAAS,EACT,UAAU,EAEV,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,cAAc,CAAC,MAAM;IACrC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACzD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAC7B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,GAC7B,SAAS,CAyDX"}
@@ -1,3 +1,4 @@
1
+ import { mixPath } from "@voila.dev/motion";
1
2
  import { Delaunay } from "d3-delaunay";
2
3
  import { isPlaceable, readChannel } from "../core/channel.js";
3
4
  import { markId, valueOn } from "../core/marks/shared.js";
@@ -42,6 +43,7 @@ function voronoi(data, options) {
42
43
  key: `${id}:${dataIndex}`,
43
44
  role: "mark",
44
45
  d: diagram.renderCell(cell) ?? "",
46
+ morph: mixPath,
45
47
  paint: options.fill ? {
46
48
  fill: options.fill(data[dataIndex], dataIndex),
47
49
  stroke: context.theme.background,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/voronoi/voronoi.ts"],
4
- "sourcesContent": ["import { Delaunay } from \"d3-delaunay\";\nimport { isPlaceable, readChannel } from \"#/core/channel.ts\";\nimport { markId, valueOn } from \"#/core/marks/shared.ts\";\nimport type {\n\tChartAccessor,\n\tChartMark,\n\tChartValue,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nexport interface VoronoiOptions<TDatum> {\n\treadonly id?: string;\n\treadonly x: ChartAccessor<TDatum, ChartValue>;\n\treadonly y: ChartAccessor<TDatum, ChartValue>;\n\treadonly stroke?: string;\n\t/** Fill each cell, by its own colour, instead of drawing the borders only. */\n\treadonly fill?: (datum: TDatum, index: number) => string;\n}\n\n/**\n * Each point's cell: the area closer to it than to any other. Clipped to the\n * plot. The borders alone read as a mesh over a scatter; filled, the cells\n * become territories.\n */\nexport function voronoi<TDatum>(\n\tdata: ReadonlyArray<TDatum>,\n\toptions: VoronoiOptions<TDatum>,\n): ChartMark {\n\tconst xs = readChannel(data, options.x);\n\tconst ys = readChannel(data, options.y);\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"cartesian\",\n\t\tchannels: {\n\t\t\tx: { values: xs.filter(isPlaceable) },\n\t\t\ty: { values: ys.filter(isPlaceable) },\n\t\t},\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"voronoi\", context);\n\t\t\tconst { x: xScale, y: yScale } = context.scales;\n\t\t\tif (xScale === undefined || yScale === undefined) {\n\t\t\t\treturn { nodes: [] };\n\t\t\t}\n\t\t\tconst { plot } = context;\n\t\t\tconst indices: number[] = [];\n\t\t\tconst pixels: Array<[number, number]> = [];\n\t\t\tfor (const [index, raw] of xs.entries()) {\n\t\t\t\tconst x = valueOn(xScale, raw);\n\t\t\t\tconst y = valueOn(yScale, ys[index]);\n\t\t\t\tif (x !== undefined && y !== undefined) {\n\t\t\t\t\tindices.push(index);\n\t\t\t\t\tpixels.push([xScale.center(x), yScale.center(y)]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (pixels.length === 0) {\n\t\t\t\treturn { nodes: [] };\n\t\t\t}\n\t\t\tconst diagram = Delaunay.from(pixels).voronoi([\n\t\t\t\tplot.x,\n\t\t\t\tplot.y,\n\t\t\t\tplot.x + plot.width,\n\t\t\t\tplot.y + plot.height,\n\t\t\t]);\n\t\t\tconst nodes: SceneNode[] = indices.map((dataIndex, cell) => ({\n\t\t\t\tkind: \"path\",\n\t\t\t\tkey: `${id}:${dataIndex}`,\n\t\t\t\trole: \"mark\",\n\t\t\t\td: diagram.renderCell(cell) ?? \"\",\n\t\t\t\tpaint: options.fill\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tfill: options.fill(data[dataIndex], dataIndex),\n\t\t\t\t\t\t\tstroke: context.theme.background,\n\t\t\t\t\t\t\tstrokeWidth: 1,\n\t\t\t\t\t\t}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tfill: \"none\",\n\t\t\t\t\t\t\tstroke: options.stroke ?? context.theme.grid,\n\t\t\t\t\t\t\tstrokeWidth: 1,\n\t\t\t\t\t\t},\n\t\t\t}));\n\t\t\treturn { nodes };\n\t\t},\n\t};\n}\n"],
5
- "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,aAAa,mBAAmB;AACzC,SAAS,QAAQ,eAAe;AAsBzB,SAAS,QACf,MACA,SACY;AACZ,QAAM,KAAK,YAAY,MAAM,QAAQ,CAAC;AACtC,QAAM,KAAK,YAAY,MAAM,QAAQ,CAAC;AACtC,SAAO;AAAA,IACN,IAAI,QAAQ;AAAA,IACZ,YAAY;AAAA,IACZ,UAAU;AAAA,MACT,GAAG,EAAE,QAAQ,GAAG,OAAO,WAAW,EAAE;AAAA,MACpC,GAAG,EAAE,QAAQ,GAAG,OAAO,WAAW,EAAE;AAAA,IACrC;AAAA,IACA,OAAO,SAAS;AACf,YAAM,KAAK,OAAO,QAAQ,IAAI,WAAW,OAAO;AAChD,YAAM,EAAE,GAAG,QAAQ,GAAG,OAAO,IAAI,QAAQ;AACzC,UAAI,WAAW,UAAa,WAAW,QAAW;AACjD,eAAO,EAAE,OAAO,CAAC,EAAE;AAAA,MACpB;AACA,YAAM,EAAE,KAAK,IAAI;AACjB,YAAM,UAAoB,CAAC;AAC3B,YAAM,SAAkC,CAAC;AACzC,iBAAW,CAAC,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG;AACxC,cAAM,IAAI,QAAQ,QAAQ,GAAG;AAC7B,cAAM,IAAI,QAAQ,QAAQ,GAAG,KAAK,CAAC;AACnC,YAAI,MAAM,UAAa,MAAM,QAAW;AACvC,kBAAQ,KAAK,KAAK;AAClB,iBAAO,KAAK,CAAC,OAAO,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC;AAAA,QACjD;AAAA,MACD;AACA,UAAI,OAAO,WAAW,GAAG;AACxB,eAAO,EAAE,OAAO,CAAC,EAAE;AAAA,MACpB;AACA,YAAM,UAAU,SAAS,KAAK,MAAM,EAAE,QAAQ;AAAA,QAC7C,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK,IAAI,KAAK;AAAA,QACd,KAAK,IAAI,KAAK;AAAA,MACf,CAAC;AACD,YAAM,QAAqB,QAAQ,IAAI,CAAC,WAAW,UAAU;AAAA,QAC5D,MAAM;AAAA,QACN,KAAK,GAAG,EAAE,IAAI,SAAS;AAAA,QACvB,MAAM;AAAA,QACN,GAAG,QAAQ,WAAW,IAAI,KAAK;AAAA,QAC/B,OAAO,QAAQ,OACZ;AAAA,UACA,MAAM,QAAQ,KAAK,KAAK,SAAS,GAAG,SAAS;AAAA,UAC7C,QAAQ,QAAQ,MAAM;AAAA,UACtB,aAAa;AAAA,QACd,IACC;AAAA,UACA,MAAM;AAAA,UACN,QAAQ,QAAQ,UAAU,QAAQ,MAAM;AAAA,UACxC,aAAa;AAAA,QACd;AAAA,MACH,EAAE;AACF,aAAO,EAAE,MAAM;AAAA,IAChB;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["import { mixPath } from \"@voila.dev/motion\";\nimport { Delaunay } from \"d3-delaunay\";\nimport { isPlaceable, readChannel } from \"#/core/channel.ts\";\nimport { markId, valueOn } from \"#/core/marks/shared.ts\";\nimport type {\n\tChartAccessor,\n\tChartMark,\n\tChartValue,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nexport interface VoronoiOptions<TDatum> {\n\treadonly id?: string;\n\treadonly x: ChartAccessor<TDatum, ChartValue>;\n\treadonly y: ChartAccessor<TDatum, ChartValue>;\n\treadonly stroke?: string;\n\t/** Fill each cell, by its own colour, instead of drawing the borders only. */\n\treadonly fill?: (datum: TDatum, index: number) => string;\n}\n\n/**\n * Each point's cell: the area closer to it than to any other. Clipped to the\n * plot. The borders alone read as a mesh over a scatter; filled, the cells\n * become territories.\n */\nexport function voronoi<TDatum>(\n\tdata: ReadonlyArray<TDatum>,\n\toptions: VoronoiOptions<TDatum>,\n): ChartMark {\n\tconst xs = readChannel(data, options.x);\n\tconst ys = readChannel(data, options.y);\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"cartesian\",\n\t\tchannels: {\n\t\t\tx: { values: xs.filter(isPlaceable) },\n\t\t\ty: { values: ys.filter(isPlaceable) },\n\t\t},\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"voronoi\", context);\n\t\t\tconst { x: xScale, y: yScale } = context.scales;\n\t\t\tif (xScale === undefined || yScale === undefined) {\n\t\t\t\treturn { nodes: [] };\n\t\t\t}\n\t\t\tconst { plot } = context;\n\t\t\tconst indices: number[] = [];\n\t\t\tconst pixels: Array<[number, number]> = [];\n\t\t\tfor (const [index, raw] of xs.entries()) {\n\t\t\t\tconst x = valueOn(xScale, raw);\n\t\t\t\tconst y = valueOn(yScale, ys[index]);\n\t\t\t\tif (x !== undefined && y !== undefined) {\n\t\t\t\t\tindices.push(index);\n\t\t\t\t\tpixels.push([xScale.center(x), yScale.center(y)]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (pixels.length === 0) {\n\t\t\t\treturn { nodes: [] };\n\t\t\t}\n\t\t\tconst diagram = Delaunay.from(pixels).voronoi([\n\t\t\t\tplot.x,\n\t\t\t\tplot.y,\n\t\t\t\tplot.x + plot.width,\n\t\t\t\tplot.y + plot.height,\n\t\t\t]);\n\t\t\tconst nodes: SceneNode[] = indices.map((dataIndex, cell) => ({\n\t\t\t\tkind: \"path\",\n\t\t\t\tkey: `${id}:${dataIndex}`,\n\t\t\t\trole: \"mark\",\n\t\t\t\td: diagram.renderCell(cell) ?? \"\",\n\t\t\t\tmorph: mixPath,\n\t\t\t\tpaint: options.fill\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tfill: options.fill(data[dataIndex], dataIndex),\n\t\t\t\t\t\t\tstroke: context.theme.background,\n\t\t\t\t\t\t\tstrokeWidth: 1,\n\t\t\t\t\t\t}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tfill: \"none\",\n\t\t\t\t\t\t\tstroke: options.stroke ?? context.theme.grid,\n\t\t\t\t\t\t\tstrokeWidth: 1,\n\t\t\t\t\t\t},\n\t\t\t}));\n\t\t\treturn { nodes };\n\t\t},\n\t};\n}\n"],
5
+ "mappings": "AAAA,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,aAAa,mBAAmB;AACzC,SAAS,QAAQ,eAAe;AAsBzB,SAAS,QACf,MACA,SACY;AACZ,QAAM,KAAK,YAAY,MAAM,QAAQ,CAAC;AACtC,QAAM,KAAK,YAAY,MAAM,QAAQ,CAAC;AACtC,SAAO;AAAA,IACN,IAAI,QAAQ;AAAA,IACZ,YAAY;AAAA,IACZ,UAAU;AAAA,MACT,GAAG,EAAE,QAAQ,GAAG,OAAO,WAAW,EAAE;AAAA,MACpC,GAAG,EAAE,QAAQ,GAAG,OAAO,WAAW,EAAE;AAAA,IACrC;AAAA,IACA,OAAO,SAAS;AACf,YAAM,KAAK,OAAO,QAAQ,IAAI,WAAW,OAAO;AAChD,YAAM,EAAE,GAAG,QAAQ,GAAG,OAAO,IAAI,QAAQ;AACzC,UAAI,WAAW,UAAa,WAAW,QAAW;AACjD,eAAO,EAAE,OAAO,CAAC,EAAE;AAAA,MACpB;AACA,YAAM,EAAE,KAAK,IAAI;AACjB,YAAM,UAAoB,CAAC;AAC3B,YAAM,SAAkC,CAAC;AACzC,iBAAW,CAAC,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG;AACxC,cAAM,IAAI,QAAQ,QAAQ,GAAG;AAC7B,cAAM,IAAI,QAAQ,QAAQ,GAAG,KAAK,CAAC;AACnC,YAAI,MAAM,UAAa,MAAM,QAAW;AACvC,kBAAQ,KAAK,KAAK;AAClB,iBAAO,KAAK,CAAC,OAAO,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC;AAAA,QACjD;AAAA,MACD;AACA,UAAI,OAAO,WAAW,GAAG;AACxB,eAAO,EAAE,OAAO,CAAC,EAAE;AAAA,MACpB;AACA,YAAM,UAAU,SAAS,KAAK,MAAM,EAAE,QAAQ;AAAA,QAC7C,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK,IAAI,KAAK;AAAA,QACd,KAAK,IAAI,KAAK;AAAA,MACf,CAAC;AACD,YAAM,QAAqB,QAAQ,IAAI,CAAC,WAAW,UAAU;AAAA,QAC5D,MAAM;AAAA,QACN,KAAK,GAAG,EAAE,IAAI,SAAS;AAAA,QACvB,MAAM;AAAA,QACN,GAAG,QAAQ,WAAW,IAAI,KAAK;AAAA,QAC/B,OAAO;AAAA,QACP,OAAO,QAAQ,OACZ;AAAA,UACA,MAAM,QAAQ,KAAK,KAAK,SAAS,GAAG,SAAS;AAAA,UAC7C,QAAQ,QAAQ,MAAM;AAAA,UACtB,aAAa;AAAA,QACd,IACC;AAAA,UACA,MAAM;AAAA,UACN,QAAQ,QAAQ,UAAU,QAAQ,MAAM;AAAA,UACxC,aAAa;AAAA,QACd;AAAA,MACH,EAAE;AACF,aAAO,EAAE,MAAM;AAAA,IAChB;AAAA,EACD;AACD;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voila.dev/chart",
3
- "version": "5.0.114",
3
+ "version": "5.0.118",
4
4
  "type": "module",
5
5
  "description": "Charts as data: a definition of marks, channels and scales, drawn to SVG or Canvas, keyboard and screen-reader accessible. The core has no dependencies; the heavy layouts (geo, hierarchy, sankey…) sit behind their own subpaths.",
6
6
  "license": "MIT",
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "sideEffects": false,
66
66
  "dependencies": {
67
- "@voila.dev/motion": "5.0.114",
67
+ "@voila.dev/motion": "5.0.118",
68
68
  "d3-contour": "4.0.2",
69
69
  "d3-delaunay": "6.0.4",
70
70
  "d3-force": "3.0.0",
@@ -92,7 +92,11 @@ function fillAndStroke(
92
92
  context.lineWidth = paint.strokeWidth ?? 1;
93
93
  context.lineCap = paint.strokeLinecap ?? "butt";
94
94
  context.lineJoin = paint.strokeLinejoin ?? "miter";
95
- context.setLineDash(dashes(paint.strokeDasharray));
95
+ context.setLineDash(
96
+ paint.drawn
97
+ ? [paint.drawn.fraction * paint.drawn.length, paint.drawn.length]
98
+ : dashes(paint.strokeDasharray),
99
+ );
96
100
  draw("stroke");
97
101
  context.setLineDash([]);
98
102
  }
@@ -1,3 +1,4 @@
1
+ import { mixPath } from "@voila.dev/motion";
1
2
  import { contourDensity } from "d3-contour";
2
3
  import { isPlaceable, readChannel } from "#/core/channel.ts";
3
4
  import { markId, valueOn } from "#/core/marks/shared.ts";
@@ -72,6 +73,7 @@ export function density2d<TDatum>(
72
73
  key: `${id}:${index}`,
73
74
  role: "mark",
74
75
  d: multiPolygonPath(level.coordinates as never, plot.x, plot.y),
76
+ morph: mixPath,
75
77
  paint: options.stroke
76
78
  ? {
77
79
  fill: "none",
@@ -14,7 +14,8 @@ import {
14
14
  tips,
15
15
  } from "#/core/marks/shared.ts";
16
16
  import { stackSegments } from "#/core/marks/stack.ts";
17
- import { areaPath, type ChartXY, linePath } from "#/core/paths.ts";
17
+ import { POINTS_MOTION, pointsPath } from "#/core/motion/points-motion.ts";
18
+ import { type ChartXY, linePath } from "#/core/paths.ts";
18
19
  import { categoryKey } from "#/core/scales/discrete.ts";
19
20
  import type {
20
21
  ChartAccessor,
@@ -22,6 +23,7 @@ import type {
22
23
  ChartMark,
23
24
  ChartPoint,
24
25
  ChartValue,
26
+ GeometryPoint,
25
27
  SceneNode,
26
28
  } from "#/core/types.ts";
27
29
 
@@ -135,21 +137,23 @@ function areaMark<TDatum>(
135
137
  data.length,
136
138
  seriesOf,
137
139
  ).values()) {
138
- const runs: Array<{ top: ChartXY[]; bottom: ChartXY[] }> = [
139
- { top: [], bottom: [] },
140
- ];
140
+ const runs: GeometryPoint[][] = [[]];
141
141
  for (const index of indices) {
142
142
  const position = positions[index];
143
143
  const edge = edges[index];
144
144
  if (!isPlaceable(position) || edge === undefined) {
145
- runs.push({ top: [], bottom: [] });
145
+ runs.push([]);
146
146
  continue;
147
147
  }
148
148
  const along = positionScale.center(position);
149
149
  const top = place(along, edge.high);
150
- const run = runs[runs.length - 1];
151
- run.top.push(top);
152
- run.bottom.push(place(along, edge.low));
150
+ const bottom = place(along, edge.low);
151
+ runs[runs.length - 1].push({
152
+ key: categoryKey(position),
153
+ ...top,
154
+ x0: bottom.x,
155
+ y0: bottom.y,
156
+ });
153
157
  if (!tips(options.tip, data[index], index)) {
154
158
  continue;
155
159
  }
@@ -172,15 +176,23 @@ function areaMark<TDatum>(
172
176
  value: vertical ? context.formatY(value) : context.formatX(value),
173
177
  });
174
178
  }
175
- const shaped = runs.filter((run) => run.top.length > 0);
179
+ const shaped = runs.filter((run) => run.length > 0);
180
+ const area = {
181
+ kind: "points" as const,
182
+ runs: shaped,
183
+ shape: "area" as const,
184
+ curve: options.curve,
185
+ };
186
+ const enter = options.enter ?? "grow";
176
187
  nodes.push({
177
188
  kind: "path",
178
189
  key: `${id}:area:${series.key}`,
179
190
  series: series.key,
180
191
  role: "mark",
181
- d: shaped
182
- .map((run) => areaPath(run.top, run.bottom, options.curve))
183
- .join(""),
192
+ d: pointsPath(area),
193
+ geometry: area,
194
+ motion: POINTS_MOTION,
195
+ enter,
184
196
  paint: paint({
185
197
  fill: series.color,
186
198
  fillOpacity: options.fillOpacity ?? DEFAULT_FILL_OPACITY,
@@ -193,7 +205,10 @@ function areaMark<TDatum>(
193
205
  key: `${id}:line:${series.key}`,
194
206
  series: series.key,
195
207
  role: "mark",
196
- d: shaped.map((run) => linePath(run.top, options.curve)).join(""),
208
+ d: shaped.map((run) => linePath(run, options.curve)).join(""),
209
+ geometry: { ...area, shape: "line" },
210
+ motion: POINTS_MOTION,
211
+ enter,
197
212
  paint: paint({
198
213
  fill: "none",
199
214
  stroke: series.color,
@@ -198,6 +198,8 @@ function barMark<TDatum>(
198
198
  role: "mark",
199
199
  ...rect,
200
200
  corners: corners(vertical, bar, radius, explicit),
201
+ baseline: { axis: vertical ? "y" : "x", at: lowPixel },
202
+ enter: options.enter ?? "grow",
201
203
  paint: paint(
202
204
  options.projected?.(data[bar.index], bar.index)
203
205
  ? {
@@ -9,13 +9,15 @@ import {
9
9
  tips,
10
10
  valueOn,
11
11
  } from "#/core/marks/shared.ts";
12
- import { type ChartXY, linePath } from "#/core/paths.ts";
12
+ import { POINTS_MOTION, pointsPath } from "#/core/motion/points-motion.ts";
13
+ import { categoryKey } from "#/core/scales/discrete.ts";
13
14
  import type {
14
15
  ChartAccessor,
15
16
  ChartCurve,
16
17
  ChartMark,
17
18
  ChartPoint,
18
19
  ChartValue,
20
+ GeometryPoint,
19
21
  SceneNode,
20
22
  } from "#/core/types.ts";
21
23
 
@@ -98,7 +100,7 @@ function lineMark<TDatum>(
98
100
  seriesOf,
99
101
  ).values()) {
100
102
  // A gap in the data is a gap in the line, not a dive to zero.
101
- const runs: ChartXY[][] = [[]];
103
+ const runs: GeometryPoint[][] = [[]];
102
104
  for (const index of indices) {
103
105
  const position = positions[index];
104
106
  const value = valueOn(valueScale, values[index]);
@@ -111,7 +113,7 @@ function lineMark<TDatum>(
111
113
  const xy = vertical
112
114
  ? { x: along, y: across }
113
115
  : { x: across, y: along };
114
- runs[runs.length - 1].push(xy);
116
+ runs[runs.length - 1].push({ key: categoryKey(position), ...xy });
115
117
  if (tips(options.tip, data[index], index)) {
116
118
  points.push({
117
119
  key: `${id}:${index}`,
@@ -149,16 +151,21 @@ function lineMark<TDatum>(
149
151
  });
150
152
  }
151
153
  }
152
- const d = runs
153
- .map((run) => linePath(run, options.curve))
154
- .filter((part) => part !== "")
155
- .join("");
154
+ const geometry = {
155
+ kind: "points" as const,
156
+ runs: runs.filter((run) => run.length > 1),
157
+ shape: "line" as const,
158
+ curve: options.curve,
159
+ };
156
160
  lines.push({
157
161
  kind: "path",
158
162
  key: `${id}:line:${series.key}`,
159
163
  series: series.key,
160
164
  role: "mark",
161
- d,
165
+ d: pointsPath(geometry),
166
+ geometry,
167
+ motion: POINTS_MOTION,
168
+ enter: options.enter,
162
169
  paint: paint({
163
170
  stroke: series.color,
164
171
  strokeWidth: options.strokeWidth ?? DEFAULT_STROKE_WIDTH,
@@ -12,6 +12,8 @@ import {
12
12
  seriesResolver,
13
13
  tips,
14
14
  } from "#/core/marks/shared.ts";
15
+ import { ARC_MOTION } from "#/core/motion/arc-motion.ts";
16
+ import { POINTS_MOTION } from "#/core/motion/points-motion.ts";
15
17
  import {
16
18
  arcPath,
17
19
  type ChartXY,
@@ -181,6 +183,9 @@ export function arc<TDatum>(
181
183
  series: series.key,
182
184
  role: "mark",
183
185
  d: arcPath(shape),
186
+ geometry: { kind: "arc", ...shape },
187
+ motion: ARC_MOTION,
188
+ enter: options.enter ?? "grow",
184
189
  paint: paint({
185
190
  fill: series.color,
186
191
  stroke: options.stroke ?? context.theme.background,
@@ -306,6 +311,9 @@ export function radialBar<TDatum>(
306
311
  series: series.key,
307
312
  role: "mark",
308
313
  d: arcPath(shape),
314
+ geometry: { kind: "arc", ...shape },
315
+ motion: ARC_MOTION,
316
+ enter: options.enter ?? "grow",
309
317
  paint: paint({ fill: series.color, opacity: options.opacity }),
310
318
  });
311
319
  if (!tips(options.tip, data[index], index)) {
@@ -532,16 +540,25 @@ export function radar<TDatum>(
532
540
  }
533
541
  }
534
542
  for (const [key, polygon] of polygons) {
543
+ const geometry = {
544
+ kind: "points" as const,
545
+ shape: "polygon" as const,
546
+ runs: [
547
+ polygon.vertices.map((vertex, spoke) => ({
548
+ key: categoryKey(axes[spoke] as ChartValue),
549
+ ...(vertex ?? { x: frame.cx, y: frame.cy }),
550
+ })),
551
+ ],
552
+ };
535
553
  nodes.push({
536
554
  kind: "path",
537
555
  key: `${id}:shape:${key}`,
538
556
  series: key,
539
557
  role: "mark",
540
- d: polygonPath(
541
- polygon.vertices.map(
542
- (vertex) => vertex ?? { x: frame.cx, y: frame.cy },
543
- ),
544
- ),
558
+ d: polygonPath(geometry.runs[0]),
559
+ geometry,
560
+ motion: POINTS_MOTION,
561
+ enter: options.enter,
545
562
  paint: paint({
546
563
  fill: polygon.color,
547
564
  fillOpacity: options.fillOpacity ?? 0.2,
@@ -8,6 +8,7 @@ import { categoryKey } from "#/core/scales/discrete.ts";
8
8
  import type {
9
9
  ChartAccessor,
10
10
  ChartChannel,
11
+ ChartEnter,
11
12
  ChartMarkContext,
12
13
  ChartPaint,
13
14
  ChartPositionScale,
@@ -29,6 +30,13 @@ export interface ChartMarkOptions<TDatum> {
29
30
  */
30
31
  readonly tip?: boolean | ((datum: TDatum, index: number) => boolean);
31
32
  readonly opacity?: number;
33
+ /**
34
+ * How the mark appears when an update adds it: "grow" from its baseline
35
+ * or neighbour (the default for bars, areas and slices), "fade" (the
36
+ * default otherwise), "none". A line can also "draw": trace itself in on
37
+ * the first render.
38
+ */
39
+ readonly enter?: ChartEnter;
32
40
  }
33
41
 
34
42
  export function markId(