Animation

Springs that keep their speed, shapes that move by what they mean, morphing maps and tinting colours.

A data update moves from what is on screen to the new data. Every number moves on a spring, built on @voila.dev/motion. When an update lands mid-flight, each spring starts from where it is painted, at the speed it has. The motion bends instead of kinking.

Hit-testing, keyboard focus, the tooltip and the data table always read the data the chart is moving towards, never a frame in between.

Lines slide

A line is a list of points keyed by x. When the window moves, the points slide. A new month enters from the last point the line had. A month that drops off folds into its neighbour.

Arrow keys to move through the values, Enter to pin, Escape to leave.
Data: Bookings per month
monthBookings
January 202630
February 202641
March 202649
April 202642
May 202643
June 202640
July 202625
August 202624

Areas, stacked areas, radar charts and the difference mark move the same way.

Slices open and close

A slice moves by its angles, not by its path, so no frame ever folds back on itself. A new slice opens from the place where it will sit. A removed one closes onto its neighbour.

Arrow keys to move through the values, Enter to pin, Escape to leave.
Data: Projects per city
cityprojects
Paris51 (44.7%)
Lyon35 (30.7%)
Nantes28 (24.6%)

Timing

animate takes:

ValueMeans
trueThe default spring: 300 ms, no bounce.
falseSnap.
a numberA spring of that perceived duration, in milliseconds.
{ duration, bounce, stagger }A spring. bounce goes from 0 to 0.5.
{ type: "tween", duration, easing, stagger }A fixed curve: "easeOut", "easeInOut"…

The default spring never overshoots. A bar that passes its value shows a number the data doesn't have. Bounce is opt-in:

Arrow keys to move through the values, Enter to pin, Escape to leave.
Data: Hours per team, with bounce
teamHours
Web10
Mobile17
Data24
Design31
Ops38
Support15
<Chart definition={definition} ariaLabel="…" animate={{ duration: 500, bounce: 0.35 }} />

stagger puts milliseconds between marks, in focus order. The whole cascade is capped at 300 ms, so a long series doesn't make its last bar wait.

Arrow keys to move through the values, Enter to pin, Escape to leave.
Data: Hours per team, staggered
teamHours
Web10
Mobile17
Data24
Design31
Ops38
Support15

How a mark enters

The enter option of a mark says how it appears when an update adds it:

  • "grow": from its baseline (bars, areas) or its neighbour (slices). The default for bars, areas and slices.
  • "fade": fades in. The default for the other marks.
  • "none": there at once.
  • "draw": a line traces itself in, left to right, on the first render.
Arrow keys to move through the values, Enter to pin, Escape to leave.
Data: Bookings per month, drawn in
monthBookings
January 202630
February 202641
March 202649
April 202642
May 202643
June 202640
July 202625
August 202624
September 202626
October 202620
November 202629
December 202640
lineY(months, { x: "month", y: "bookings", enter: "draw" });

Data marks never linger after they leave: a bar shrinks to its baseline, and a slice closes. Guides are different: ticks, grid lines and their labels fade out where they stood.

Maps and other outlines morph

Some shapes have no simple meaning to animate: a country, a contour, a Voronoi cell. When their outline changes, the chart morphs one outline into the other. It resamples both and matches their rings. The morph loads only with @voila.dev/chart/geo, /contour and /voronoi. A line chart doesn't pay for it.

Arrow keys to move through the values, Enter to pin, Escape to leave.
1234
Data: Projects per zone
xSeriesProjects
NorthProjects34
WestProjects21
EastProjects12
SouthProjects27

Colours tint

A fill or a stroke that changes colour moves through color-mix(in oklab, …). Theme tokens like var(--chart-1) mix as they are, and the canvas resolves them against the page.

Arrow keys to move through the values, Enter to pin, Escape to leave.
010
Data: Bookings per day and hour
dayhour
Mon · 9h0
Mon · 11h5
Mon · 14h10
Mon · 16h4
Tue · 9h3
Tue · 11h8
Tue · 14h2
Tue · 16h7
Wed · 9h6
Wed · 11h0
Wed · 14h5
Wed · 16h10
Thu · 9h9
Thu · 11h3
Thu · 14h8
Thu · 16h2
Fri · 9h1
Fri · 11h6
Fri · 14h0
Fri · 16h5

Reduced motion

With prefers-reduced-motion: reduce, every update snaps, on SVG and Canvas alike. setReducedMotion("always" | "never" | "user") from @voila.dev/motion sets the policy for the whole page.

A resize always snaps. So does zooming with the pointer, which is direct manipulation. Zooming with + and − uses the spring.