Blog
Jan 31, 2026 - 4 MIN READ
Level Up Your D3 Charts: Exploring d3-3d v2

Level Up Your D3 Charts: Exploring d3-3d v2

Create lightweight 3D visuals in the browser without WebGL — learn how d3-3d brings 3D to D3 with TypeScript, projections, and fast SVG rendering.

Stefan Nieke

Stefan Nieke

Why would you want 3D on the web without WebGL, cameras, or shaders? Why stick to SVG and math when engines like Three.js can render stunning 3D scenes?

That’s exactly why d3-3d exists.

I didn’t create this library to replace Three.js. I created it because I often needed lightweight 3D: avatars, isometric charts, or simple shapes with depth, rotation, and a sense of volume — without the heavy setup.

This article explains the thinking behind the library, focusing on simplicity, TypeScript-first design, and seamless integration with D3.

The problem I faced

True 3D requires a camera, lighting, shadows, and often textures. But many projects just need 3D-ish visuals:

  • Simple 3D avatars
  • Small charts with depth
  • Animated shapes with volume

WebGL and Three.js felt heavy for these use cases. D3 is declarative and data-driven, but lives in 2D. So the question became:

Can we get the feeling of 3D while staying in D3’s mental model?

The core idea: projection instead of scene

d3-3d projects 3D coordinates into 2D space using math. There’s no camera, no perspective matrix, no WebGL. Just points, rotations, and projections. Simple, predictable, and fast.

This is what makes it different: it’s not trying to simulate reality; it’s giving you just enough 3D to feel spatial.

TypeScript-first

3D math can be tricky. Strong typing avoids subtle bugs and gives IDE autocomplete for points, shapes, and transformations. All core shapes are fully typed.

Example from the docs:

import { triangles3D, sort } from "d3-3d";

const data3D = [
  [
    { x: 0, y: -1, z: 0 },
    { x: -1, y: 1, z: 0 },
    { x: 1, y: 1, z: 0 },
  ],
];

const renderer = triangles3D()
  .scale(100)
  .origin({ x: 480, y: 250 })
  .rotateY(Math.PI / 4);

const transformedData = renderer.data(data3D);

Each transformed triangle includes rotated 3D coordinates, projected 2D coordinates, centroids, and counter-clockwise orientation detection. Perfect for SVG rendering.

Using custom data with TypeScript

import { cubes3D, sort, type Point3D } from "d3-3d";

interface Building {
  lat: number;
  lng: number;
  height: number;
  name: string;
  color: string;
}

const renderer = cubes3D<Building>()
  .x((d) => d.lng)
  .y((d) => d.height)
  .z((d) => d.lat)
  .scale(50)
  .origin({ x: 400, y: 300 });

const buildings: Building[][] = [
  /* ... */
];
const transformed = renderer.data(buildings);
const sorted = transformed.sort(sort);

// Render with D3
svg
  .selectAll("path")
  .data(sorted)
  .join("path")
  .attr("d", renderer.draw)
  .attr("fill", (d) => d[0].color);

Full type safety, computed properties, and seamless D3 integration.

Conclusion

If you need lighting, textures, or complex cameras, Three.js or WebGL is better. But for fast, lightweight, D3-friendly 3D with TypeScript support, d3-3d hits the sweet spot.

It’s not a competitor to heavy 3D engines — it’s a pragmatic choice for everyday 3D in the browser.

Stefan Nieke © 2026