Skip to content

Instantly share code, notes, and snippets.

@johnkiernander
Last active April 11, 2018 00:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnkiernander/6449830 to your computer and use it in GitHub Desktop.
Save johnkiernander/6449830 to your computer and use it in GitHub Desktop.
Horizontal Waterfall
<div id="chartContainer">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://dimplejs.org/dist/dimple.v1.js"></script>
<script type="text/javascript">
// This code should support any data in this structure changing the data here
// should be dealt with by the the code below. The y axis min and max must
// be able to contain the data required or the results will be incorrect.
// Also the "bar" elements must have the correct value (sum of previous "bar"
// + intervening "var" elements)
var xMin = -400000,
xMax = 400000,
yLabel = "Bar",
xLabel = "Value";
data = [
{ "label":"2011", "value":200000, "type":"bar" },
{ "label":"Alpha", "value":-100000, "type":"var" },
{ "label":"Beta", "value":200000, "type":"var" },
{ "label":"Delta", "value":-500000, "type":"var" },
{ "label":"Gamma", "value":-100000, "type":"var" },
{ "label":"2012", "value":-300000, "type":"bar" },
{ "label":"Alpha", "value":150000, "type":"var" },
{ "label":"Beta", "value":100000, "type":"var" },
{ "label":"Delta", "value":-35000, "type":"var" },
{ "label":"Gamma", "value":205000, "type":"var" },
{ "label":"2013", "value":120000, "type":"bar" }
];
// Create the svg as usual
var svg = dimple.newSvg("#chartContainer", 590, 400);
// The waterfall requires some data manipulation in a similar
// way that one would create a waterfall in Excel. The data
// will be padded and rearranged for display
var waterfallData = [];
// Maintain a running total for padding
var runningTotal = 0;
// Calculate the base level of padding, this is to deal with
// waterfalls which cross the x-axis
var padBase = Math.abs(xMin);
// Rearrange the data
data.forEach(function (d, i) {
// Add the padding bar, this will be removed later but is used
// for positioning
waterfallData.push({
y: i,
x: padBase
+ (d.type === "var" ? runningTotal : 0)
+ (d.value < 0 ? d.value : 0),
series: "pad" });
// Add the bar itself. The values are always positive here. The labels in
// popups will be changed later.
waterfallData.push({
y: i,
x: Math.abs(d.value),
series: (d.type === "bar" ?
"bar" : (d.value < 0 ?
"negative" : "positive")) });
// Move the running total for managing the flow
runningTotal = (d.type === "var" ? runningTotal : 0) + d.value;
});
// Create the chart from the updated data
var myChart = new dimple.chart(svg, waterfallData);
myChart.setBounds(60, 30, 510, 335)
var y = myChart.addCategoryAxis("y", "y");
// By default bar charts are ordered by value, this forces the ordering by
// name (which at this point includes a leading string for positioning)
y.addOrderRule("y");
// We need 2 x axes. The bars will be positioned using
// the second axis (all positive) and the first axis
// will be used to draw the visible axis, In an all positive waterfall
// both axes will be the same but for a negative the x2 axis will go from 0
// to abs(xMin) + xMax to allow axis crossing
var x1 = myChart.addMeasureAxis("x", xLabel);
x1.overrideMin = padBase * -1;
x1.overrideMax = xMax;
var x2 = myChart.addMeasureAxis("x", "x");
x2.overrideMin = 0;
x2.overrideMax = padBase + xMax;
x2.hidden = true;
// Add the series
var s = myChart.addSeries(["series"], dimple.plot.bar, [y, x2]);
// Assign blue, red and green using the default colors, these can
// be any colors. We could assign transparency to the "pad" bars but
// to avoid tooltips we delete the bars after drawing instead
myChart.assignColor("bar", myChart.defaultColors[0].fill);
myChart.assignColor("negative", myChart.defaultColors[1].fill);
myChart.assignColor("positive", myChart.defaultColors[3].fill);
// Draw the chart. Code beyond this point manipulates the objects
// and hacks some changes to the chart which would break in the event
// of redrawing.
myChart.draw();
// Remove the axis titles here
y.titleShape.remove();
x1.titleShape.remove();
// Remove the leading numbers from the y axis text
y.shapes.selectAll("text").text(function (d) {
return (d === "" ? "" : data[parseInt(d)].label);
});
// Remove the padding elements entirely
svg.selectAll(".pad").remove();
// Change the measure name and the category names for the tooltips
s.x.measure = xLabel;
s.y.categoryFields = [yLabel];
// Remove the category fields from the series, to remove from the tooltips
s.categoryFields = [];
s.shapes.each(function (d) {
// Get the index from the original data
var j = parseInt(d.yField[0]);
// Remove the unwanted y label prefixes from the shapes for the tooltips
d.yField[0] = data[j].label;
// Change the values back to negatives where necessary for tooltips
d.width = data[j].value;
});
</script>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment