Public
Edited
Feb 28, 2023
1 star
Insert cell
Insert cell
concentricPair(data, {
circleSize: 65,
groupOne: 'male', // key for group in data
groupTwo: 'female', // key for group in data
label: 'ref',
groupOnePrefix: 'M:',
groupTwoPrefix: 'F:',
col: {
g1: '#00c4aa', // colors are inspired by the “Votes for Women” campaign
g2: '#8700f9'
}
})
Insert cell
function concentricPair (data, {
f = d3.format('.1f'),
margin = {top: 10, right: 100, bottom: 30, left: 80},
label = null, //
groupOne = 'g1', // key for group in data
groupTwo = 'g2', // key for group in data
circleSize = 60,
width = (circleSize * 2) * data.length + (margin.left + margin.right),
height = circleSize * 2.5 + (margin.top + margin.bottom),
groupOnePrefix = 'G1:',
groupTwoPrefix = 'G2:',
col = {
g1: '#0024cc',
g2: '#fa9442'
}
} = {}) {

// set the dimensions and margins of the graph
const w = width - margin.left - margin.right
const h = height - margin.top - margin.bottom

const svg = DOM.svg(width, height);
const sel = d3.select(svg)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);

const sqrtScale = d3.scaleSqrt()
.domain([0, 100])
.range([0, circleSize])
const linearScale = d3.scaleLinear()
.domain([0, data.length])
.range([0, width]);

const r = sqrtScale(100); // radius of center circle

const groups = sel.selectAll('g.g-cc')
.data(data)
.join('g')
.attr('class', 'g-cc')
.attr('transform', (d, i) => `translate(${linearScale(i)},${height / 2})`);

// base circle(s)
groups.append('circle')
.attr('class', 'c-100')
.attr('r', d => sqrtScale(100))
.attr('fill', 'transparent')
.attr('stroke', '#ccc')
.style('stroke-dasharray', 4)
.attr('stroke-width', 2);
// group one circles
groups.append('circle')
.attr('class', 'c-male')
.attr('r', d => sqrtScale(d[groupOne]))
.attr('fill', 'transparent')
.attr('stroke', col.g1)
.attr('stroke-width', 4);

// group two circle(s)
groups.append('circle')
.attr('class', 'c-female')
.attr('r', d => sqrtScale(d[groupTwo]))
.attr('fill', 'transparent')
.attr('stroke', col.g2)
.attr('stroke-width', 4);

if (label !== null) {
// render group labels
groups.append('text')
.attr('class', 'area-label')
.attr('dy', 5)
.style('text-anchor', 'middle')
.text(d => d[label]);
}

// render group figures
groups.append('text')
.attr('class', 'percent-label')
.attr('y', (r + 25) * -1)
// .attr('dy', 5)
.style('text-anchor', 'end')
.style('fill', '#ccc')
.text(d => {
return `${groupTwoPrefix} ${f(d[groupTwo])}%`
});
groups.append('text')
.attr('class', 'percent-label')
.attr('y', (r + 15) * -1)
.attr('dy', 5)
.style('text-anchor', 'end')
.style('fill', '#ccc')
.text(d => {
return `${groupOnePrefix} ${f(d[groupOne])}%`
});
// show diff
groups.append('text')
.attr('class', 'percent-label')
.attr('y', r + 15)
.attr('dy', 5)
.style('text-anchor', 'end')
.style('fill', d => {
if (d[groupOne] > d[groupTwo]) {
return col.g2
} else {
return '#454545'
}
})
.text(d => {
const one = d[groupOne]
const two = d[groupTwo]
if (one > two) {
return `Diff: ${f(one - two)}%`
} else {
return `Diff: ${f(two - one)}%`
}
});

return svg;

}
Insert cell
Insert cell
data = [
{
ref: 'G1',
// population: 3450,
male: 80,
female: 20
},
{
ref: 'G2',
// population: 2000,
male: 55,
female: 45
},
{
ref: 'G3',
// population: 200,
male: 75,
female: 25
},
{
ref: 'G4',
// population: 800,
male: 30,
female: 70
}
]
Insert cell
<hr>
<link href="https://fonts.googleapis.com/css?family=Space+Mono" rel="stylesheet">
<style>
text {
font-family:'Space Mono',monospace;
fill: #130C0E;
font-size: 11px;
}
</style>
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