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.
Last active
January 29, 2018 12:57
-
-
Save eesur/599f65bffe30425a4497f8dd90fc4478 to your computer and use it in GitHub Desktop.
d3js | negative waterfall chart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit | |
height: 500 | |
border: no |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*{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} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
!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")}]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
* | |
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