Published
Edited
Jun 3, 2019
1 fork
15 stars
Insert cell
md`# Animated chord diagram`
Insert cell
chart = {
const svg = d3.select(DOM.svg(width, height))
.attr("viewBox", "0 0 " + (width) + " " + (height))
.attr("preserveAspectRatio", "xMidYMid meet");
const wrapper = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ") rotate(75)");
let matrix = updateMatrix();
let chord = chordGenerator(matrix);
reorderChord(chord);
const grads = svg.append("defs")
.selectAll("linearGradient")
.data(chord)
.enter()
.append("linearGradient")
.attr("id", getGradID)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", function(d, i){ return innerRadius * Math.cos((d.source.endAngle-d.source.startAngle) / 2 + d.source.startAngle - Math.PI/2); })
.attr("y1", function(d, i){ return innerRadius * Math.sin((d.source.endAngle-d.source.startAngle) / 2 + d.source.startAngle - Math.PI/2); })
.attr("x2", function(d,i){ return innerRadius * Math.cos((d.target.endAngle-d.target.startAngle) / 2 + d.target.startAngle - Math.PI/2); })
.attr("y2", function(d,i){ return innerRadius * Math.sin((d.target.endAngle-d.target.startAngle) / 2 + d.target.startAngle - Math.PI/2); });
// set the starting color (at 0%)
grads.append("stop")
.attr("offset", "0%")
.attr("stop-color", function(d){ return color(d.source.index)});

//set the ending color (at 100%)
grads.append("stop")
.attr("offset", "100%")
.attr("stop-color", function(d){ return color(d.target.index)});

// make ribbons
const chords = wrapper.selectAll("path")
.data(chord)
.enter()
.append("path")
.attr("class", function(d) {
return "chord chord-" + d.source.index + " chord-" + d.target.index
})
.style("fill", function(d){ return "url(#" + getGradID(d) + ")"; })
.transition()
.duration(2000)
.attr("d", ribbon);

// make arcs
const g = wrapper.selectAll("g")
.data(chord.groups)
.enter()
.append("g")
.attr("class", "group");

g.append("path")
.style("fill", function(d){ return color(d.index)})
.attr("class", "arc")
.style("opacity", 1)
.transition()
.duration(2000)
.attr("d", arcs);
while (true) {
matrix = updateMatrix();
chord = chordGenerator(matrix);
reorderChord(chord);
svg.selectAll("linearGradient")
.data(chord)
.transition()
.duration(2000)
.attr("x1", function(d, i){ return innerRadius * Math.cos((d.source.endAngle-d.source.startAngle) / 2 + d.source.startAngle - Math.PI/2); })
.attr("y1", function(d, i){ return innerRadius * Math.sin((d.source.endAngle-d.source.startAngle) / 2 + d.source.startAngle - Math.PI/2); })
.attr("x2", function(d,i){ return innerRadius * Math.cos((d.target.endAngle-d.target.startAngle) / 2 + d.target.startAngle - Math.PI/2); })
.attr("y2", function(d,i){ return innerRadius * Math.sin((d.target.endAngle-d.target.startAngle) / 2 + d.target.startAngle - Math.PI/2); });
svg.selectAll(".chord")
.data(chord)
.transition()
.duration(2000)
.attr("d", ribbon);

svg.selectAll(".arc")
.data(chord.groups)
.transition()
.duration(2000)
.attr("d", arcs);
yield svg.node();
await Promises.tick(2000);
}
}
Insert cell
md `Generate random data`
Insert cell
function updateMatrix () {
let arr = [];
for (var i = 0; i < 6; i++) {
arr[i] = [];
for (var j=0; j<6; j++) {
let num = random[Math.floor(Math.random() * random.length)];
if (j !== i) {
arr[i].push(num);
} else {
arr[i].push(0);
}
}
}
return arr;
}
Insert cell
md `Ensure that ribbons don't swap around during the transition`
Insert cell
function reorderChord (chord) {
for (var i=0; i<15; i++) {
let obj = chord[i];
if (obj.source.index > obj.target.index) {
let a = obj.source;
let b = obj.target;
obj.source = b;
obj.target = a;
}
}
}
Insert cell
md`Set up variables and functions`
Insert cell
function getGradID(d){
return "linkGrad-" + d.source.index + "-" + d.target.index;
}
Insert cell
chordGenerator = d3.chord()
.padAngle(0.1);
Insert cell
ribbon = d3.ribbon()
.radius(180);
Insert cell
color = d3.scaleOrdinal()
.domain(d3.range(6))
.range(colors);
Insert cell
arcs = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
Insert cell
innerRadius = outerRadius - 25;
Insert cell
outerRadius = Math.min(width, height) * 0.5 -55;
Insert cell
height = 600;

Insert cell
width = 500;
Insert cell
colors = ["#ff82da", "#ffaf98", "#ffe579", "#baf2a9", "#15bde8", "#b29de6"];
Insert cell
random = [10,10,25,45,15,40,15,55,10,5,10,10,5,15,35,10,20,30,25,50]
Insert cell
d3 = require("d3")
Insert cell
md `Gradients inspired by Julien Assouline's [Block](https://bl.ocks.org/JulienAssouline/2847e100ac7d4d3981b0f49111e185fe).`
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