# Self-contained D3 Pie Chart Function

Travis Horn

Published on Oct 7, 2019

D3 is a great data visualization library. I often find myself coding the same types of charts over and over again. I decided that it would be wise to build each basic chart type I use into a single self-contained function that can be included and called on any page. These functions should create a static chart from a set of data and insert it into any element.

This post is the second in a series where I write a function for each chart type. In this post, we’ll be writing a pie chart function. In the last one, we wrote a bar chart function.

### The Goal

Say we have data in this format.

``````[
{ name: 'Option A', value: 107 },
{ name: 'Option B', value: 31 },
{ name: 'Option C', value: 635 },
]
``````

And we want to visualize it like this.

A simple pie chart with three data points.

### The Function

Just like the bar chart, let’s write a function that accepts two arguments.

``````const pieChart = (selector, data) => {
// All the rest of the code goes here
};
``````

The first parameter, `selector`, will be the DOM element in which to add the chart. The second parameter, `data`, will be the data to visualize.

At the top of the function, we need to define some constants.

``````const size = 500;
const fourth = size / 4;
const half = size / 2;
const labelOffset = fourth * 1.4;
const total = data.reduce((acc, cur) => acc + cur.value, 0);
const container = d3.select(selector);
``````

This is very similar to the constants in the previous post. Although they’re not exactly the same, we set some sizing constants first. These are `size`, `fourth`, `half`, and `labelOffset`.

The `total` constant simply sums up the data values. We’ll reference this number later.

The `container` constant uses a D3 selector to select the passed in element.

Next we create the chart element itself inside the container.

``````const chart = container.append('svg')
.style('width', '100%')
.attr('viewBox', `0 0 \${size} \${size}`);
``````

Now for the plot area. This is a container element that starts exactly in the middle of the chart. This is a good reference point for where to place the pie slices.

``````const plotArea = chart.append('g')
.attr('transform', `translate(\${half}, \${half})`);
``````

### Scales

There’s only one scale this time: the color scale. We use this to color each slice based on the data names.

``````const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(d3.schemeCategory10);
``````

### Calculating the slices

We calculate the slices using three D3 methods.

``````const pie = d3.pie()
.sort(null)
.value(d => d.value);

const arcs = pie(data);

const arc = d3.arc()
``````

`d3.pie` is the base of the calculations. By default, D3 sorts our data from largest to smallest. In my case, I want to keep data points in the order they’re defined, so I pass `.sort(null)`. Then I define where the data values come from.

Next we tie in our pie function with our data.

Finally we create an `arc` function which we’ll use to calculate the SVG paths. The inner radius is 0 so our slices start in the center of the chart. The outer radius goes out to a fourth of the total chart size.

To understand the concept of the pie going out the a fourth the chart size, it may be helpful to visualize the chart broken into 4 pieces.

We’ll position the labels with arcs, too.

``````const arcLabel = d3.arc()
``````

We defined `labelOffset` earlier as 1.4 times a fourth of the chart size. This spaces the labels away from the slices a bit. Increase this number for farther-away labels. Decrease it for closer or overlapping labels.

### Plotting the Slices

Now we can plot the slices by appending `path`s to our chart.

``````plotArea.selectAll('path')
.data(arcs)
.enter()
.append('path')
.attr('fill', d => color(d.data.name))
.attr('stroke', 'white')
.attr('d', arc);
``````

### Labels

It looks good but we still need some labels.

``````const labels = plotArea.selectAll('text')
.data(arcs)
.enter()
.append('text')
.style('text-anchor', 'middle')
.style('alignment-baseline', 'middle')
.style('font-size', '20px')
.attr('transform', d => `translate(\${arcLabel.centroid(d)})`)
``````

Each label is positioned based on a D3 arc method called `centroid`. It places the label near the center of the arc.

While the code block above does add the `text` elements. They are empty at the moment.

First add the data names by appending a `tspan` to each `text` label.

``````labels.append('tspan')
.attr('y', '-0.6em')
.attr('x', 0)
.style('font-weight', 'bold')
.text(d => `\${d.data.name}`);
``````

Then add the data values and percentages by appending another `tspan`.

``````labels.append('tspan')
.attr('y', '0.6em')
.attr('x', 0)
.text(d => `\${d.data.value} (\${Math.round(d.data.value / total * 100)}%)`);
``````

The values appear first, then the percentage is calculated inside of parenthesis.

That’s it! Our pie chart is complete.

### Putting it all Together

The entire function looks like this.

``````const pieChart = (selector, data) => {
const size = 500;
const fourth = size / 4;
const half = size / 2;
const labelOffset = fourth * 1.4;
const total = data.reduce((acc, cur) => acc + cur.value, 0);
const container = d3.select(selector);

const chart = container.append('svg')
.style('width', '100%')
.attr('viewBox', `0 0 \${size} \${size}`);

const plotArea = chart.append('g')
.attr('transform', `translate(\${half}, \${half})`);

const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(d3.schemeCategory10);

const pie = d3.pie()
.sort(null)
.value(d => d.value);

const arcs = pie(data);

const arc = d3.arc()

const arcLabel = d3.arc()

plotArea.selectAll('path')
.data(arcs)
.enter()
.append('path')
.attr('fill', d => color(d.data.name))
.attr('stroke', 'white')
.attr('d', arc);

const labels = plotArea.selectAll('text')
.data(arcs)
.enter()
.append('text')
.style('text-anchor', 'middle')
.style('alignment-baseline', 'middle')
.style('font-size', '20px')
.attr('transform', d => `translate(\${arcLabel.centroid(d)})`)

labels.append('tspan')
.attr('y', '-0.6em')
.attr('x', 0)
.style('font-weight', 'bold')
.text(d => `\${d.data.name}`);

labels.append('tspan')
.attr('y', '0.6em')
.attr('x', 0)
.text(d => `\${d.data.value} (\${Math.round(d.data.value / total * 100)}%)`);
};
``````

This function has only one dependency: D3.

See the pen below for full code and a demonstration.