Skip to content

Bar Terrain

three.js

An 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.json

Also 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

  • colsnumberDefault 30Bars along the long side.
  • rowsnumberDefault 16Bars along the short side.
  • sourceHeightSourceDefault Built inHeight of a bar at a grid position and time in seconds. Pass it from a client component.
  • pointerRadiusnumberDefault 1.6How wide the bump under the pointer spreads.
  • pointerLiftnumberDefault 3.2How high the bump under the pointer rises.
  • peaknumberDefault —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.
  • cameraTerrainCameraDefault 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.
  • autoPickbooleanDefault falseLifts a bar every few seconds when nobody's pointing.
  • pickInsetPickInsetDefault —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.
  • pauseWhenSlowbooleanDefault falseHolds a still frame if the device can't keep up, so the page around it stays responsive.
  • labelstringDefault 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.