Skip to content

Instantly share code, notes, and snippets.

@ckuijjer
Last active August 28, 2017 10:05
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 ckuijjer/f2411940bd9047f4bfc3713a1e3bd25d to your computer and use it in GitHub Desktop.
Save ckuijjer/f2411940bd9047f4bfc3713a1e3bd25d to your computer and use it in GitHub Desktop.
Interactive stacked bar chart using Protoviz
license: mit

An implementation in D3 of Figure 5-13 "Interactive stacked bar chart in Protovis" in "Visualize This" by Nathan Yau.

The implementation of this chart is quite like the one in the book, as both Protovis and D3 were created by Mike Bostock.

const data = {
Issue: [
'Race Relations',
'Education',
'Terrorism',
'Energy Policy',
'Foreign Affairs',
'Environment',
'Situation in Iraq',
'Taxes',
'Healthcare Policy',
'Economy',
'Situation in Afghanistan',
'Federal Budget Deficit',
'Immigration',
],
Approve: [52, 49, 48, 47, 44, 43, 41, 41, 40, 38, 36, 31, 29],
Disapprove: [38, 40, 45, 42, 48, 51, 53, 54, 57, 59, 57, 64, 62],
None: [10, 11, 7, 11, 8, 6, 6, 5, 3, 3, 7, 5, 9],
};
const width = 500,
height = 400,
margin = { top: 20, right: 100, bottom: 100, left: 40 },
chartWidth = width - margin.left - margin.right,
chartHeight = height - margin.top - margin.bottom;
// convert to a list of { Issue, Approve, Disapprove, None }
const transformedData = data.Issue.map((Issue, index) => ({
Issue,
Approve: data.Approve[index],
Disapprove: data.Disapprove[index],
None: data.None[index],
}));
const answers = ['Approve', 'Disapprove', 'None'];
const x = d3
.scaleBand()
.paddingInner(0.2)
.range([0, chartWidth])
.domain(data.Issue);
const y = d3.scaleLinear().range([chartHeight, 0]).domain([0, 100]);
const z = d3
.scaleOrdinal()
.range(['#809ead', '#b1c0c9', '#d7d6cb'])
.domain(answers);
const stack = d3.stack().keys(answers)(transformedData);
const svg = d3
.select('#container')
.append('svg')
.attr('width', width)
.attr('height', height);
const chart = svg
.append('g')
.attr('width', chartWidth)
.attr('height', chartHeight)
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// Add the X-axis
const xAxis = d3.axisBottom().scale(x).tickSize(0);
chart
.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0, ${chartHeight})`)
.call(xAxis)
.selectAll('text')
.style('text-anchor', 'end')
.attr('dy', '-.35em')
.attr('transform', 'translate(0, 10), rotate(-45)');
// Add the Y-axis
const yAxis = d3
.axisLeft()
.scale(y)
.tickFormat(d => (d === 100 ? '100%' : d))
.tickSize(15);
chart
.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(-10, 0)')
.call(yAxis)
.selectAll('text')
.attr('transform', 'translate(16, -10)');
const serieColor = d => z(d.key);
const serie = chart
.selectAll('.serie')
.data(stack)
.enter()
.append('g')
.attr('class', 'serie')
.attr('fill', serieColor)
.on('mouseover', (d, i, nodes) => d3.select(nodes[i]).attr('fill', '#555'))
.on('mouseout', (d, i, nodes) =>
d3.select(nodes[i]).attr('fill', serieColor)
);
const bar = serie
.selectAll('.bar')
.data((data, i, nodes) =>
data.map(d => {
// use the parent node to get the value
d.value = d.data[nodes[i].__data__.key];
return d;
})
)
.enter()
.append('g')
.attr('class', 'bar');
bar
.append('rect')
.attr('x', d => x(d.data.Issue))
.attr('width', x.bandwidth())
.attr('y', d => y(d[1]))
.attr('height', d => y(d[0]) - y(d[1]));
bar
.append('text')
.attr('x', d => x(d.data.Issue) + x.bandwidth() / 2)
.attr('y', d => y(d[1]) + (y(d[0]) - y(d[1])) / 2)
.attr('dy', '0.35em')
.attr('text-anchor', 'middle')
.attr('fill', '#fff')
.text(d => (d.value > 11 ? d.value : ''));
bar.append('title').text(d => `${d.value}%`);
const legend = serie
.append('g')
.attr('class', 'legend')
.append('text')
.attr('x', chartWidth + 8)
.attr('y', d => {
const last = d[d.length - 1];
return y(last[1]) + (y(last[0]) - y(last[1])) / 2;
})
.attr('dy', '0.35em')
.attr('fill', '#000')
.text(d => d.key === 'None' ? 'No Opinion': d.key);
<!DOCTYPE html>
<body>
<style>
body {
width: 100%;
font-family: Georgia;
font-size: 14px;
color: #333;
}
#container {
width: 500px;
margin: 0 auto;
position: relative;
}
#header h1 {
text-transform: uppercase;
font-size: 18px;
}
#header {
line-height: 1.4em;
margin-bottom: 16px;
}
.bar {
font-family: Arial;
font-size: 12px;
shape-rendering: crispEdges;
}
.x.axis path {
stroke-width: 2;
}
.y.axis path {
stroke-width: 0
}
.y.axis line {
stroke: #aaa;
stroke-width: 2;
}
.legend {
fill: #000;
font-style: italic;
}
</style>
<div id="container">
<div id="header">
<h1>Approval ratings for Barack Obama</h1>
Recent Gallup and CBS polls show a 52% approval rating for Barack Obama in race relations. It is the only issue out of the
below thirteen where he has a majority approval. In eight of the thirteen, results show a majority disapproval.
</div>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="app.js"></script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment