Skip to content

Instantly share code, notes, and snippets.

@LemoNode
Last active November 26, 2018 12:48
Show Gist options
  • Save LemoNode/21d81ff82e80cbe4acbfee28ff060b11 to your computer and use it in GitHub Desktop.
Save LemoNode/21d81ff82e80cbe4acbfee28ff060b11 to your computer and use it in GitHub Desktop.
Stacked horizontal bar chart
license: gpl-3.0
Year State Under 5 Years 5 to 13 Years 14 to 17 Years
2017 AL 552 259 310
2017 AK 856 421 520
2017 AZ 828 362 515
2017 AR 343 157 202
2017 CA 449 215 270
2017 CO 587 261 358
2017 CT 403 196 211
2017 DE 794 474 593
2018 AL 310 552 259
2018 AK 520 556 421
2018 AZ 515 828 362
2018 AR 202 343 157
2018 CA 270 449 215
2018 CO 358 587 261
2018 CT 211 403 196
2018 DE 593 994 474
<html>
<head>
<meta charset ="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
body {
margin: auto;
width: 650px;
font: 12px arial;
}
</style>
</head>
<body>
<svg id="chart" width="650" height="420"></svg>
Select year:
<select id="year"></select>
<input type="checkbox" id="sort">
Toggle sort
<script>
d3.csv("data.csv").then(d => chart(d))
function chart(csv) {
var keys = csv.columns.slice(2);
var year = [...new Set(csv.map(d => d.Year))]
var states = [...new Set(csv.map(d => d.State))]
var options = d3.select("#year").selectAll("option")
.data(year)
.enter().append("option")
.text(d => d)
var svg = d3.select("#chart"),
margin = {top: 35, left: 35, bottom: 0, right: 15},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var y = d3.scaleBand()
.range([margin.top, height - margin.bottom])
.padding(0.1)
.paddingOuter(0.2)
.paddingInner(0.2)
var x = d3.scaleLinear()
.range([margin.left, width - margin.right])
var yAxis = svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.attr("class", "y-axis")
var xAxis = svg.append("g")
.attr("transform", `translate(0,${margin.top})`)
.attr("class", "x-axis")
var z = d3.scaleOrdinal()
.range(["steelblue", "darkorange", "lightblue"])
.domain(keys);
update(d3.select("#year").property("value"), 0)
function update(input, speed) {
var data = csv.filter(f => f.Year == input)
data.forEach(function(d) {
d.total = d3.sum(keys, k => +d[k])
return d
})
x.domain([0, d3.max(data, d => d.total)]).nice();
svg.selectAll(".x-axis").transition().duration(speed)
.call(d3.axisTop(x).ticks(null, "s"))
data.sort(d3.select("#sort").property("checked")
? (a, b) => b.total - a.total
: (a, b) => states.indexOf(a.State) - states.indexOf(b.State))
y.domain(data.map(d => d.State));
svg.selectAll(".y-axis").transition().duration(speed)
.call(d3.axisLeft(y).tickSizeOuter(0))
var group = svg.selectAll("g.layer")
.data(d3.stack().keys(keys)(data), d => d.key)
group.exit().remove()
group.enter().insert("g", ".y-axis").append("g")
.classed("layer", true)
.attr("fill", d => z(d.key));
var bars = svg.selectAll("g.layer").selectAll("rect")
.data(d => d, e => e.data.State);
bars.exit().remove()
bars.enter().append("rect")
.attr("height", y.bandwidth())
.merge(bars)
.transition().duration(speed)
.attr("y", d => y(d.data.State))
.attr("x", d => x(d[0]))
.attr("width", d => x(d[1]) - x(d[0]))
var text = svg.selectAll(".text")
.data(data, d => d.State);
text.exit().remove()
text.enter().append("text")
.attr("class", "text")
.attr("text-anchor", "start")
.merge(text)
.transition().duration(speed)
.attr("y", d => y(d.State) + y.bandwidth() / 2)
.attr("x", d => x(d.total) + 5)
.text(d => d.total)
}
var select = d3.select("#year")
.on("change", function() {
update(this.value, 750)
})
var checkbox = d3.select("#sort")
.on("click", function() {
update(select.property("value"), 750)
})
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment