@voila.dev/chart 5.0.113 → 5.0.117
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/canvas/paint-scene.d.ts.map +1 -1
- package/dist/canvas/paint-scene.js +3 -1
- package/dist/canvas/paint-scene.js.map +2 -2
- package/dist/contour/density.d.ts.map +1 -1
- package/dist/contour/density.js +2 -0
- package/dist/contour/density.js.map +2 -2
- package/dist/core/marks/area.d.ts.map +1 -1
- package/dist/core/marks/area.js +27 -11
- package/dist/core/marks/area.js.map +2 -2
- package/dist/core/marks/bar.d.ts.map +1 -1
- package/dist/core/marks/bar.js +2 -0
- package/dist/core/marks/bar.js.map +2 -2
- package/dist/core/marks/line.d.ts.map +1 -1
- package/dist/core/marks/line.js +13 -4
- package/dist/core/marks/line.js.map +2 -2
- package/dist/core/marks/polar.d.ts.map +1 -1
- package/dist/core/marks/polar.js +22 -5
- package/dist/core/marks/polar.js.map +2 -2
- package/dist/core/marks/shared.d.ts +8 -1
- package/dist/core/marks/shared.d.ts.map +1 -1
- package/dist/core/marks/shared.js.map +2 -2
- package/dist/core/marks/stats/difference.d.ts.map +1 -1
- package/dist/core/marks/stats/difference.js +20 -7
- package/dist/core/marks/stats/difference.js.map +2 -2
- package/dist/core/motion/arc-motion.d.ts +9 -0
- package/dist/core/motion/arc-motion.d.ts.map +1 -0
- package/dist/core/motion/arc-motion.js +67 -0
- package/dist/core/motion/arc-motion.js.map +7 -0
- package/dist/core/motion/enter-exit.d.ts +6 -0
- package/dist/core/motion/enter-exit.d.ts.map +1 -0
- package/dist/core/motion/enter-exit.js +20 -0
- package/dist/core/motion/enter-exit.js.map +7 -0
- package/dist/core/motion/leaving.d.ts +12 -0
- package/dist/core/motion/leaving.d.ts.map +1 -0
- package/dist/core/motion/leaving.js +33 -0
- package/dist/core/motion/leaving.js.map +7 -0
- package/dist/core/motion/motion-store.d.ts +22 -0
- package/dist/core/motion/motion-store.d.ts.map +1 -0
- package/dist/core/motion/motion-store.js +325 -0
- package/dist/core/motion/motion-store.js.map +7 -0
- package/dist/core/motion/points-motion.d.ts +6 -0
- package/dist/core/motion/points-motion.d.ts.map +1 -0
- package/dist/core/motion/points-motion.js +140 -0
- package/dist/core/motion/points-motion.js.map +7 -0
- package/dist/core/motion/strings.d.ts +18 -0
- package/dist/core/motion/strings.d.ts.map +1 -0
- package/dist/core/motion/strings.js +34 -0
- package/dist/core/motion/strings.js.map +7 -0
- package/dist/core/motion/timing.d.ts +30 -0
- package/dist/core/motion/timing.d.ts.map +1 -0
- package/dist/core/motion/timing.js +25 -0
- package/dist/core/motion/timing.js.map +7 -0
- package/dist/core/motion/tween.d.ts +2 -0
- package/dist/core/motion/tween.d.ts.map +1 -1
- package/dist/core/motion/tween.js +1 -0
- package/dist/core/motion/tween.js.map +2 -2
- package/dist/core/types.d.ts +85 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/geo/geo.d.ts.map +1 -1
- package/dist/geo/geo.js +2 -0
- package/dist/geo/geo.js.map +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +2 -2
- package/dist/react/chart.d.ts +7 -2
- package/dist/react/chart.d.ts.map +1 -1
- package/dist/react/chart.js +12 -7
- package/dist/react/chart.js.map +2 -2
- package/dist/react/svg-scene.d.ts.map +1 -1
- package/dist/react/svg-scene.js +3 -1
- package/dist/react/svg-scene.js.map +2 -2
- package/dist/react/use-animated-scene.d.ts +6 -5
- package/dist/react/use-animated-scene.d.ts.map +1 -1
- package/dist/react/use-animated-scene.js +36 -24
- package/dist/react/use-animated-scene.js.map +2 -2
- package/dist/voronoi/voronoi.d.ts.map +1 -1
- package/dist/voronoi/voronoi.js +2 -0
- package/dist/voronoi/voronoi.js.map +2 -2
- package/package.json +2 -1
- package/src/canvas/paint-scene.ts +5 -1
- package/src/contour/density.ts +2 -0
- package/src/core/marks/area.ts +28 -13
- package/src/core/marks/bar.ts +2 -0
- package/src/core/marks/line.ts +15 -8
- package/src/core/marks/polar.ts +22 -5
- package/src/core/marks/shared.ts +8 -0
- package/src/core/marks/stats/difference.ts +28 -8
- package/src/core/motion/arc-motion.ts +113 -0
- package/src/core/motion/enter-exit.ts +44 -0
- package/src/core/motion/leaving.ts +55 -0
- package/src/core/motion/motion-store.ts +449 -0
- package/src/core/motion/points-motion.ts +190 -0
- package/src/core/motion/strings.ts +68 -0
- package/src/core/motion/timing.ts +63 -0
- package/src/core/motion/tween.ts +1 -1
- package/src/core/types.ts +98 -0
- package/src/geo/geo.ts +2 -0
- package/src/index.ts +9 -0
- package/src/react/chart.tsx +21 -9
- package/src/react/svg-scene.tsx +5 -1
- package/src/react/use-animated-scene.ts +49 -33
- package/src/voronoi/voronoi.ts +2 -0
package/dist/react/chart.js.map
CHANGED
|
@@ -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 { 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;\nconst DEFAULT_DURATION = 300;\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/** Tween a data update, in milliseconds; `false` snaps. Reduced motion always snaps. */\n\treadonly animate?: boolean | number;\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\tconst shown = useAnimatedScene(\n\t\tscene,\n\t\tanimationTrigger,\n\t\tanimate === false ? 0 : animate === true ? DEFAULT_DURATION : animate,\n\t);\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": "
|
|
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;
|
|
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"}
|
package/dist/react/svg-scene.js
CHANGED
|
@@ -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": "
|
|
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,10 +1,11 @@
|
|
|
1
|
+
import type { ChartTiming } from "../core/motion/timing.js";
|
|
1
2
|
import type { ChartScene } from "../core/types.js";
|
|
2
3
|
/**
|
|
3
4
|
* The scene to draw this frame. A data change (`trigger` changed: a new
|
|
4
|
-
* definition, a series toggled)
|
|
5
|
-
* scene
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* definition, a series toggled) moves from what is on screen to the new
|
|
6
|
+
* scene on springs; one landing mid-flight keeps every node's speed. A
|
|
7
|
+
* resize snaps, since watching bars slide while a window is dragged helps no
|
|
8
|
+
* one. Reduced motion always snaps.
|
|
8
9
|
*/
|
|
9
|
-
export declare function useAnimatedScene(scene: ChartScene, trigger: unknown,
|
|
10
|
+
export declare function useAnimatedScene(scene: ChartScene, trigger: unknown, timing: ChartTiming | null): ChartScene;
|
|
10
11
|
//# sourceMappingURL=use-animated-scene.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-animated-scene.d.ts","sourceRoot":"","sources":["../../src/react/use-animated-scene.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -1,38 +1,50 @@
|
|
|
1
|
+
import { cancelFrame, frame, shouldReduceMotion } from "@voila.dev/motion";
|
|
1
2
|
import * as React from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function useAnimatedScene(scene, trigger, duration) {
|
|
3
|
+
import {
|
|
4
|
+
createMotionStore
|
|
5
|
+
} from "../core/motion/motion-store.js";
|
|
6
|
+
function useAnimatedScene(scene, trigger, timing) {
|
|
7
7
|
const [shown, setShown] = React.useState(scene);
|
|
8
|
-
const shownRef = React.useRef(scene);
|
|
9
8
|
const triggerRef = React.useRef(trigger);
|
|
9
|
+
const introduced = React.useRef(false);
|
|
10
|
+
const introStart = React.useRef(null);
|
|
11
|
+
const storeRef = React.useRef(null);
|
|
10
12
|
React.useLayoutEffect(() => {
|
|
11
13
|
const changed = triggerRef.current !== trigger;
|
|
12
14
|
triggerRef.current = trigger;
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
+
if (timing && storeRef.current?.timing !== timing) {
|
|
16
|
+
storeRef.current = { store: createMotionStore(scene, timing), timing };
|
|
17
|
+
}
|
|
18
|
+
const store = storeRef.current?.store;
|
|
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;
|
|
29
|
+
store?.snap(scene);
|
|
15
30
|
setShown(scene);
|
|
16
31
|
return;
|
|
17
32
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const next = tweenScene(from, scene, easeOutCubic(t));
|
|
27
|
-
shownRef.current = next;
|
|
28
|
-
setShown(next);
|
|
29
|
-
if (t < 1) {
|
|
30
|
-
frame = requestAnimationFrame(step);
|
|
33
|
+
if (changed) store.retarget(scene, performance.now());
|
|
34
|
+
function tick() {
|
|
35
|
+
const now = performance.now();
|
|
36
|
+
if (store?.settled(now)) {
|
|
37
|
+
introduced.current = true;
|
|
38
|
+
cancelFrame(tick);
|
|
39
|
+
setShown(scene);
|
|
40
|
+
return;
|
|
31
41
|
}
|
|
42
|
+
setShown(store?.frame(now) ?? scene);
|
|
32
43
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
tick();
|
|
45
|
+
frame.update(tick, true);
|
|
46
|
+
return () => cancelFrame(tick);
|
|
47
|
+
}, [scene, trigger, timing]);
|
|
36
48
|
return shown;
|
|
37
49
|
}
|
|
38
50
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/react/use-animated-scene.ts"],
|
|
4
|
-
"sourcesContent": ["import * as React from \"react\";\nimport {
|
|
5
|
-
"mappings": "AAAA,
|
|
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":"
|
|
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"}
|
package/dist/voronoi/voronoi.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "5.0.117",
|
|
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,6 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"sideEffects": false,
|
|
66
66
|
"dependencies": {
|
|
67
|
+
"@voila.dev/motion": "5.0.117",
|
|
67
68
|
"d3-contour": "4.0.2",
|
|
68
69
|
"d3-delaunay": "6.0.4",
|
|
69
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(
|
|
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
|
}
|
package/src/contour/density.ts
CHANGED
|
@@ -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",
|
package/src/core/marks/area.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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(
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
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.
|
|
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:
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
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,
|
package/src/core/marks/bar.ts
CHANGED
|
@@ -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
|
? {
|
package/src/core/marks/line.ts
CHANGED
|
@@ -9,13 +9,15 @@ import {
|
|
|
9
9
|
tips,
|
|
10
10
|
valueOn,
|
|
11
11
|
} from "#/core/marks/shared.ts";
|
|
12
|
-
import {
|
|
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:
|
|
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
|
|
153
|
-
|
|
154
|
-
.filter((
|
|
155
|
-
|
|
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,
|