Skip to content
Learni
View all tutorials
Data Visualization

How to Master D3.js for Interactive Visualizations in 2026

Lire en français

Introduction

D3.js remains in 2026 the reference library for highly customized data visualizations. Unlike high-level frameworks, D3 gives you full control over the DOM and SVG. This expert tutorial guides you through creating a complete interactive visualization with dynamic data handling, performant animations, and physics simulations.

Prerequisites

  • Node.js 20+ and TypeScript 5.5+
  • Solid knowledge of modern JavaScript and SVG
  • D3.js v7 installed
  • An editor with TypeScript support

Project Initialization

terminal
npm init -y
npm install d3 @types/d3
npm install -D typescript vite

We initialize a Vite project for modern development and install D3.js v7 with its official TypeScript types.

TypeScript Configuration

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Strict TypeScript configuration optimized for D3.js to benefit from type inference on selections and data.

SVG Container Creation

src/main.ts
import * as d3 from 'd3';

const width = 800;
const height = 600;

const svg = d3.select('#app')
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .attr('viewBox', `0 0 ${width} ${height}`);

Create a responsive SVG using viewBox. Always use viewBox to ensure adaptability across all screens.

Data Binding and Scales

src/scales.ts
interface DataPoint { x: number; y: number; value: number; }

const data: DataPoint[] = [
  { x: 10, y: 20, value: 45 },
  { x: 30, y: 50, value: 78 },
  { x: 60, y: 30, value: 92 }
];

const xScale = d3.scaleLinear()
  .domain(d3.extent(data, d => d.x) as [number, number])
  .range([50, width - 50]);

const yScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)!])
  .range([height - 50, 50]);

Use linear scales with automatic domain calculation via d3.extent. Always type your data to avoid runtime errors.

Adding Circles with Transitions

src/visualization.ts
svg.selectAll('circle')
  .data(data)
  .join('circle')
  .attr('cx', d => xScale(d.x))
  .attr('cy', d => yScale(d.y))
  .attr('r', 0)
  .attr('fill', '#3b82f6')
  .transition()
  .duration(800)
  .ease(d3.easeBounceOut)
  .attr('r', d => Math.sqrt(d.value) * 1.5);

Modern .join() pattern and transition with custom ease. D3 transitions are performant because they use requestAnimationFrame.

Best Practices

  • Always type your data interfaces to benefit from autocompletion
  • Use .join() instead of .enter()/.exit() for more concise code
  • Prefer short transitions (600-900ms) with appropriate easing
  • Clean up event listeners during component unmount
  • Measure performance with DevTools before adding too many elements

Common Mistakes to Avoid

  • Forgetting to handle null values in scale domains
  • Using overly generic CSS selectors that break scoping
  • Applying transitions to thousands of elements without throttling
  • Neglecting accessibility (aria-labels on SVG elements)

Going Further

Deepen your knowledge of force simulations and hierarchical layouts in our complete training: Learni Courses.