Productive days
W42Deep-work hours per weekday, from calendar and editor activity. The ball marks the day you point at.
Showcase · Three.js · WebGPU · TSL
Three stat cards drawn like technical drawings. Hover a card and point at a bar, a slice or an hour, then click to fold the drawing flat into a readable chart. Everything is rendered live in your browser.
Deep-work hours per weekday, from calendar and editor activity. The ball marks the day you point at.
Share of monthly spending by category. Dining out grew 18% against September.
Sessions per hour of the day. Tick length is activity; the light rests on the busiest stretch.
Hover the cards · click one to flatten it
Showcase · Three.js · WebGPU · TSL
Isometric Data Cards is a set of three interactive stat cards for a dashboard or a landing page. Each card holds an isometric line drawing of its data: a 3D bar chart, a 3D pie chart and a lighthouse on a 24-hour dial. Lines that would be hidden behind a solid are dashed, like in an engineering drawing.
The motion carries meaning instead of decoration. A marker ball hops to the bar you point at, a slice slides out to be read, the lighthouse lights the hour under your cursor. Click a card and the drawing folds flat into an ordinary chart with axes and values, so the playful view and the precise view are the same object.
The three cards
Each card answers one question, and each animation shows the answer instead of decorating it.
Seven bars for seven weekdays. The bars never move, because they are the data. An orange ball rests on the peak; point at another day and it hops over in an arc, lands with a squash and a small bounce, and that bar's top face lights up.
Technique: The ball is simulated: gravity, a take-off speed that clears the bars in between, restitution on landing and a damped squash spring. It throws a soft orange light on the bar top beneath it.
Five slices, one per category. The slice under the pointer turns orange and slides a little out of the pie, its light falling on a dotted ground plane. At rest the card highlights the category that changed most.
Technique: Slices are extruded sectors. Their curved walls get silhouette lines computed per frame, so the outline stays correct from any angle and during the flattening.
A 24-hour dial where tick length is activity. The lighthouse throws a cone of light down onto the dial: at rest over the busiest stretch, on hover onto the hour you point at, with its share of the peak.
Technique: The light is a cone stretched each frame from the lamp to a spot on the dial, with a light pool where it lands. The hour comes from where the pointer ray meets the dial plane.
How it's built
The drawings use the Three.js WebGPURenderer and TSL, the Three.js Shading Language. One orthographic camera in CSS pixels covers the page, so every chart can sit exactly on its HTML card and still leave it.
Every edge is drawn twice. The second pass only draws where the depth test says the edge is hidden, and draws it dashed. Depth-only solids, shrunk by half a pixel, do the hiding.
// Every solid is drawn as its edges, twice.
// In front: solid lines. Behind something: dashed.
const front = new Line2NodeMaterial({ linewidth: 1.25 })
const behind = new Line2NodeMaterial({
linewidth: 1, dashed: true, dashSize: 3, gapSize: 3.5,
depthFunc: THREE.GreaterDepth, // only where hidden
})
// Depth-only solids, a hair smaller, hide the edges behind them.
const solid = new MeshBasicNodeMaterial({ colorWrite: false })A cylinder has no edges along its wall, so a pie slice or the lighthouse would lose its outline. Each frame, two silhouette lines are placed where the wall turns away from the viewer.
// Curved walls have no edges: find where the wall turns away.
// n(a) · view = 0 -> A cos a + B sin a = C
const d = viewDir.transformDirection(inverseWorldMatrix)
const k = (r0 - r1) / (y1 - y0) // taper of the cone
const phi = Math.atan2(-d.z, d.x)
const delta = Math.acos(-k * d.y / Math.hypot(d.x, d.z))
const silhouettes = [phi + delta, phi - delta]The marker ball is not tweened. It jumps with a speed that clears every bar on the way, can be steered mid-air when you change your mind, and loses energy with every bounce.
// Hop to the next bar: an arc that clears everything between.
const jump = (lift: number) => {
ball.vy = Math.sqrt(2 * G * (clearance() + lift - ball.y))
const t = (ball.vy + Math.sqrt(ball.vy ** 2 + 2 * G * (ball.y - landY))) / G
ball.vx = (targetX - ball.x) / t
}
// On touch-down: squash by impact speed, bounce with less energy.
if (ball.y <= landY) ball.vy = impact > 120 ? impact * 0.42 : 0Raycasting the bars or slices fails as soon as they move. The pointer ray is intersected with a plane in the chart's own space instead, and the position on that plane names the day, the category or the hour.
// Pick by column, not by mesh: intersect the pointer ray
// with the vertical plane through the bars.
inverse.copy(chart.matrixWorld).invert()
ray.copy(raycaster.ray).applyMatrix4(inverse)
if (ray.intersectPlane(barPlane, hit)) {
active = Math.round((hit.x - firstBarX) / STEP)
}On click the card grows into the middle of the screen while the drawing rotates to a straight view and its depth is pressed flat. Axis labels and values fade in as HTML at the end of the motion.
// Isometric -> flat: rotate to a straight view and
// press the depth axis flat, in one eased motion.
q.slerpQuaternions(Q_ISO, chart.qFlat, eased)
chart.root.quaternion.copy(q)
chart.root.scale[chart.collapse] = scale * lerp(1, 0.02, eased)Interaction
Every reaction answers a question about the data, so playing with the cards is also reading them.
The hovered card flips from dark to cream and its line drawing switches ink, in step with the 3D.
The bar, slice or hour under your cursor becomes the active one, and its value appears beside it and in the card footer.
The drawing folds into a 2D chart with axes and values. Click again, press Escape or click outside to fold it back.
Cards are focusable and open with Enter or Space. Each card carries a text description of its chart.
Performance
Three charts should not cost a laptop fan. These choices keep the cards cheap.
All drawings share one renderer and one camera in CSS pixels, placed on the HTML cards every frame.
The engine loads when the section comes near and stops drawing once it is off screen and every card is back in place.
Titles, values, axis labels and footers are HTML, readable by search engines and screen readers, and sharp at any zoom.
The WebGPURenderer uses WebGPU where available and WebGL 2 elsewhere. The same TSL code runs on both.
For your product
The same building blocks work with your own numbers:
Stat cards that explain a metric at a glance and open into a precise chart.
Weekly reports and year-in-review pages people actually explore.
Show what your product measures with live, playful charts instead of screenshots.
Spending breakdowns and savings goals with a slice or a bar that responds to touch.
Streaks, busiest days and best hours visualised as small objects.
Isometric scenes for editorial pages, annual reports and pitch decks on the web.
I build it to your brief: your data, your colours, the chart types you need, in plain Three.js or inside React and Next.js. You get clean, documented code that works on phones too.
TypeScript, React & Next.js developmentAn isometric chart draws data as 3D objects seen from a fixed angle without perspective, so parallel lines stay parallel and sizes stay comparable. It looks like a technical drawing and keeps values easier to compare than a perspective 3D chart.
Perspective 3D charts distort values. These cards use an isometric view for the overview and fold into an ordinary flat chart with exact values on click, so nothing is hidden behind the effect.
Yes. Each chart takes plain arrays of numbers and labels. Live data from an API or a database works the same way.
Yes. The engine is a framework-free TypeScript class that a small client component mounts and disposes. This page is a Next.js page.
Not when built like this. All text is server-rendered HTML. The engine loads when the section comes near and stops drawing when it is off screen.
I work at 75 USD per hour. The total depends on the number of charts, the interactions and the integration into your product. We agree on scope and an estimate before I start.
Tell me what you want to show, where it will live and your timeline. I reply with questions or a first estimate. Writing in German is fine too.
Design inspiration: the interactive isometric data cards by Zsolt Kacso. This version was rebuilt from scratch with Three.js, WebGPU and TSL. Original post on X