Skip to content

Instantly share code, notes, and snippets.

@karen-izuka
Last active February 17, 2020 20:03
Show Gist options
  • Save karen-izuka/ba990860762dc1b51910b88d8e242609 to your computer and use it in GitHub Desktop.
Save karen-izuka/ba990860762dc1b51910b88d8e242609 to your computer and use it in GitHub Desktop.
Animated Stacked Barplot
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>The Gap Revenue by Brand</h1>
<div id="legend"></div>
<div id="chart"></div>
<br>
<form id="control">
<label><input type="radio" name="mode" value="dollar" checked>Dollars ($M)</label>
<label><input type="radio" name="mode" value="percent">Percent (%)</label>
</form>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="index.js"></script>
</body>
</html>
const load = async () => {
const url = 'https://gist.githubusercontent.com/karen-izuka/adcf08accb53fedc410003acc85e8f8c/raw/0edef058740771200211192ff35979f722ca44af/data.csv'
const data_01 = await d3.csv(url, ({year, old_navy, gap, banana}) => ({
year: year, old_navy: +old_navy, gap: +gap, banana: +banana
}));
const data_02 = [];
for (let i=0; i<data_01.length; i++) {
data_02.push({
year: data_01[i].year,
old_navy: data_01[i].old_navy / (data_01[i].old_navy + data_01[i].gap + data_01[i].banana),
gap: data_01[i].gap / (data_01[i].old_navy + data_01[i].gap + data_01[i].banana),
banana: data_01[i].banana / (data_01[i].old_navy + data_01[i].gap + data_01[i].banana)
});
}
data_02.columns = ['year', 'old_navy', 'gap', 'banana'];
chart(data_01, data_02);
}
const chart = (data_01, data_02) => {
const margin = {top: 25, right: 25, bottom: 25, left: 75};
const width = 1250 - margin.left - margin.right;
const height = 600 - margin.top - margin.bottom;
const svg = d3.select('#chart')
.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})`);
const legend = d3.select('#legend')
.append('svg')
.attr('class', 'legend')
.attr('width', 315)
.attr('height', 50)
.append('g')
.attr('transform', `translate(10, 25)`);
const x = d3.scaleBand()
.range([0, width])
.padding(0.2);
const y = d3.scaleLinear()
.range([height, 0]);
const z = d3.scaleOrdinal()
.range(['#4C5F97', '#8C92AC', '#747680']);
//update
const radio = document.querySelector('#control').mode;
for (let i=0; i<radio.length; i++) {
radio[i].addEventListener('change', update);
}
function update() {
const mode = this.value;
if (mode === 'dollar') {draw(data_01, mode)} else {draw(data_02, mode)}
}
const draw = (data, mode) => {
svg.selectAll('.rect').remove();
svg.selectAll('.label').remove();
svg.selectAll('.axis').remove();
const groups = d3.map(data, d => d.year).keys();
const subgroups = data.columns.slice(1);
x.domain(groups);
y.domain([0, d3.max(data, d => d.old_navy + d.gap + d.banana)]);
z.domain(subgroups);
const xAxis = g => g
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
.call(g => g.select('.domain').remove());
const yAxis = g => g
.call(d3.axisLeft(y).tickSizeOuter(0).ticks(17))
.call(g => g.select('.domain').remove());
const format = mode === 'percent' ? d3.format('.1%') : d3.format('$,d');
let stack = d3.stack()
.keys(subgroups)
(data);
const g = svg.append('g')
.selectAll('g')
.data(stack)
.join('g')
.attr('class', d => `${d.key}`)
.attr('fill', d => z(d.key));
const bar = g.selectAll('rect')
.data(d => d)
.join('rect')
.attr('class', 'rect')
.attr('x', d => x(d.data.year))
.attr('y', d => y(d[1]))
.attr('width', x.bandwidth())
.attr('height', d => y(d[0]) - y(d[1]))
.attr('stroke', '#FFFFFF')
.attr('stroke-width', 3)
.attr('opacity', 0);
bar.transition()
.duration(1000)
.delay((d,i) => i*100)
.ease(d3.easeQuadIn)
.attr('opacity', 1);
const text = g.selectAll('text')
.data(d => d)
.join('text')
.text(d => `${format(d[1] - d[0])}`)
.attr('class', 'label')
.attr('x', d => x(d.data.year) + x.bandwidth()/2)
.attr('y', d => y(d[1]) + 20)
.attr('fill', 'black')
.attr('text-anchor', 'middle')
.attr('opacity', 0);
text.transition()
.duration(1000)
.delay((d,i) => i*100)
.ease(d3.easeQuadIn)
.attr('opacity', 1);
const gx = svg.append('g')
.attr('class', 'axis')
.call(xAxis);
const gy = svg.append('g')
.attr('class', 'axis')
.call(yAxis);
legend.selectAll('circle')
.data(subgroups)
.join('circle')
.attr('cx', (d, i) => i * 100)
.attr('cy', 0)
.attr('r', 5)
.attr('fill', d => z(d));
legend.selectAll('text')
.data(['Old Navy', 'Gap', 'Banana Republic'])
.join('text')
.attr('x', (d, i) => i * 100 + 10)
.attr('y', 0)
.attr('alignment-baseline', 'middle')
.text(d => d);
}
draw(data_01);
}
load();
body {
text-align: center;
}
form {
display: inline-block;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
padding: 5px;
}
h1 {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
text-rendering: optimizeLegibility;
}
svg {
display: block;
margin: auto;
}
.axis {
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
shape-rendering: crispEdges;
text-rendering: optimizeLegibility;
}
.label {
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
text-rendering: optimizeLegibility;
}
.legend {
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
text-rendering: optimizeLegibility;
}
.rect {
shape-rendering: crispEdges;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment