Public
Edited
Jun 12, 2023
Insert cell
Insert cell
Insert cell
chart = {

const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height]);

const chords = chord(matrix);

const group = svg.append("g")
.attr("font-size", 9)
.attr("font-family", "sans-serif")
.selectAll("g")
.data(chords.groups)
.join("g");

function onMouseOver(selected) {
svg.selectAll(".chord")
.filter(d => d.source.index !== selected.index)
.style("opacity", 0.1);
}
function onMouseOut() {
group.style("opacity", 1);
svg.selectAll(".chord")
.style("opacity", 1);
}

group.append("path")
.attr("fill", d => color(regions[d.index]))
.attr("d", arc);

group.append("text")
.each(d => (d.angle = (d.startAngle + d.endAngle) / 2))
.attr("dy", "0.35em")
.attr("transform", d => `
rotate(${(d.angle * 180 / Math.PI - 90)})
translate(${outerRadius + 5})
${d.angle > Math.PI ? "rotate(180)" : ""}
`)
.attr("text-anchor", d => d.angle > Math.PI ? "end" : null)
.text(d => names[d.index]);

group.append("title")
.text(d => `${names[d.index]}
${formatNumber(d3.sum(chords, c => (c.source.index === d.index) * c.source.value))} outgoing →
${formatNumber(d3.sum(chords, c => (c.target.index === d.index) * c.source.value))} incoming ←`);

svg.append("g")
.attr("fill-opacity", 1)
.selectAll("path")
.data(chords)
.join("path")
.attr('class', 'chord')
.attr("fill", d => color(regions[d.source.index]))
.attr("d", ribbon)
.on('mouseover', (event, d) => onMouseOver(d.source))
.on("mouseout", (event, d) => onMouseOut())
.append("title")
.text(d => `${names[d.source.index]} → ${names[d.target.index]} ${formatNumber(d.source.value)}`);

return svg.node();
}
Insert cell
data = FileAttachment("flow (2).csv").csv()
Insert cell
rename = name => name.substring(name.indexOf(".") + 1, name.lastIndexOf("."))
Insert cell
countries = FileAttachment("names (3).csv").csv()
Insert cell
names = countries.map(d => d.Country)
Insert cell
regions = countries.map(d => d.Region)
Insert cell
formatNumber = d3.format(",d")
Insert cell
matrix = {
const index = new Map(names.map((name, i) => [name, i]));
const matrix = Array.from(index, () => new Array(names.length).fill(0));
for (const {source, target, value} of data) matrix[index.get(source)][index.get(target)] += value;
return matrix;
}
Insert cell
chord = d3.chordDirected()
.padAngle(10 / innerRadius)
.sortSubgroups(d3.descending)
.sortChords(d3.descending)
Insert cell
arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
Insert cell
ribbon = d3.ribbonArrow()
.radius(innerRadius - 1)
.padAngle(1 / innerRadius)
Insert cell
color = d3.scaleOrdinal(regions, d3.quantize(d3.interpolateRainbow, 7))
Insert cell
outerRadius = innerRadius + 15
Insert cell
innerRadius = Math.min(width, height) * 0.5 - 90
Insert cell
width = 800
Insert cell
height = 800
Insert cell
import {Swatches} from "@d3/color-legend"
Insert cell
d3 = require("d3@6")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more