How to Build Chord Diagrams with d3-chord: A Complete Guide

To build chord diagrams with d3-chord, pass a square flow matrix to d3.chord() to generate layout data containing groups and chords, then render outer arcs using d3.arc() and inner ribbons using d3.ribbon().

Chord diagrams visualize bidirectional flows between entities using curved connections between circular segments. In the d3/d3 repository, the d3-chord module provides a pure data layout engine that computes angular positions from a matrix, leaving rendering entirely to your choice of SVG generators.

Understanding the d3-chord Architecture

The d3-chord workflow separates data layout from graphical rendering into three distinct stages. According to the implementation in src/index.js, the module exports two primary generators: the chord layout computer and the ribbon path generator.

Layout generation occurs when d3.chord() processes a square matrix of numeric values. As documented in docs/d3-chord/chord.md, this returns an object containing:

  • An array of groups representing the outer arcs, each with startAngle, endAngle, and value properties
  • An array of chords representing the flow ribbons between groups, containing source and target subgroups with their own angle and index data

Group rendering uses the standard d3.arc() generator from d3-shape, feeding it the inner and outer radii along with the angles provided by the chord layout.

Ribbon rendering uses d3.ribbon(), documented in docs/d3-chord/ribbon.md, which expects chord objects and a radius parameter to generate the curved paths connecting groups.

Step-by-Step Implementation

Preparing the Flow Matrix

The chord layout requires a square matrix where each row and column represents an entity, and cell values represent the flow magnitude from row to column.

const matrix = [
  [0, 5, 3, 4],
  [5, 0, 2, 1],
  [3, 2, 0, 6],
  [4, 1, 6, 0]
];

Generating the Layout with d3.chord()

Invoke d3.chord() to create a layout function, then pass your matrix to generate the data structure. The implementation supports several configuration methods:

  • padAngle(radians) — Adds angular padding between adjacent groups
  • sortGroups(comparator) — Orders the outer arcs (e.g., d3.descending)
  • sortSubgroups(comparator) — Orders ribbons within each group
  • sortChords(comparator) — Orders the chord array itself
const chord = d3.chord()
    .padAngle(0.05)
    .sortGroups(d3.descending);

const chords = chord(matrix);

Rendering Group Arcs with d3.arc()

The chords.groups array contains angle data suitable for d3.arc(). Configure the arc generator with inner and outer radii, then bind the groups data to path elements.

const arc = d3.arc()
    .innerRadius(innerRadius)
    .outerRadius(outerRadius);

svg.append("g")
  .selectAll("path")
  .data(chords.groups)
  .join("path")
    .attr("d", arc)
    .attr("fill", d => color(d.index));

Drawing Ribbon Connections with d3.ribbon()

The ribbon generator creates the curved paths between groups. Configure it with ribbon.radius() for a fixed radius, or use sourceRadius and targetRadius accessors for variable thickness per connection.

const ribbon = d3.ribbon()
    .radius(innerRadius);

svg.append("g")
  .selectAll("path")
  .data(chords)
  .join("path")
    .attr("d", ribbon)
    .attr("fill", d => color(d.source.index));

Complete Working Example

The following standalone HTML module demonstrates the full implementation, including matrix definition, layout computation, and rendering with labels:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>d3-chord example</title>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7";

const matrix = [
  [0, 5, 3, 4],
  [5, 0, 2, 1],
  [3, 2, 0, 6],
  [4, 1, 6, 0]
];

const width = 700,
      height = 700,
      outerRadius = Math.min(width, height) * 0.45,
      innerRadius = outerRadius * 0.9;

const color = d3.scaleOrdinal(d3.schemeCategory10);

// 1️⃣ Layout
const chord = d3.chord()
    .padAngle(0.05)
    .sortGroups(d3.descending);
const chords = chord(matrix);

// 2️⃣ SVG container
const svg = d3.select("body")
  .append("svg")
  .attr("viewBox", [-width / 2, -height / 2, width, height]);

// 3️⃣ Group arcs
const arc = d3.arc()
    .innerRadius(innerRadius)
    .outerRadius(outerRadius);

svg.append("g")
  .selectAll("path")
  .data(chords.groups)
  .join("path")
    .attr("d", arc)
    .attr("fill", d => color(d.index))
    .attr("stroke", "#fff");

// 4️⃣ Ribbons
const ribbon = d3.ribbon()
    .radius(innerRadius);

svg.append("g")
  .attr("fill-opacity", 0.7)
  .selectAll("path")
  .data(chords)
  .join("path")
    .attr("d", ribbon)
    .attr("fill", d => color(d.source.index))
    .attr("stroke", "#000");

// 5️⃣ Optional: add labels
svg.append("g")
  .selectAll("text")
  .data(chords.groups)
  .join("text")
    .attr("dy", ".35em")
    .attr("transform", d => {
      const angle = (d.startAngle + d.endAngle) / 2;
      const radius = outerRadius + 15;
      return `rotate(${(angle * 180 / Math.PI) - 90}) translate(${radius})${angle > Math.PI ? " rotate(180)" : ""}`;
    })
    .style("text-anchor", d => (d.startAngle + d.endAngle) / 2 > Math.PI ? "end" : "start")
    .text(d => `Group ${d.index}`);
</script>
</head>
<body></body>
</html>

Customizing Your Chord Diagram

Beyond the basic setup, d3-chord offers precise control over the layout computation. When you call d3.chord(), you can chain several configuration methods before passing the matrix.

Pad angle creates visual separation between segments using padAngle(radians), which helps distinguish adjacent groups. Sorting methods control the arrangement of both groups and individual ribbons, which affects the diagram's readability and visual balance. Because the layout is pure data, you can freely combine it with other d3 modules such as color scales, legends, and interaction handlers without modifying the core algorithm.

Summary

  • d3.chord() computes layout data from a square flow matrix, returning groups and chords with pre-calculated angles
  • d3.arc() renders the outer group segments using the startAngle and endAngle provided by the chord layout
  • d3.ribbon() generates the curved connections between groups, configurable with fixed or variable radii
  • Configuration methods like padAngle(), sortGroups(), and sortSubgroups() control the visual organization before rendering
  • The source implementation resides in src/index.js with detailed API documentation in docs/d3-chord/chord.md and docs/d3-chord/ribbon.md

Frequently Asked Questions

What data format does d3-chord require?

d3-chord requires a square matrix represented as a 2D array where matrix[i][j] indicates the flow value from entity i to entity j. The matrix must have equal numbers of rows and columns, with dimensions corresponding to the number of entities in your diagram. Zero values indicate no flow between those entities.

How do I control the spacing between groups?

Use the padAngle(radians) method on your chord layout generator before computing the layout. For example, d3.chord().padAngle(0.05) adds a small angular gap between adjacent outer arcs. This padding is expressed in radians and affects the startAngle and endAngle values returned in the groups array.

Can I use different radii for individual ribbons?

Yes. While ribbon.radius() sets a default radius for all ribbons, you can override this per-connection using the sourceRadius and targetRadius accessor methods. Pass these methods functions that receive the chord datum and return a numeric radius, allowing you to vary ribbon thickness based on flow magnitude or other data attributes.

Where can I find the source code for d3-chord?

The d3-chord module source is located in the d3/d3 repository at src/index.js, which re-exports the chord and ribbon modules. Comprehensive API documentation is available in docs/d3-chord/chord.md for the layout generator and docs/d3-chord/ribbon.md for the ribbon generator, with a general overview provided in docs/d3-chord.md.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →