Published
Edited
Sep 13, 2022
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3
.create("svg")
.attr("width", config.width)
.attr("height", config.height)
.attr("id", "d3-chord-chart")
.attr("viewBox", [
-config.width / 2,
-config.height / 2,
config.width,
config.height
]);
const g = svg.append("g");

const textId = DOM.uid("text");
const textPath = d3.arc()({
outerRadius: config.outerRadius,
startAngle: 0,
endAngle: 2 * Math.PI
});
g.append("path")
.attr("id", textId.id)
.attr("d", textPath)
.attr("fill", "none")
.attr("stroke", "lightgray");

// predefined functions, immutable

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

const chordGen = d3
.chordDirected()
.padAngle(12 / config.innerRadius)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);

const ribbon = d3
.ribbonArrow()
.radius(config.innerRadius - 0.5)
.padAngle(1 / config.innerRadius);

// ----

function render([matrix, groups]) {
const chord = chordGen(matrix);
const groupByIndex = d3.index(groups.values(), (d) => d.index);
console.log(groupByIndex);

// chord graph content
g.selectAll(".chord-content")
.data(chord, (d) => {
const k = [
groupByIndex.get(d.source.index)?.name,
"-",
groupByIndex.get(d.target.index)?.name
].join("");
console.log(k);
return k;
})
.join(
// enter
(node) =>
node
.append("g")
.attr("class", "chord-content")
.attr("data-content", JSON.stringify)
.call((g) =>
g
.append("path")
.attr("d", ribbon)
.attr("opacity", 0.3)
.attr("fill", (d) => groupByIndex.get(d.source.index).color)
),
(node) =>
node.call((g) =>
g
.select("path")
.attr("d", ribbon)
.attr("fill", (d) => groupByIndex.get(d.source.index).color)
),
// exit
(node) => node.remove()
);

// axis and label
g.selectAll(".axis-arc")
.data(chord.groups, (d) => groupByIndex.get(d.index)?.name)
.join(
// enter
(node) =>
node
.append("g")
.attr("class", "axis-arc")
.attr("data-axis-content", JSON.stringify)
.call((g) =>
g
.append("path")
.attr("opacity", 0.5)
.attr("d", arcGen)
.attr("fill", (d) => groupByIndex.get(d.index).color)
)
.call((g) =>
g
.append("text")
.append("textPath")
.attr("xlink:href", textId.href)
.attr("startOffset", (d) => d.startAngle * config.outerRadius)
.text((d) => groupByIndex.get(d.index)?.name)
),
// update
(node) =>
node
.call((g) =>
g
.select("path")
.attr("d", arcGen)
.attr("fill", (d) => groupByIndex.get(d.index).color)
)
.call((g) =>
g
.select("textPath")
.attr("startOffset", (d) => d.startAngle * config.outerRadius)
.text((d) => groupByIndex.get(d.index)?.name)
),
// exit
(node) => node.remove()
);
}
render(defaultValues);
return Object.assign(svg.node(), { render });
}
Insert cell
Insert cell
groups1 = d3.index(
[
{ name: "A", color: "blue" },
{ name: "B", color: "red" },
{ name: "C", color: "green" },
{ name: "D", color: "yellow" }
].map((d, index) => ({ ...d, index })),
(d) => d.name
)
Insert cell
paths1 = [
{ from: "A", to: "B", strength: 5 },
{ from: "A", to: "C", strength: 5 },
{ from: "A", to: "D", strength: 5 },
{ from: "B", to: "A", strength: 5 },
{ from: "B", to: "C", strength: 5 },
{ from: "B", to: "D", strength: 5 },
{ from: "C", to: "A", strength: 5 },
{ from: "C", to: "B", strength: 5 },
{ from: "C", to: "D", strength: 5 },
{ from: "D", to: "A", strength: 5 },
{ from: "D", to: "B", strength: 5 },
{ from: "D", to: "C", strength: 5 }
]
Insert cell
matrix1 = {
// init 2d matrix
const matrix = d3
.range(groups1.size)
.map(() => d3.range(groups1.size).map(() => 0));
paths1.forEach((conn) => {
const [x, y] = [groups1.get(conn.from).index, groups1.get(conn.to).index];
matrix[x][y] = conn.strength;
});
return matrix;
}
Insert cell
groups2 = d3.index(
[
{ name: "E", color: "#301E1E" },
{ name: "F", color: "#083E77" },
{ name: "G", color: "#342350" }
].map((d, index) => ({...d, index})),
(d) => d.name
)
Insert cell
paths2 = [
{ from: "E", to: "F", strength: 5 },
{ from: "E", to: "G", strength: 5 },
{ from: "F", to: "E", strength: 5 },
{ from: "F", to: "G", strength: 5 },
{ from: "G", to: "E", strength: 5 },
{ from: "G", to: "F", strength: 5 }
]
Insert cell
matrix2 = {
// init 2d matrix
const matrix = d3
.range(groups2.size)
.map(() => d3.range(groups2.size).map(() => 0));

paths2.forEach((conn) => {
const [x, y] = [groups2.get(conn.from).index, groups2.get(conn.to).index];
matrix[x][y] = conn.strength;
});
return matrix;
}
Insert cell
defaultValues = [matrix1, groups1]
Insert cell
Insert cell
d3 = import('d3@v7')
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