Thanks to visit codestin.com
Credit goes to github.com

Skip to content
/ d3-3d Public

d3-3d is a powerful JavaScript library designed for 3D visualizations, specifically tailored to work seamlessly with d3.js. This library enables the projection of 3D data onto web browsers, making it an essential tool for developers interested in creating immersive and dynamic visualizations.

License

Notifications You must be signed in to change notification settings

Niekes/d3-3d

Repository files navigation

d3-3d

d3-3d is meant for 3d visualizations. d3-3d allows the projection of 3d data onto the screen in the webbrowser. It is specially designed to work with d3.js.

Build Coverage npm npm npm npm bundle size npm TypeScript stars sponsor this project

See more examples

Features

  • First-class TypeScript support with full type definitions
  • Custom data accessor functions for any data format
  • Automatic centroid calculation for all shapes
  • Counter-clockwise orientation detection for polygons
  • Generic types for type-safe data transformations
  • Orthographic projection for SVG rendering
  • Sorting utilities for proper z-ordering (painter's algorithm)

Installing

If you use npm, npm install d3-3d. You can also download the latest release. Otherwise use unpkg to get the latest release. For example:

<script src="https://unpkg.com/d3-3d/build/d3-3d.js"></script>

For a specific version:

<script src="https://unpkg.com/d3-3d@version/build/d3-3d.js"></script>

TypeScript users: Type definitions are included automatically. No need to install @types/d3-3d.

Import

ES6 / TypeScript:

import {
  triangles3D,
  cubes3D,
  gridPlanes3D,
  points3D,
  lineStrips3D,
  polygons3D,
  planes3D,
  lines3D
} from 'd3-3d';

// Import utility function for sorting
import { sort } from 'd3-3d';

// Import TypeScript types
import type { Point3D, Point2D, TransformedPoint } from 'd3-3d';

API Reference

Note: All shapes (points3D, lines3D, lineStrips3D, triangles3D, planes3D, polygons3D, gridPlanes3D, cubes3D) share the same API. The methods below work for all shape types.

Core Methods:

  • .data() - transform and compute properties for your data.
  • .draw() - draw SVG path for a shape.

Configuration Methods:

  • .x() - set the x accessor.
  • .y() - set the y accessor.
  • .z() - set the z accessor.
  • .scale() - sets the scale for the projected points.
  • .rotateX() - set the angle for the x rotation.
  • .rotateY() - set the angle for the y rotation.
  • .rotateZ() - set the angle for the z rotation.
  • .rotationCenter() - set the rotation center.
  • .origin() - set the 2D rendering origin.
  • .rows() - set the points per row (gridPlanes3D only).

Utility Functions:

  • sort() - utility function to sort shapes by depth.

Overview

d3-3d uses the browser's coordinate system and orthographic projection to display your data on the screen. It will calculate the centroid for all elements and the orientation for your polygons. Due to the fact that SVG isn't very 3d compatible d3-3d adds 3d transformations to SVG.

With d3-3d you can easily visualize your 3d data with full TypeScript support.

Basic Example:

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 }
  ]
];

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

// Transform data - returns array with computed properties
const transformedData = renderer.data(data3D);

// Each transformed triangle includes:
// - rotated: { x, y, z } - rotated 3D coordinates
// - projected: { x, y } - 2D screen coordinates
// - centroid: { x, y, z } - geometric center
// - ccw: boolean - counter-clockwise orientation

// Render with D3
const svg = d3.select('svg');

svg
  .selectAll('path')
  .data(transformedData)
  .join('path')
  .attr('d', renderer.draw)
  .attr('fill', 'steelblue');

TypeScript with Custom Data Types:

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

// Define your domain-specific data type
interface Building {
  lat: number;
  lng: number;
  height: number;
  name: string;
  color: string;
}

// Use generic type for full type safety
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[][] = [
  [
    { lat: 0, lng: 0, height: 10, name: 'Building A', color: '#ff6b6b' },
    { lat: 1, lng: 0, height: 15, name: 'Building B', color: '#4ecdc4' },
    { lat: 0.5, lng: 1, height: 20, name: 'Building C', color: '#45b7d1' }
  ]
];

const transformed = renderer.data(buildings);

// TypeScript knows about your custom properties!
transformed[0][0].name; // ✓ string
transformed[0][0].color; // ✓ string
transformed[0].centroid; // ✓ Point3D
transformed[0].ccw; // ✓ boolean

// Sort by depth for proper rendering (back-to-front)
const sorted = transformed.sort(sort);

svg
  .selectAll('path')
  .data(sorted)
  .join('path')
  .attr('d', renderer.draw)
  .attr('fill', (d) => d[0].color)
  .attr('stroke', 'black');

Shapes

All shapes share the same API for configuration, but differ in their input data format and output properties.

Shape SVG Element Input Format .draw() ccw Property
points3D <circle> Datum[] - Array of points
lines3D <line> Datum[][] - Array of line pairs
lineStrips3D <path> Datum[][] - Array of point arrays
triangles3D <path> Datum[][] - Array of 3-point arrays
planes3D <path> Datum[][] - Array of 4-point arrays
polygons3D <path> Datum[][] - Array of N-point arrays
gridPlanes3D <path> Datum[] - Grid of points*
cubes3D <path> Datum[][] - Array of 8-vertex arrays ✅ (per face)

Notes:

  • All shapes compute centroid, rotated, and projected properties
  • ccw (counter-clockwise) is computed for polygon-based shapes to detect front/back faces
  • Shapes without .draw() method (points3D, lines3D) can be rendered directly with SVG elements using the projected coordinates

Input Data Details:

  • points3D: Each point must have properties accessible via .x(), .y(), .z() accessors (default: {x, y, z})
  • lines3D: Each line is defined by exactly 2 points (start and end)
  • lineStrips3D: Each strip connects consecutive points in the array
  • triangles3D: Each triangle requires exactly 3 points in counter-clockwise order
  • planes3D: Each plane requires exactly 4 points in counter-clockwise order
  • polygons3D: Each polygon can have any number of points (≥3) in counter-clockwise order
  • gridPlanes3D: Input is a flat array of points that forms a grid. Important: You must specify the number of points per row using .rows() so the library can correctly reconstruct the faces. All rows must have the same length.
  • cubes3D: Each cube requires exactly 8 vertices ordered as shown below:

cube

API Methods

.data(data)

Transforms the input data by applying rotation, projection, and computing additional properties.

Available on: All shapes

Parameters:

  • data: Datum[][] - Array of shapes, where each shape is an array of data points

Returns: Triangle<Datum>[] (or Polygon<Datum>[], Plane<Datum>[], etc. depending on the shape)

Transformed shapes with the following properties:

  • Original data preserved - All properties from your input data are preserved
  • rotated: Point3D - Rotated 3D coordinates for each point
  • projected: Point2D - 2D screen coordinates for each point
  • centroid: Point3D - Computed geometric center of the shape
  • ccw: boolean - Whether the shape is counter-clockwise oriented (polygons only)

Example:

const renderer = triangles3D()
  .scale(100)
  .rotateY(Math.PI / 4);

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

const result = renderer.data(data);

// Access computed properties
console.log(result[0].centroid); // { x: 0.33, y: 0.33, z: 0 }
console.log(result[0].ccw); // true
console.log(result[0][0].rotated); // { x: ..., y: ..., z: ... }
console.log(result[0][0].projected); // { x: ..., y: ... }

TypeScript with custom data:

interface CustomPoint {
  x: number;
  y: number;
  z: number;
  id: string;
  value: number;
}

const renderer = triangles3D<CustomPoint>()
  .x((d) => d.x)
  .y((d) => d.y)
  .z((d) => d.z);

const data: CustomPoint[][] = [...];
const result = renderer.data(data);

// Original properties are preserved
result[0][0].id;     // ✓ string
result[0][0].value;  // ✓ number

// Computed properties are added
result[0].centroid;  // ✓ Point3D
result[0].ccw;       // ✓ boolean

.draw()

Constructs an SVG <path> element string based on the transformed shape data.

Available on: lineStrips3D, triangles3D, planes3D, polygons3D, gridPlanes3D, cubes3D

Parameters:

  • shape: TransformedPoint<Datum>[] - A single transformed shape (from .data() result)

Returns: string - SVG path string (e.g., "M0,0L1,0L0.5,1Z")

Example:

const renderer = triangles3D();
const transformed = renderer.data(myData);

// Draw single shape
const pathString = renderer.draw(transformed[0]);

// Use with D3
svg.selectAll('path').data(transformed).join('path').attr('d', renderer.draw);

.x(x)

If x is specified, sets the x accessor to the specified function or number and returns the shape instance for chaining. If x is not specified, returns the current x accessor, which defaults to:

Available on: All shapes

function x(p) {
  return p.x;
}

This function will be invoked for each point in the input data array.

.y(y)

If y is specified, sets the y accessor to the specified function or number and returns the shape instance for chaining. If y is not specified, returns the current y accessor, which defaults to:

Available on: All shapes

function y(p) {
  return p.y;
}

This function will be invoked for each point in the input data array.

.z(z)

If z is specified, sets the z accessor to the specified function or number and returns the shape instance for chaining. If z is not specified, returns the current z accessor, which defaults to:

Available on: All shapes

function z(p) {
  return p.z;
}

This function will be invoked for each point in the input data array.

.scale(scale)

If scale is specified, sets the scale to the specified number and returns the shape instance for chaining. If scale is not specified, returns the current scale.

Available on: All shapes

Default: 1

.rotateX(angleX)

If angleX is specified, sets angleX to the specified number (in radians) and returns the shape instance for chaining. If angleX is not specified, returns the current angleX.

Available on: All shapes

Default: 0

angleX should be expressed in radians, for example: Math.PI / 4.

.rotateY(angleY)

If angleY is specified, sets angleY to the specified number (in radians) and returns the shape instance for chaining. If angleY is not specified, returns the current angleY.

Available on: All shapes

Default: 0

angleY should be expressed in radians, for example: Math.PI / 4.

.rotateZ(angleZ)

If angleZ is specified, sets angleZ to the specified number (in radians) and returns the shape instance for chaining. If angleZ is not specified, returns the current angleZ.

Available on: All shapes

Default: 0

angleZ should be expressed in radians, for example: Math.PI / 4.

.rotationCenter(point)

Sets the center point around which rotations are performed. This is different from .origin() which controls the 2D rendering position on the screen.

Available on: All shapes

Parameters:

  • point?: Point3D - The 3D point to rotate around

Returns:

  • If called without arguments: current Point3D value
  • If called with arguments: this (for chaining)

Default: { x: 0, y: 0, z: 0 }

Example:

const renderer = triangles3D()
  .rotationCenter({ x: 50, y: 50, z: 0 }) // Rotate around point (50,50,0)
  .rotateY(Math.PI / 2);

// The rotation will pivot around (50,50,0) instead of (0,0,0)

.origin(origin)

If origin is specified, sets the 2D rendering origin to the specified point and returns the shape instance for chaining. If origin is not specified, returns the current origin.

Available on: All shapes

Default: { x: 0, y: 0 }

.rows(rows)

Sets the number of points per row (columns) for gridPlanes3D. Since a grid is passed as a flat array, the library needs to know how many points constitute one horizontal line to correctly create the rectangular faces.

Available on: gridPlanes3D

Parameters:

  • rows?: number - Number of points per row

Returns:

  • If called without arguments: current number
  • If called with arguments: this (for chaining)

Default: 1

Example:

const points = [
  { x: 0, y: 0, z: 0 },
  { x: 1, y: 0, z: 0 },
  { x: 2, y: 0, z: 0 }, // Row 0
  { x: 0, y: 0, z: 1 },
  { x: 1, y: 0, z: 1 },
  { x: 2, y: 0, z: 1 } // Row 1
];

const grid = gridPlanes3D()
  .rows(3) // 3 points per row
  .data(points);

Utility Functions

sort()

A comparator function for sorting 3D shapes by their centroid's z-coordinate. Use this with JavaScript's .sort() to render shapes in correct depth order (painter's algorithm).

Usage:

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

const renderer = triangles3D();
const transformed = renderer.data(data);

// Sort back-to-front for correct rendering
const sorted = transformed.sort(sort);

svg.selectAll('path').data(sorted).join('path').attr('d', renderer.draw);

Type Signature:

function sort<T extends HasCentroid>(a: T, b: T): number;

interface HasCentroid {
  centroid: { z: number };
}

Computed Properties

All shape transformations automatically compute additional properties on the returned data:

centroid

The geometric center of the shape in 3D space (after rotation). Available on all shapes.

const data = triangles3D().data(myTriangles);
console.log(data[0].centroid); // { x: 1.5, y: 2.0, z: 0.5 }

Use this for:

  • Sorting shapes by depth
  • Calculating bounding boxes
  • Finding center points for labels or interaction

ccw (Counter-Clockwise)

Boolean indicating whether the polygon is oriented counter-clockwise when viewed from the camera. Useful for backface culling. Available on: triangles3D, polygons3D, planes3D, cubes3D (per face).

const data = triangles3D().data(myTriangles);
if (data[0].ccw) {
  // Front-facing triangle - render normally
} else {
  // Back-facing triangle - optionally skip or render differently
}

Algorithm: Uses the shoelace formula on the rotated 2D projection to determine orientation.

rotated

3D coordinates after rotation has been applied. Available on each individual point.

const data = points3D().data(myPoints);
console.log(data[0].rotated); // { x: number, y: number, z: number }

projected

2D screen coordinates after orthographic projection. Available on each individual point.

const data = points3D().data(myPoints);
console.log(data[0].projected); // { x: number, y: number }

Migration Guide

Upgrading from v0.x to v1.0

The API has been modernized with a new .data() method pattern for better TypeScript support and clarity.

Before (v0.x):

const triangles = triangles3D();
const result = triangles(data); // Called as function

After (v1.0+):

const renderer = triangles3D();
const result = renderer.data(data); // Explicit .data() method

Breaking Changes:

  1. ❌ Direct function invocation removed: renderer(data) no longer works
  2. ✅ Use .data() method instead: renderer.data(data)
  3. ✅ Full TypeScript generics support added
  4. ✅ Computed properties (centroid, ccw) now included automatically

Benefits:

  • Better IDE autocomplete and type inference
  • Explicit API that's easier to understand
  • No confusion between configuration and data transformation
  • Full type safety with custom data structures

Migration Example:

- const triangles = triangles3D().scale(100);
- const result = triangles(data);
+ const renderer = triangles3D().scale(100);
+ const result = renderer.data(data);

  svg.selectAll('path')
    .data(result)
    .join('path')
    .attr('d', triangles.draw);

Star History

Star History Chart

About

d3-3d is a powerful JavaScript library designed for 3D visualizations, specifically tailored to work seamlessly with d3.js. This library enables the projection of 3D data onto web browsers, making it an essential tool for developers interested in creating immersive and dynamic visualizations.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project