Chord/Ribbon

Chords are most often used to display relations between group elements when arranged in a circle. They use quadratic Bezier curves to create a closed shape connecting two points on an arc.

Put another way, a basic chord looks similar to half a villain's mustache:

Chords have changed quite a lot since D3 v3. Now in their own package (d3-chord), instantiating d3.chord creates a new layout, with the actual shapes drawn by d3.ribbon.

We just want to draw a ribbon here at the moment, so we're not going to use the entire chord layout. We pass d3.ribbon() to create a new ribbon generator, which draws our path:

g3.append('g') 
.selectAll('path')
.data([{
source: {
radius: 50,
startAngle: -Math.PI * 0.30,
endAngle: -Math.PI * 0.20,
},
target: {
radius: 50,
startAngle: Math.PI * 0.30,
endAngle: Math.PI * 0.30,
},
}])
.enter()
.append('path')
.attr('d', d3.ribbon());

This code adds a new grouping element, defines a dataset with a single data, and appends a path using the default d3.ribbon() generator for the d attribute.

The data itself works fine with the default accessors, so we can just hand it off to d3. ribbon.. If we need to change the placement of each end of the chord, we can use ribbon source() to define where the chord begins and ribbon.target() to set where it ends. Both are fed to another set of accessors, specifying the arc's radius, startAngle, and endAngle. As with the arc generator, angles are defined using radians.

Let's make up some data and draw a chord diagram:

const data = d3.zip(d3.range(0, 12), d3.shuffle(d3.range(0, 12))); 
const colors = ['linen', 'lightsteelblue', 'lightcyan', 'lavender', 'honeydew', 'gainsboro'];

Nothing too fancy. We defined two arrays of numbers, shuffled one, and merged them into an array of pairs; we will look at the details in the next chapter, but suffice to say that d3.range gives you an array of values between two numbers, d3.shuffle randomizes the order of an array, and d3.zip gives you an array of arrays. We then defined some colors:

const ribbon = d3.ribbon() 
.source(d => d[0])
.target(d => d[1])
.radius(150)
.startAngle(d => -2 * Math.PI * (1 / data.length) * d)
.endAngle(d => -2 * Math.PI * (1 / data.length) * ((d - 1) % data.length));

All of the arrays define the generator. We're going to divide a circle into sections and connect random pairs with chords.

The .source() and .target() accessors tell us the first item in every pair is the source and the second is the target. For startAngle, we remember a full circle is 2Pi and divide it by the number of sections. Finally, to pick a section, we multiply by the current data. The endAngle accessor is more of the same, except with data offset by one:

g3.append('g') 
.attr('transform', 'translate(300, 200)')
.selectAll('path')
.data(data)
.enter()
.append('path')
.attr('d', ribbon)
.attr('fill', (d, i) => colors[i % colors.length])
.attr('stroke', (d, i) => colors[(i + 1) % colors.length]);

To draw the actual diagram, we create a new grouping, join the dataset, and then append a path for each data. We use the ribbon generator from earlier to give each ribbon a shape, draw chords from each source to target, and add some color for fun.

The end result changes with every refresh, but looks something like this:

We'll discuss the chord layout itself in Chapter 6, Hierarchical Layouts of D3.

There used to be a section about d3.svg.diagonal here, but that's been removed in D3 v4. Suffice to say that drawing individual diagonals wasn't that useful or easy and all that functionality is wrapped up into the hierarchical chart layouts, part of the d3-hierarchy module. We'll get to that in Chapter 6, Hierarchical Layouts of D3.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset