Skip to content

Instantly share code, notes, and snippets.

@eesur
Last active January 29, 2018 12:57
Show Gist options
  • Save eesur/599f65bffe30425a4497f8dd90fc4478 to your computer and use it in GitHub Desktop.
Save eesur/599f65bffe30425a4497f8dd90fc4478 to your computer and use it in GitHub Desktop.
d3js | negative waterfall chart
license: mit
height: 500
border: no

Waterfall chart with negative values

The Waterfall chart renders the given data as a series of vertical bars, showing the cumulative effect of each bar. The chart is visualising a starting quantity, positive and negative changes to that quantity, and the resulting ending quantity. Ascending and descending bars are classed with 'up' (green) and 'down' (red) respectively.

Mike Bostock's vertical example More info

*{box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Consolas,monaco,monospace;width:960px;margin:0 auto;font-size:14px}text{fill:#454545}.bar.total rect{fill:#44a4f6}.bar.positive rect{fill:#82bb25}.bar.negative rect{fill:#fd3753}.bar text{font-weight:700;text-anchor:middle}.axis text{font-size:11px}.axis line,.axis path{fill:none;stroke:#eee;shape-rendering:crispEdges}.gridline{stroke:#ccc;stroke-width:1}
!function(n){function t(g){if(a[g])return a[g].exports;var e=a[g]={i:g,l:!1,exports:{}};return n[g].call(e.exports,e,e.exports,t),e.l=!0,e.exports}var a={};t.m=n,t.c=a,t.i=function(n){return n},t.d=function(n,a,g){t.o(n,a)||Object.defineProperty(n,a,{configurable:!1,enumerable:!0,get:g})},t.n=function(n){var a=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(a,"a",a),a},t.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},t.p="",t(t.s=1)}([function(module,exports,__webpack_require__){"use strict";eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nvar sampleData = [{\n label: 'one',\n value: -3.50\n}, {\n label: 'two',\n value: -2.94\n}, {\n label: 'three',\n value: -1.65\n}, {\n label: 'four',\n value: -0.89\n}, {\n label: 'five',\n value: -0.65\n}, {\n label: 'six',\n value: -0.45\n}, {\n label: 'seven',\n value: -0.34\n}, {\n label: 'eight',\n value: 0.0\n}, {\n label: 'nine',\n value: 0.12\n}, {\n label: 'ten',\n value: 0.21\n}, {\n label: 'eleven',\n value: 0.49\n}, {\n label: 'twelve',\n value: 0.62\n}, {\n label: 'thirteen',\n value: 1.28\n}, {\n label: 'fourteen',\n value: 1.57\n}];\n\nexports.default = sampleData;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9zYW1wbGVEYXRhLmpzPzM5N2YiXSwic291cmNlc0NvbnRlbnQiOlsiY29uc3Qgc2FtcGxlRGF0YSA9IFtcbiAge1xuICAgIGxhYmVsOiAnb25lJyxcbiAgICB2YWx1ZTogLTMuNTBcbiAgfSxcbiAge1xuICAgIGxhYmVsOiAndHdvJyxcbiAgICB2YWx1ZTogLTIuOTRcbiAgfSxcbiAge1xuICAgIGxhYmVsOiAndGhyZWUnLFxuICAgIHZhbHVlOiAtMS42NVxuICB9LFxuICB7XG4gICAgbGFiZWw6ICdmb3VyJyxcbiAgICB2YWx1ZTogLTAuODlcbiAgfSxcbiAge1xuICAgIGxhYmVsOiAnZml2ZScsXG4gICAgdmFsdWU6IC0wLjY1XG4gIH0sXG4gIHtcbiAgICBsYWJlbDogJ3NpeCcsXG4gICAgdmFsdWU6IC0wLjQ1XG4gIH0sXG4gIHtcbiAgICBsYWJlbDogJ3NldmVuJyxcbiAgICB2YWx1ZTogLTAuMzRcbiAgfSxcbiAge1xuICAgIGxhYmVsOiAnZWlnaHQnLFxuICAgIHZhbHVlOiAwLjBcbiAgfSxcbiAge1xuICAgIGxhYmVsOiAnbmluZScsXG4gICAgdmFsdWU6IDAuMTJcbiAgfSxcbiAge1xuICAgIGxhYmVsOiAndGVuJyxcbiAgICB2YWx1ZTogMC4yMVxuICB9LFxuICB7XG4gICAgbGFiZWw6ICdlbGV2ZW4nLFxuICAgIHZhbHVlOiAwLjQ5XG4gIH0sXG4gIHtcbiAgICBsYWJlbDogJ3R3ZWx2ZScsXG4gICAgdmFsdWU6IDAuNjJcbiAgfSxcbiAge1xuICAgIGxhYmVsOiAndGhpcnRlZW4nLFxuICAgIHZhbHVlOiAxLjI4XG4gIH0sXG4gIHtcbiAgICBsYWJlbDogJ2ZvdXJ0ZWVuJyxcbiAgICB2YWx1ZTogMS41N1xuICB9XG5dXG5cbmV4cG9ydCBkZWZhdWx0IHNhbXBsZURhdGFcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyBzYW1wbGVEYXRhLmpzIl0sIm1hcHBpbmdzIjoiOzs7OztBQUFBO0FBRUE7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBS0E7QUFDQTtBQUZBO0FBQ0E7QUFLQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///0\n")},function(module,exports,__webpack_require__){"use strict";eval("\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _sampleData = __webpack_require__(0);\n\nvar _sampleData2 = _interopRequireDefault(_sampleData);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar d3 = window.d3;\n\nfunction waterfall(bind, data, config) {\n config = _extends({\n f: d3.format('.1f'),\n margin: { top: 20, right: 80, bottom: 30, left: 20 },\n width: 960,\n height: 500,\n domainPadding: 1.1\n }, config);\n var _config = config,\n f = _config.f,\n margin = _config.margin,\n width = _config.width,\n height = _config.height,\n domainPadding = _config.domainPadding;\n\n var w = width - margin.left - margin.right;\n var h = height - margin.top - margin.bottom;\n var _data = waterfallData(data);\n\n var chart = d3.select(bind).attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');\n\n var x = d3.scaleBand().domain(_data.map(function (d) {\n return d.label;\n })).range([0, w]).padding(0.2);\n\n var y = d3.scaleLinear().range([h, 0]);\n\n var xAxis = d3.axisBottom(x);\n var yAxis = d3.axisRight(y);\n\n var d3Min = d3.min(_data, function (d) {\n return d.end;\n });\n var d3Max = d3.max(_data, function (d) {\n return d.end;\n });\n var maxValue = d3Max > 0 ? d3Max : 0;\n var minValue = d3Min < 0 ? d3Min : 0;\n\n x.domain(_data.map(function (d) {\n return d.label;\n }));\n y.domain([minValue * domainPadding, maxValue * domainPadding]);\n\n chart.append('g').attr('class', 'x-axis axis').attr('transform', 'translate(0,' + h + ')').call(xAxis);\n\n chart.append('g').attr('class', 'y-axis axis').attr('transform', 'translate(' + w + ', 0)').call(yAxis);\n\n // render some gridlines\n gridlines(chart, -h, -w);\n\n var bar = chart.selectAll('.bar').data(_data).enter().append('g').attr('class', function (d) {\n return 'bar ' + d.class;\n }).attr('transform', function (d) {\n return 'translate(' + x(d.label) + ',0)';\n });\n\n bar.append('rect').attr('class', function (d) {\n return 'rect bar ' + d.class;\n }).attr('y', function (d) {\n return y(Math.max(d.start, d.end));\n }).attr('height', function (d) {\n return Math.abs(y(d.start) - y(d.end));\n }).attr('width', x.bandwidth());\n\n bar.append('text').attr('x', x.bandwidth() / 2).attr('y', function (d) {\n return y(d.end) + 5;\n }).attr('dy', function (d) {\n return (d.class === 'negative' ? '-' : '') + '.75em';\n }).text(function (d) {\n return f(d.end - d.start);\n });\n // .text(d => f(d.value))\n}\n// render the chart\nwaterfall('.chart', _sampleData2.default);\n\n// generate gridlines using tick groups\nfunction gridlines(selection, h, w) {\n selection.selectAll('g.x-axis g.tick').append('line').classed('gridline', true).attr('x1', 0).attr('y1', 0).attr('x2', 0).attr('y2', h);\n selection.selectAll('g.y-axis g.tick').append('line').classed('gridline', true).attr('x1', 0).attr('y1', 0).attr('x2', w).attr('y2', 0);\n}\n// transform data for floating bars\nfunction waterfallData(data) {\n var cumulativeValue = 0;\n data.forEach(function (d) {\n d.start = cumulativeValue;\n cumulativeValue += d.value;\n d.end = cumulativeValue;\n d.class = d.value >= 0 ? 'positive' : 'negative';\n });\n data.push({\n label: 'Total',\n end: cumulativeValue,\n start: 0,\n class: 'total'\n });\n return data;\n}//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9zY3JpcHQuanM/OWE5NSJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgc2FtcGxlRGF0YSBmcm9tICcuL3NhbXBsZURhdGEnXG5jb25zdCBkMyA9IHdpbmRvdy5kM1xuXG5mdW5jdGlvbiB3YXRlcmZhbGwgKGJpbmQsIGRhdGEsIGNvbmZpZykge1xuICBjb25maWcgPSB7XG4gICAgZjogZDMuZm9ybWF0KCcuMWYnKSxcbiAgICBtYXJnaW46IHt0b3A6IDIwLCByaWdodDogODAsIGJvdHRvbTogMzAsIGxlZnQ6IDIwfSxcbiAgICB3aWR0aDogOTYwLFxuICAgIGhlaWdodDogNTAwLFxuICAgIGRvbWFpblBhZGRpbmc6IDEuMSxcbiAgICAuLi5jb25maWdcbiAgfVxuICBjb25zdCB7ZiwgbWFyZ2luLCB3aWR0aCwgaGVpZ2h0LCBkb21haW5QYWRkaW5nfSA9IGNvbmZpZ1xuICBjb25zdCB3ID0gd2lkdGggLSBtYXJnaW4ubGVmdCAtIG1hcmdpbi5yaWdodFxuICBjb25zdCBoID0gaGVpZ2h0IC0gbWFyZ2luLnRvcCAtIG1hcmdpbi5ib3R0b21cbiAgY29uc3QgX2RhdGEgPSB3YXRlcmZhbGxEYXRhKGRhdGEpXG5cbiAgY29uc3QgY2hhcnQgPSBkMy5zZWxlY3QoYmluZClcbiAgICAuYXR0cignd2lkdGgnLCB3aWR0aClcbiAgICAuYXR0cignaGVpZ2h0JywgaGVpZ2h0KVxuICAuYXBwZW5kKCdnJylcbiAgICAuYXR0cigndHJhbnNmb3JtJywgJ3RyYW5zbGF0ZSgnICsgbWFyZ2luLmxlZnQgKyAnLCcgKyBtYXJnaW4udG9wICsgJyknKVxuXG4gIGNvbnN0IHggPSBkMy5zY2FsZUJhbmQoKVxuICAuZG9tYWluKF9kYXRhLm1hcChkID0+IGQubGFiZWwpKVxuICAucmFuZ2UoWzAsIHddKVxuICAucGFkZGluZygwLjIpXG5cbiAgY29uc3QgeSA9IGQzLnNjYWxlTGluZWFyKClcbiAgLnJhbmdlKFtoLCAwXSlcblxuICBjb25zdCB4QXhpcyA9IGQzLmF4aXNCb3R0b20oeClcbiAgY29uc3QgeUF4aXMgPSBkMy5heGlzUmlnaHQoeSlcblxuICBjb25zdCBkM01pbiA9IGQzLm1pbihfZGF0YSwgZCA9PiBkLmVuZClcbiAgY29uc3QgZDNNYXggPSBkMy5tYXgoX2RhdGEsIGQgPT4gZC5lbmQpXG4gIGNvbnN0IG1heFZhbHVlID0gKGQzTWF4ID4gMCkgPyBkM01heCA6IDBcbiAgY29uc3QgbWluVmFsdWUgPSAoZDNNaW4gPCAwKSA/IGQzTWluIDogMFxuXG4gIHguZG9tYWluKF9kYXRhLm1hcChkID0+IGQubGFiZWwpKVxuICB5LmRvbWFpbihbbWluVmFsdWUgKiBkb21haW5QYWRkaW5nLCBtYXhWYWx1ZSAqIGRvbWFpblBhZGRpbmddKVxuXG4gIGNoYXJ0LmFwcGVuZCgnZycpXG4gICAgLmF0dHIoJ2NsYXNzJywgJ3gtYXhpcyBheGlzJylcbiAgICAuYXR0cigndHJhbnNmb3JtJywgJ3RyYW5zbGF0ZSgwLCcgKyBoICsgJyknKVxuICAgIC5jYWxsKHhBeGlzKVxuXG4gIGNoYXJ0LmFwcGVuZCgnZycpXG4gICAgLmF0dHIoJ2NsYXNzJywgJ3ktYXhpcyBheGlzJylcbiAgICAuYXR0cigndHJhbnNmb3JtJywgJ3RyYW5zbGF0ZSgnICsgdyArICcsIDApJylcbiAgICAuY2FsbCh5QXhpcylcblxuICAvLyByZW5kZXIgc29tZSBncmlkbGluZXNcbiAgZ3JpZGxpbmVzKGNoYXJ0LCAtaCwgLXcpXG5cbiAgY29uc3QgYmFyID0gY2hhcnQuc2VsZWN0QWxsKCcuYmFyJylcbiAgICAgIC5kYXRhKF9kYXRhKVxuICAgIC5lbnRlcigpLmFwcGVuZCgnZycpXG4gICAgICAuYXR0cignY2xhc3MnLCBkID0+ICdiYXIgJyArIGQuY2xhc3MpXG4gICAgICAuYXR0cigndHJhbnNmb3JtJywgZCA9PiAndHJhbnNsYXRlKCcgKyB4KGQubGFiZWwpICsgJywwKScpXG5cbiAgYmFyLmFwcGVuZCgncmVjdCcpXG4gICAgLmF0dHIoJ2NsYXNzJywgZCA9PiAncmVjdCBiYXIgJyArIGQuY2xhc3MpXG4gICAgLmF0dHIoJ3knLCBkID0+IHkoTWF0aC5tYXgoZC5zdGFydCwgZC5lbmQpKSlcbiAgICAuYXR0cignaGVpZ2h0JywgZCA9PiBNYXRoLmFicyh5KGQuc3RhcnQpIC0geShkLmVuZCkpKVxuICAgIC5hdHRyKCd3aWR0aCcsIHguYmFuZHdpZHRoKCkpXG5cbiAgYmFyLmFwcGVuZCgndGV4dCcpXG4gICAgLmF0dHIoJ3gnLCB4LmJhbmR3aWR0aCgpIC8gMilcbiAgICAuYXR0cigneScsIGQgPT4geShkLmVuZCkgKyA1KVxuICAgIC5hdHRyKCdkeScsIGQgPT4gKChkLmNsYXNzID09PSAnbmVnYXRpdmUnKSA/ICctJyA6ICcnKSArICcuNzVlbScpXG4gICAgLnRleHQoZCA9PiBmKChkLmVuZCAtIGQuc3RhcnQpKSlcbiAgICAvLyAudGV4dChkID0+IGYoZC52YWx1ZSkpXG59XG4vLyByZW5kZXIgdGhlIGNoYXJ0XG53YXRlcmZhbGwoJy5jaGFydCcsIHNhbXBsZURhdGEpXG5cbi8vIGdlbmVyYXRlIGdyaWRsaW5lcyB1c2luZyB0aWNrIGdyb3Vwc1xuZnVuY3Rpb24gZ3JpZGxpbmVzIChzZWxlY3Rpb24sIGgsIHcpIHtcbiAgc2VsZWN0aW9uLnNlbGVjdEFsbCgnZy54LWF4aXMgZy50aWNrJylcbiAgICAuYXBwZW5kKCdsaW5lJylcbiAgICAgIC5jbGFzc2VkKCdncmlkbGluZScsIHRydWUpXG4gICAgICAuYXR0cigneDEnLCAwKVxuICAgICAgLmF0dHIoJ3kxJywgMClcbiAgICAgIC5hdHRyKCd4MicsIDApXG4gICAgICAuYXR0cigneTInLCBoKVxuICBzZWxlY3Rpb24uc2VsZWN0QWxsKCdnLnktYXhpcyBnLnRpY2snKVxuICAgIC5hcHBlbmQoJ2xpbmUnKVxuICAgICAgLmNsYXNzZWQoJ2dyaWRsaW5lJywgdHJ1ZSlcbiAgICAgIC5hdHRyKCd4MScsIDApXG4gICAgICAuYXR0cigneTEnLCAwKVxuICAgICAgLmF0dHIoJ3gyJywgdylcbiAgICAgIC5hdHRyKCd5MicsIDApXG59XG4vLyB0cmFuc2Zvcm0gZGF0YSBmb3IgZmxvYXRpbmcgYmFyc1xuZnVuY3Rpb24gd2F0ZXJmYWxsRGF0YSAoZGF0YSkge1xuICBsZXQgY3VtdWxhdGl2ZVZhbHVlID0gMFxuICBkYXRhLmZvckVhY2goZCA9PiB7XG4gICAgZC5zdGFydCA9IGN1bXVsYXRpdmVWYWx1ZVxuICAgIGN1bXVsYXRpdmVWYWx1ZSArPSBkLnZhbHVlXG4gICAgZC5lbmQgPSBjdW11bGF0aXZlVmFsdWVcbiAgICBkLmNsYXNzID0gKGQudmFsdWUgPj0gMCkgPyAncG9zaXRpdmUnIDogJ25lZ2F0aXZlJ1xuICB9KVxuICBkYXRhLnB1c2goe1xuICAgIGxhYmVsOiAnVG90YWwnLFxuICAgIGVuZDogY3VtdWxhdGl2ZVZhbHVlLFxuICAgIHN0YXJ0OiAwLFxuICAgIGNsYXNzOiAndG90YWwnXG4gIH0pXG4gIHJldHVybiBkYXRhXG59XG5cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gc2NyaXB0LmpzIl0sIm1hcHBpbmdzIjoiOzs7O0FBQUE7QUFDQTs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFMQTtBQURBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUNBO0FBU0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBS0E7QUFDQTtBQUFBO0FBQ0E7QUFHQTtBQUNBO0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQ0E7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFJQTtBQUNBO0FBSUE7QUFDQTtBQUNBO0FBQ0E7QUFHQTtBQUFBO0FBQ0E7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBQ0E7QUFBQTtBQUNBO0FBQUE7QUFDQTtBQUVBO0FBRUE7QUFBQTtBQUNBO0FBQUE7QUFDQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQU9BO0FBT0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBSkE7QUFNQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///1\n")}]);
<!DOCTYPE html>
<title>blockup</title>
<link href='dist.css' rel='stylesheet' />
<body>
<svg class="chart"></svg>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='dist.js'></script>
</body>
const sampleData = [
{
label: 'one',
value: -3.50
},
{
label: 'two',
value: -2.94
},
{
label: 'three',
value: -1.65
},
{
label: 'four',
value: -0.89
},
{
label: 'five',
value: -0.65
},
{
label: 'six',
value: -0.45
},
{
label: 'seven',
value: -0.34
},
{
label: 'eight',
value: 0.0
},
{
label: 'nine',
value: 0.12
},
{
label: 'ten',
value: 0.21
},
{
label: 'eleven',
value: 0.49
},
{
label: 'twelve',
value: 0.62
},
{
label: 'thirteen',
value: 1.28
},
{
label: 'fourteen',
value: 1.57
}
]
export default sampleData
import sampleData from './sampleData'
const d3 = window.d3
function waterfall (bind, data, config) {
config = {
f: d3.format('.1f'),
margin: {top: 20, right: 80, bottom: 30, left: 20},
width: 960,
height: 500,
domainPadding: 1.1,
...config
}
const {f, margin, width, height, domainPadding} = config
const w = width - margin.left - margin.right
const h = height - margin.top - margin.bottom
const _data = waterfallData(data)
const chart = d3.select(bind)
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
const x = d3.scaleBand()
.domain(_data.map(d => d.label))
.range([0, w])
.padding(0.2)
const y = d3.scaleLinear()
.range([h, 0])
const xAxis = d3.axisBottom(x)
const yAxis = d3.axisRight(y)
const d3Min = d3.min(_data, d => d.end)
const d3Max = d3.max(_data, d => d.end)
const maxValue = (d3Max > 0) ? d3Max : 0
const minValue = (d3Min < 0) ? d3Min : 0
x.domain(_data.map(d => d.label))
y.domain([minValue * domainPadding, maxValue * domainPadding])
chart.append('g')
.attr('class', 'x-axis axis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis)
chart.append('g')
.attr('class', 'y-axis axis')
.attr('transform', 'translate(' + w + ', 0)')
.call(yAxis)
// render some gridlines
gridlines(chart, -h, -w)
const bar = chart.selectAll('.bar')
.data(_data)
.enter().append('g')
.attr('class', d => 'bar ' + d.class)
.attr('transform', d => 'translate(' + x(d.label) + ',0)')
bar.append('rect')
.attr('class', d => 'rect bar ' + d.class)
.attr('y', d => y(Math.max(d.start, d.end)))
.attr('height', d => Math.abs(y(d.start) - y(d.end)))
.attr('width', x.bandwidth())
bar.append('text')
.attr('x', x.bandwidth() / 2)
.attr('y', d => y(d.end) + 5)
.attr('dy', d => ((d.class === 'negative') ? '-' : '') + '.75em')
.text(d => f((d.end - d.start)))
// .text(d => f(d.value))
}
// render the chart
waterfall('.chart', sampleData)
// generate gridlines using tick groups
function gridlines (selection, h, w) {
selection.selectAll('g.x-axis g.tick')
.append('line')
.classed('gridline', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', h)
selection.selectAll('g.y-axis g.tick')
.append('line')
.classed('gridline', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', w)
.attr('y2', 0)
}
// transform data for floating bars
function waterfallData (data) {
let cumulativeValue = 0
data.forEach(d => {
d.start = cumulativeValue
cumulativeValue += d.value
d.end = cumulativeValue
d.class = (d.value >= 0) ? 'positive' : 'negative'
})
data.push({
label: 'Total',
end: cumulativeValue,
start: 0,
class: 'total'
})
return data
}
*
box-sizing border-box
body
font-family:-apple-system,BlinkMacSystemFont,Consolas,monaco,monospace
width: 960px
margin: 0 auto
font-size: 14px
text
fill: #454545
.bar.total rect {
fill: #44a4f6;
}
.bar.positive rect {
fill: #82bb25;
}
.bar.negative rect {
fill: #fd3753;
}
.bar text {
font-weight: 700;
text-anchor: middle;
}
.axis text {
font-size: 11px
}
.axis path,
.axis line {
fill: none;
stroke: #eee;
shape-rendering: crispEdges;
}
.gridline {
stroke: #ccc;
stroke-width: 1.0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment