Pie charts have become a trolling topic in the data viz world. As a lot of memes can testify. And for good reason. They make data slow and ambiguous to read. Let’s look at a better alternative and how to code it in D3.js.

Don’t use pie charts.

We don’t use them and don’t recommend them, because they present information poorly. Here is a pie chart. Its purpose is to compare our company’s market share with our 4 competitors.

Now, very quickly at a glance (this is data visualization after all): Which company has the biggest share? Ours or A? And between B and D? Are we 10 times the size of C? 7 times? 5? Are there any clusters of companies with similar market shares?

Pie charts use the arc length of each slice to represent quantitative information. This is very ineffective when trying to compare or order the slices. Maybe we should add the values to the labels?

But then why not just use a table? We can sort it and instantly see that we are number 2:

Country Market Share
Company A 31.67%
Our company 30.01%
Company B 17.04%
Company D 14.96%
Company C 6.32%
  100.00%

So what’s the point of visualizing data if a table is more useful?

The bar graph solution.

Look how easy it is to compare and estimate quantities using a bar graph. Each bar is aligned to the left. Just like wooden sticks, we instinctively are able to sort them from longest to shortest. We can quickly estimate which ones are 2 times, 3 times, 10 times the length of others. Which ones can be assembled in groups of the same lengths.

Step by step in D3.js

Let’s start with a simple horizontal bar chart:

// Define data
data = [
  {"name": "Our company", "value": 0.3001},
  {"name": "Company A", "value": 0.3167},
  {"name": "Company B", "value": 0.1704},
  {"name": "Company C", "value": 0.0632},
  {"name": "Company D", "value": 0.1496}
];

// Define number formats
var formatPercent = d3.format(".2%"),
    formatTicks = d3.format(".0%");

// Define margins
var margin = {top: 10, right: 10, bottom: 25, left: 100};

// Define width and height
var width = 400 - margin.left - margin.right,
  height = 200 - margin.top - margin.bottom;

// Construct the main svg
var svg = d3.select("body").append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Construct both x and y scales
var x = d3.scale.linear()
  .range([0, width])
  .domain([0, d3.max(data, function(d) { return d.value; })])

var y = d3.scale.ordinal()
  .rangeRoundBands([height, 0], 0.25, 0.25)
  .domain(data.map(function(d) { return d.name; }));

// Construct bars
var bars = svg.selectAll(".bar")
  .data(data)
  .enter()
  .append("g")

// Append rects
bars.append("rect")
  .attr("class", "bar")
  .attr("y", function (d) { return y(d.name); })
  .attr("height", y.rangeBand())
  .attr("x", 0)
  .attr("width", function(d) { return x(d.value); })
  .attr("fill", "#eef");

// Construct x axis
var xAxis = d3.svg.axis()
  .scale(x)
  .ticks(5)
  .orient("bottom")
  .tickFormat(formatTicks);

var gx = svg.append("g")
  .attr("class", "x axis")
  .call(xAxis)
  .attr("transform", "translate(0," + height + ")");

// Construct y axis to show bar names
var yAxis = d3.svg.axis()
  .scale(y)
  .tickSize(0)
  .orient("left");

var gy = svg.append("g")
  .attr("class", "y axis")
  .call(yAxis)
  .selectAll(".y.axis text")
  .attr("transform", "translate(-90,0)");

Data points should always be sortable and identifiable at a glance. Let’s sort the bars in descending order and choose an accent color for our company:

// Sort data values in descending order
data.sort(function(a, b) { return d3.ascending(a.value, b.value); })

// Define bar colors
var barColor = d3.scale.ordinal()
  .domain(["Company A", "Our company", "Company B", "Company C", "Company D"])
  .range(["#eef","#d8d8ff","#eef","#eef","#eef"]);

// ... and use them as the fill attribute for the bar rects
  .attr("fill", function(d, i) { return barColor(d.name); });

Data points should be labeled:

// Add a value label to the left of each bar
bars.append("text")
  .attr("class", "label")
  .attr("text-anchor", "end")
  .attr("y", function(d) { return y(d.name) + y.rangeBand() / 2 + 4; })  // y position of the label is halfway down the bar
  .attr("x", -35)
  .text(function(d) { return formatPercent(d.value); });  // We use the formatPercent() function defined at the top

The redeemable quality of a pie chart is that it instantly shows that all of the parts sum to 100%. To indicate the same thing with bars, we can stick them together vertically and add the “100%” label:

// The 2nd attribute of the rangeRoundBands() method defines the inner padding between the bars
  .rangeRoundBands([height, 0], 0, 0.25)

// Mark the total
var totalLine = svg.append("line")
  .attr("class", "total")
  .attr("x1", -25)
  .attr("y1", height)
  .attr("x2", -90)
  .attr("y2", height);

var totalLabel = svg.append("text")
  .attr("class", "total")
  .attr("text-anchor", "end")
  .attr("x", -35)
  .attr("y", height + 20)
  .text(function() { return formatPercent(1); });

Full code

// Define data and sort it based on value
data = [
  {"name": "Our company", "value": 0.3001},
  {"name": "Company A", "value": 0.3167},
  {"name": "Company B", "value": 0.1704},
  {"name": "Company C", "value": 0.0632},
  {"name": "Company D", "value": 0.1496}
];
data.sort(function(a, b) { return d3.ascending(a.value, b.value); })

// Define number formats
var formatPercent = d3.format(".2%"),
    formatTicks = d3.format(".0%");

// Define bar colors
var barColor = d3.scale.ordinal()
  .domain(["Company A", "Our company", "Company B", "Company C", "Company D"])
  .range(["#eef","#d8d8ff","#eef","#eef","#eef"]);

// Define margins
var margin = {top: 10, right: 10, bottom: 25, left: 190};

// Define width and height
var width = 400 - margin.left - margin.right,
  height = 200 - margin.top - margin.bottom;

// Construct the main svg
var svg = d3.select("#g2").append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Construct both x and y scales
var x = d3.scale.linear()
  .range([0, width])
  .domain([0, d3.max(data, function(d) { return d.value; })])

var y = d3.scale.ordinal()
  .rangeRoundBands([height, 0], 0, 0.25)
  .domain(data.map(function(d) { return d.name; }));

// Construct bars
var bars = svg.selectAll(".bar")
  .data(data)
  .enter()
  .append("g")

// Append rects
bars.append("rect")
  .attr("class", "bar")
  .attr("y", function (d) { return y(d.name); })
  .attr("height", y.rangeBand())
  .attr("x", 0)
  .attr("width", function(d) { return x(d.value); })
  .attr("fill", function(d, i) { return barColor(d.name); });

// Construct x axis
var xAxis = d3.svg.axis()
  .scale(x)
  .ticks(5)
  .orient("bottom")
  .tickFormat(formatTicks);

var gx = svg.append("g")
  .attr("class", "x axis")
  .call(xAxis)
  .attr("transform", "translate(0," + height + ")");

// Construct y axis to show bar names
var yAxis = d3.svg.axis()
  .scale(y)
  .tickSize(0)
  .orient("left");

var gy = svg.append("g")
  .attr("class", "y axis")
  .call(yAxis)
  .selectAll(".y.axis text")
  .attr("transform", "translate(-180,0)");

// Add a value label to the left of each bar
bars.append("text")
  .attr("class", "label")
  .attr("text-anchor", "end")
  .attr("y", function(d) { return y(d.name) + y.rangeBand() / 2 + 4; })
  .attr("x", -35)
  .text(function(d) { return formatPercent(d.value); });

// Mark the total
var totalLine = svg.append("line")
  .attr("class", "total")
  .attr("x1", -25)
  .attr("y1", height)
  .attr("x2", -90)
  .attr("y2", height);

var totalLabel = svg.append("text")
  .attr("class", "total")
  .attr("text-anchor", "end")
  .attr("x", -35)
  .attr("y", height + 20)
  .text(function() { return formatPercent(1); });