Built with blockbuilder.org
Based on M. Bostock's original
What I learnt
d3.arc creates a donut-like shape and can be using for adding labels to groups created by d3.chord
d3.format can be used to format numbers (sig figs, commas, etc.)
Can use second argument of d3.csv (and other fetch functions) to format each row
svg element 'textPath' is used to draw text along a path (include an href attr with a reference to the path)
.darker() function available for d3.rgb
svg 'title' is used as a tooltip (on hover) description of svg elements (not all browsers support)
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; background-color: black; }
h3 {
color: white;
font-family: sans-serif;
text-align: center
}
</style>
</head>
<h3>The Euro Debt Crisis by M. Bostock - converted to d3v4 by Me</h3>
<body>
<script>
const width = 480
const height = 500
const outerRadius = Math.min(width, height) / 2 - 4
const innerRadius = outerRadius - 20
const numFormat = d3.format(',.3r') // grouped thousands with three significant digits, "4,000"
const debits = []
const credits = []
// chord layout for computing angles of chords
const layout = d3.chord()
.sortGroups(d3.descending)
.sortSubgroups(d3.descending)
.sortChords(d3.descending)
.padAngle(.02)
// Color scale for "risk"
const fill = d3.scaleOrdinal()
.domain([0, 1, 2]) // question: what's the significance of these numbers?
. range(["#DB704D", "#D2D0C6", "#ECD08D", "#F8EDD3"])
// This is for creating donut chart around the circle (for groups)
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
// ribbon generator for the chords
const ribbon = d3.ribbon()
.radius(innerRadius)
// creating two divs, one to hold debits, one to hold credits
const svg = d3.select('body')
.selectAll('div')
.data([debits, credits])
.enter()
.append('div')
.style('display', 'inline-block')
.style('width', `${width}px`)
.style('height', `${height}px`)
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${width/2},${height / 2})`)
function value () {
return this.amount
}
const formatRow = (row) => {
const _row = {}
_row.amount = +row.amount
_row.risk = +row.risk
_row.valueOf = value // for chord layout -- question: where is this being used
return { ...row, ..._row}
}
d3.csv('debts.csv', formatRow, (data) => {
let countryByName = d3.map()
let countryIndex = -1
const countryByIndex = []
data.forEach(d => {
if (countryByName.has(d.creditor)) {
d.creditor = countryByName.get(d.creditor)
} else {
countryByName.set(d.creditor, d.creditor = { name: d.creditor, index: ++countryIndex })
}
if (countryByName.has(d.debtor)) {
d.debtor = countryByName.get(d.debtor)
} else {
countryByName.set(d.debtor, d.debtor = { name: d.debtor, index: ++countryIndex })
}
d.debtor.risk = d.risk
})
// square matrix of debits and credits
for (let i = 0; i <= countryIndex; i++) {
debits[i] = []
credits[i] = []
for (let j =0; j <= countryIndex; j++){
debits[i][j] = 0
credits[i][j] = 0
}
}
data.forEach(d => {
debits[d.creditor.index][d.debtor.index] = d
credits[d.debtor.index][d.creditor.index] = d
countryByIndex[d.creditor.index] = d.creditor
countryByIndex[d.debtor.index] = d.debtor
})
svg.each(function(matrix, j){
const svg = d3.select(this)
svg.selectAll('.chord')
.data(layout(matrix))
.enter().append('path')
.attr('class', 'chord')
.style('fill', d => fill(d.source.value.risk))
.style('stroke', d => d3.rgb(fill(d.source.value.risk)).darker())
.attr('d', ribbon)
.append('title')
.text(d => `${d.source.value.debtor.name} owes ${d.source.value.creditor.name} $${numFormat(d.source.value)} B.`)
// Add groups
const g = svg.selectAll('.group')
.data(layout(matrix).groups)
.enter().append('g')
.attr('class', 'group')
// Add group "donut chart" arc
g.append('path')
.style('fill', d => fill(countryByIndex[d.index].risk))
.attr('id', (d,i) => `group${d.index}-${j}`)
.attr('d', arc)
.append('title')
.text(d => `${countryByIndex[d.index].name} ${(j ? 'owes' : 'is owed')} $${numFormat(d.value)}B.`)
g.append('text')
.attr('x', 6)
.attr('dy', 15)
.filter(d => d.value > 110)
.append('textPath')
.attr('href', d => `#group${d.index}-${j}`)
.text(d => countryByIndex[d.index].name)
})
})
</script>
</body>
https://d3js.org/d3.v4.min.js