Skip to content

Instantly share code, notes, and snippets.

@vijithassar
Last active November 1, 2020 20:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vijithassar/3cc6ecbc0a8dcfaf4e47f44b97637012 to your computer and use it in GitHub Desktop.
Save vijithassar/3cc6ecbc0a8dcfaf4e47f44b97637012 to your computer and use it in GitHub Desktop.
pie chart segment sorting

In The Wall Street Journal Guide to Information Graphics, Dona Wong suggests a clever and unusual algorithm for sorting pie chart segments: the two largest pieces should appear at the top of the chart, on the right and left hand sides of the 12 o'clock position. This aligns the two most consequential data points on either side of a shared vertical axis, allowing for more natural comparison than simply positioning each segment in the original input order.

The pie chart layout generator from d3-shape provides the .sort() method for custom sorting. To implement this segment sort you'll need to provide the complete data set to the sorting function so each value can be situated in context to determine which are the two largest – for example, by closing over it with a factory which returns the comparator function. You could also accomplish the same thing by manipulating the data set directly, but pairing the sorting logic with the layout generator is clearer and more concise, particularly when rendering a lot of charts.

<html>
<head>
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
<link type="text/css" href="style.css" rel="stylesheet" />
</head>
<body>
<main>
</main>
<script type="text/javascript" src="./pie.js"></script>
</body>
</html>
(() => {
const target = d3.select('main')
const height = target.node().clientHeight
const width = target.node().clientWidth
const padding = 1
const sorter = (data) => {
const sorted = data.sort((a, b) => b - a)
const first = sorted[0]
const last = sorted[1]
const rest = sorted.slice(2)
const ordered = [first, ...rest.reverse(), last]
return (a, b) => ordered.indexOf(a) - ordered.indexOf(b)
}
const svg = target.append('svg')
.attr('height', height)
.attr('width', width)
const trellis = [10, 10]
const charts = trellis[0] * trellis[1]
const chart_width = width / trellis[0]
const chart_height = height / trellis[1]
const radius = d3.min([chart_height, chart_width]) / 2 - padding
const segments = d3.randomUniform(3, 6)
const arc = d3.arc().innerRadius(0).outerRadius(radius)
const data = Array.from({length: charts})
.map(() => Array.from({length: segments()})
.map(Math.random))
const wrapper = svg.append('g')
.attr('transform', `translate(${radius + padding},${radius + padding})`)
const chart = wrapper
.selectAll('g.chart')
.data(data)
.enter()
.append('g')
.classed('chart', true)
.attr('transform', (d, i) => {
const x = i % trellis[0] * chart_width + chart_width * 0.5 - radius
const y = Math.floor(i / trellis[1]) * chart_height
return `
translate(
${x},
${y}
)
`
})
const pie = selection => {
const data = selection.datum()
const layout = d3.pie().sort(sorter(data))
const segments = layout(data)
const slice = selection
.selectAll('path')
.data(segments)
.enter()
.append('path')
const color = d3.scaleOrdinal()
.domain(selection.data())
.range(d3.schemeTableau10)
slice.attr('d', arc)
.style('fill', (d) => color(d.data))
}
chart.each(function() {
d3.select(this).call(pie)
})
})()
main {
min-height: 500px;
}
svg {
shape-rendering: auto;
}
path {
stroke: black;
stroke-width: 0.5px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment