Bar Terrain
three.jsAn instanced field of bars driven by a height function, with a bump under the pointer.
Install
$ npx shadcn@latest add https://spotcheckui.com/r/bar-terrain.jsonAlso installs three, @react-three/fiber.
Usage
Give it a height. It fills its parent's width, and colours come from your tokens.
"use client"
import dynamic from "next/dynamic"
const BarTerrain = dynamic(
() => import("@/components/spotcheck/bar-terrain").then((module) => module.BarTerrain),
{ ssr: false },
)
<BarTerrain className="h-96" label="Requests per hour across 30 regions" />Load it with dynamic so three.js only downloads where it's used. The canvas is created as it
nears the viewport, pauses off screen and is removed when it's far away.
Set label to describe your data, because screen readers get that instead of the canvas. It also
replaces the canvas if WebGL fails or the browser drops it.
Your own data
source returns the height of a bar at a grid position and a time in seconds.
const source = (col: number, row: number, time: number) =>
values[row][col] + Math.sin(time + col) * 0.1
<BarTerrain cols={24} rows={7} source={source} />As a background
Pass camera and lookAt for a wider view, fade to dissolve the far bars into the page, and
peak to turn the tallest bars the accent colour. autoPick lifts a bar every few seconds, and
onPick tells you where it is, so you can label it. pauseWhenSlow holds a still frame on a device
that can't keep up, so a large field doesn't make the page around it slow to respond.
<BarTerrain
cols={110}
rows={64}
camera={{ fov: 34, position: [10, 9.5, 21] }}
lookAt={[4.5, 0.2, 0]}
fade={{ near: 16, far: 52 }}
peak={2.4}
autoPick
pauseWhenSlow
onPick={(pick) => moveLabel(pick)}
className="absolute inset-0 h-auto"
/>Props
- cols
numberDefault 30Bars along the long side. - rows
numberDefault 16Bars along the short side. - source
HeightSourceDefault Built inHeight of a bar at a grid position and time in seconds. Pass it from a client component. - pointerRadius
numberDefault 1.6How wide the bump under the pointer spreads. - pointerLift
numberDefault 3.2How high the bump under the pointer rises. - peak
numberDefault —Bars this tall turn the accent, and lower ones shade toward it. Off by default. - fade
{ near: number; far: number; }Default —Fades the far bars into the page. Distances are in scene units from the camera. - camera
TerrainCameraDefault Built inThe camera, for a wider view than the default. - lookAt
[x: number, y: number, z: number]Default [0, 0.9, 0]The point the camera faces. - autoPick
booleanDefault falseLifts a bar every few seconds when nobody's pointing. - pickInset
PickInsetDefault —Keeps an automatic pick's lifted tip this many pixels inside the stage, so a label drawn there stays in view. - onPick
(pick: BarPick | null) => voidDefault —Runs every frame with the lifted bar, or `null` when there isn't one. Snaps the bump to one bar. - pauseWhenSlow
booleanDefault falseHolds a still frame if the device can't keep up, so the page around it stays responsive. - label
stringDefault 3D bar fieldDescribes the data for screen readers.
Keyboard
Nothing to operate: this component displays information.
Reduced motion
Renders one still frame, and renders again when the theme or skin changes. The pointer bump is off.