Public
Edited
May 1, 2024
Insert cell
Insert cell
chart = {
const width = 928;
const height = width;
const { names, colors } = data;
const outerRadius = Math.min(width, height) * 0.5 - 60;
const innerRadius = outerRadius - 10;
const tickStep = d3.tickStep(0, d3.sum(data.flat()), 100);
const formatValue = d3.format(".1~%");

const chord = d3
.chord()
.padAngle(10 / innerRadius)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);

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

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

const color = d3.scaleOrdinal(names, colors);

const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("style", "width: 100%; height: auto; font: 10px sans-serif;");

const chords = chord(data);

const group = svg.append("g").selectAll().data(chords.groups).join("g");

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

group
.append("title")
.attr("fill", "#C5C5C5")
.text((d) => `${names[d.index]}\n${formatValue(d.value)}`);

const groupTick = group
.append("g")
.selectAll()
.data((d) => groupTicks(d, tickStep))
.join("g")
.attr(
"transform",
(d) =>
`rotate(${(d.angle * 180) / Math.PI - 90}) translate(${outerRadius},0)`
);

groupTick.append("line").attr("fill", "#C5C5C5").attr("x2", 6);

groupTick
.append("text")
.attr("x", 8)
.attr("dy", "0.35em")
.attr("transform", (d) =>
d.angle > Math.PI ? "rotate(180) translate(-16)" : null
)
.attr("text-anchor", (d) => (d.angle > Math.PI ? "end" : null))
.attr("fill", "#C5C5C5")
.text((d) => formatValue(d.value));

group
.select("text")
.attr("font-weight", "bold")
.attr("fill", "#C5C5C5")
.text(function (d) {
return this.getAttribute("text-anchor") === "end"
? `↑ ${names[d.index]}`
: `${names[d.index]} ↓`;
});

svg
.append("g")
.attr("fill-opacity", 0.8)
.selectAll("path")
.data(chords)
.join("path")
.style("mix-blend-mode", "multiply")
.attr("fill", (d) => color(names[d.source.index]))
.attr("d", ribbon)
.append("title")
.attr("fill", "#C5C5C5")
.text(
(d) =>
`${formatValue(d.source.value)} ${names[d.target.index]} → ${
names[d.source.index]
}${
d.source.index === d.target.index
? ""
: `\n${formatValue(d.target.value)} ${names[d.source.index]} → ${
names[d.target.index]
}`
}`
);

return svg.node();
}
Insert cell
function groupTicks(d, step) {
const k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, step).map(value => {
return {value: value, angle: value * k + d.startAngle};
});
}
Insert cell
data = Object.assign([
[.096899, .008859, .000554, .004430, .025471, .024363, .005537, .025471],
[.001107, .018272, .000000, .004983, .011074, .010520, .002215, .004983],
[.000554, .002769, .002215, .002215, .003876, .008306, .000554, .003322],
[.000554, .001107, .000554, .012182, .011628, .006645, .004983, .010520],
[.002215, .004430, .000000, .002769, .104097, .012182, .004983, .028239],
[.011628, .026024, .000000, .013843, .087486, .168328, .017165, .055925],
[.000554, .004983, .000000, .003322, .004430, .008859, .017719, .004430],
[.002215, .007198, .000000, .003322, .016611, .014950, .001107, .054264]
], {
names: ["Apple", "HTC", "Huawei", "LG", "Nokia", "Samsung", "Sony", "Other"],
colors: ["#c4c4c4", "#69b40f", "#ec1d25", "#c8125c", "#008fc8", "#10218b", "#134b24", "#737373"]
})
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