Skip to content

Instantly share code, notes, and snippets.

@rsloan
Last active April 10, 2019 08:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rsloan/7123450 to your computer and use it in GitHub Desktop.
Save rsloan/7123450 to your computer and use it in GitHub Desktop.
d3 Waterfall Chart

A simple example of a waterfall chart using d3, showing customer retention on a week by week basis for the past month.

period start of period new returned went inactive
Four Weeks Ago 100 20 30 40
Three Weeks Ago 110 30 20 30
Two Weeks Ago 130 40 25 40
One Week Ago 155 30 55 50
Current Week 190 0 0 0
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], 0, .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.ordinal()
.range(["#2DBAD4", "#33CC33", "#00E6B8", "#C9081D"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
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 + ")");
d3.csv("data.csv", function(error, data) {
var categories = d3.keys(data[0]).filter(function(key) {
return key !== "period";
});
data.forEach(function(d) {
//first calculate the starting height for each category
total = 0
totals = {}
for (var i=0; i<categories.length; i++) {
if (i == 3) {
total = total - +d[categories[i]];
totals[categories[i]] = total;
}
else {
totals[categories[i]] = total;
total += +d[categories[i]];
}
}
//then map category name, value, and starting height to each observation
d.formatted = categories.map(function(category) {
return {
name: category,
value: +d[category],
baseHeight: +totals[category]
};
});
});
x0.domain(data.map(function(d) { return d.period; }));
x1.domain(categories).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(d.formatted, function(d) {
return d.value + d.baseHeight; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Customers");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) {
return "translate(" + x0(d.period) + ",0)";
});
state.selectAll("rect")
.data(function(d) { return d.formatted; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value + d.baseHeight); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(categories.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
</script>
@isaacalves
Copy link

Thanks for that example!

On line 53 and 54, why is there an append() with a transform translate? Why does it need this in order to show the vertical axis?

Why I ask:

I've wrote a similar code, based on this, and that exact bit causes problems when I try to export the SVG as an image, for some reason (when getting a string from the SVG, encoding and setting as the source of an image element, the onload() event for the image does not fire)

If I remove those lines of code, the image export suddenly works, but then I can't see the vertical axis of my chart. I'm at the moment clueless why both of these things happen.

I "unchained" those two lines of code, like this, but nothing happens, the transform is not applied and the vertical axis not shown.
Why that does not work?

svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

@jacoduplessis
Copy link

Thanks for this. If anyone is looking for a v5 compatible example: https://gist.github.com/jacoduplessis/29209d40299e13d9a1aa09ce0eecf02f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment