Skip to content

Instantly share code, notes, and snippets.

@eesur
Last active September 10, 2018 14:06
Show Gist options
  • Save eesur/4d0e2d9390bfbb414ec3e517b1497c72 to your computer and use it in GitHub Desktop.
Save eesur/4d0e2d9390bfbb414ec3e517b1497c72 to your computer and use it in GitHub Desktop.
d3 | split bar chart
license: mit
height: 500
border: no

bar chart showing values split around a pivot line i.e. people left on the left and people remaining on the right (length of bar is total)

body,text{font-family:'Space Mono',monospace}*{box-sizing:border-box}body{font-size:11px}text{fill:#35342f}.axis-left line,.axis-left path{stroke:#f45844}.axis-left text{fill:#f45844}.axis-right line,.axis-right path{stroke:#e6c700}.axis-right text{fill:#e6c700;font-family:Consolas,monaco,monospace}
!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});\nexports.default = [{ label: 'set-1', right: rInt(100, 300), left: rInt(20, 100) }, { label: 'set-2', right: rInt(100, 250), left: rInt(20, 80) }, { label: 'set-3', right: rInt(30, 150), left: rInt(5, 30) }, { label: 'set-4', right: rInt(2, 100), left: rInt(0, 50) }, { label: 'set-5', right: rInt(2, 50), left: rInt(0, 20) }];\n\n\nfunction rInt(min, max) {\n return Math.floor(d3.randomUniform(min, max)());\n}//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9zYW1wbGVEYXRhLmpzPzM5N2YiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgW1xuICB7bGFiZWw6ICdzZXQtMScsIHJpZ2h0OiBySW50KDEwMCwgMzAwKSwgbGVmdDogckludCgyMCwgMTAwKX0sXG4gIHtsYWJlbDogJ3NldC0yJywgcmlnaHQ6IHJJbnQoMTAwLCAyNTApLCBsZWZ0OiBySW50KDIwLCA4MCl9LFxuICB7bGFiZWw6ICdzZXQtMycsIHJpZ2h0OiBySW50KDMwLCAxNTApLCBsZWZ0OiBySW50KDUsIDMwKX0sXG4gIHtsYWJlbDogJ3NldC00JywgcmlnaHQ6IHJJbnQoMiwgMTAwKSwgbGVmdDogckludCgwLCA1MCl9LFxuICB7bGFiZWw6ICdzZXQtNScsIHJpZ2h0OiBySW50KDIsIDUwKSwgbGVmdDogckludCgwLCAyMCl9XG5dXG5cbmZ1bmN0aW9uIHJJbnQgKG1pbiwgbWF4KSB7XG4gIHJldHVybiBNYXRoLmZsb29yKGQzLnJhbmRvbVVuaWZvcm0obWluLCBtYXgpKCkpXG59XG5cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gc2FtcGxlRGF0YS5qcyJdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFNQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\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 splitBar(bind, data, config) {\n config = _extends({\n margin: { top: 80, right: 10, bottom: 10, left: 10 },\n width: 960,\n // height: 400, // calc height via datums so we don't get ugly size rects\n barFillRight: '#e6c700', // colour of right bars\n barFillLeft: '#f45844', // colour of left+ bars\n lineStroke: '#35342f',\n lineStrokeWidth: 4,\n // be able to change the keys in data\n leftKey: 'left',\n rightKey: 'right',\n labelKey: 'label'\n }, config);\n var _config = config,\n margin = _config.margin,\n width = _config.width,\n leftKey = _config.leftKey,\n rightKey = _config.rightKey;\n\n var height = data.length * 30 + margin.top + margin.bottom;\n var w = width - margin.left - margin.right;\n var h = height - margin.top - margin.bottom;\n\n // calc max value for whole data set\n var maxValues = [d3.max(data, function (d) {\n return d[leftKey];\n }), d3.max(data, function (d) {\n return d[rightKey];\n })];\n // const minValues = [\n // d3.min(data, d => d[leftKey]),\n // d3.min(data, d => d[rightKey])\n // ]\n\n // access labels\n var labels = data.map(function (d) {\n return d[config.labelKey];\n });\n\n // set up scales\n var bandScale = d3.scaleBand().domain(labels).range([10, h]).paddingInner(0.2);\n // scale for bars and right axis\n var barWidth = d3.scaleLinear().domain([0, d3.max(maxValues)]).range([0, w / 2]);\n // scale for left axis\n var leftAxisScale = d3.scaleLinear().domain([0, d3.max(maxValues)]).range([w / 2, 0]);\n\n var svg = d3.select(bind).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', function (d) {\n return 'translate(' + margin.left + ', ' + margin.top + ')';\n });\n\n var axisRight = svg.append('g').attr('class', 'axis-right').attr('transform', function (d) {\n return 'translate(' + w / 2 + ', ' + -margin.top / 2 + ')';\n });\n var axisLeft = svg.append('g').attr('class', 'axis-left').attr('transform', function (d) {\n return 'translate(' + 0 + ', ' + -margin.top / 2 + ')';\n });\n\n var g = svg.selectAll('.bar').data(data).enter().append('g').attr('class', 'bar').attr('transform', function (d) {\n return 'translate(' + w / 2 + ', ' + bandScale(d[config.labelKey]) + ')';\n });\n // append left bars\n g.append('rect').attr('class', 'bar-left').attr('x', 0).attr('width', 0).attr('height', bandScale.bandwidth()).style('fill', config.barFillLeft).transition().ease(d3.easeSinOut).delay(1000).duration(1000).attr('x', function (d) {\n return -barWidth(d[leftKey]);\n }).attr('width', function (d) {\n return barWidth(d[leftKey]);\n });\n // append right bars\n g.append('rect').attr('class', 'bar-right').attr('width', 0).attr('height', bandScale.bandwidth()).style('fill', config.barFillRight).transition().ease(d3.easeSinOut).duration(1500).attr('width', function (d) {\n return barWidth(d[rightKey]);\n });\n // create a pivot line (around zero)\n svg.append('line').attr('x1', w / 2).attr('y1', 0).attr('x2', w / 2).attr('y2', h).style('stroke', config.lineStroke).style('stroke-width', config.lineStrokeWidth);\n // append a label\n g.append('text').attr('x', 5).attr('y', 15).text(function (d) {\n return d.label;\n });\n // render an axis for each side\n // can use the bar scale for the right\n var xAxisRight = d3.axisBottom().scale(barWidth).ticks(4);\n // need the left to be reversed\n var xAxisLeft = d3.axisBottom().scale(leftAxisScale).ticks(4);\n axisRight.call(xAxisRight);\n axisLeft.call(xAxisLeft);\n // hide the right zero (so no overlap)\n svg.select('.axis-right g.tick').style('display', 'none');\n}\n\n// run the chart\nsplitBar('body', _sampleData2.default);//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9zY3JpcHQuanM/OWE5NSJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgc2FtcGxlRGF0YSBmcm9tICcuL3NhbXBsZURhdGEnXG5jb25zdCBkMyA9IHdpbmRvdy5kM1xuXG5mdW5jdGlvbiBzcGxpdEJhciAoYmluZCwgZGF0YSwgY29uZmlnKSB7XG4gIGNvbmZpZyA9IHtcbiAgICBtYXJnaW46IHt0b3A6IDgwLCByaWdodDogMTAsIGJvdHRvbTogMTAsIGxlZnQ6IDEwfSxcbiAgICB3aWR0aDogOTYwLFxuICAgIC8vIGhlaWdodDogNDAwLCAvLyBjYWxjIGhlaWdodCB2aWEgZGF0dW1zIHNvIHdlIGRvbid0IGdldCB1Z2x5IHNpemUgcmVjdHNcbiAgICBiYXJGaWxsUmlnaHQ6ICcjZTZjNzAwJywgLy8gY29sb3VyIG9mIHJpZ2h0IGJhcnNcbiAgICBiYXJGaWxsTGVmdDogJyNmNDU4NDQnLCAvLyBjb2xvdXIgb2YgbGVmdCsgYmFyc1xuICAgIGxpbmVTdHJva2U6ICcjMzUzNDJmJyxcbiAgICBsaW5lU3Ryb2tlV2lkdGg6IDQsXG4gICAgLy8gYmUgYWJsZSB0byBjaGFuZ2UgdGhlIGtleXMgaW4gZGF0YVxuICAgIGxlZnRLZXk6ICdsZWZ0JyxcbiAgICByaWdodEtleTogJ3JpZ2h0JyxcbiAgICBsYWJlbEtleTogJ2xhYmVsJyxcbiAgICAuLi5jb25maWdcbiAgfVxuICBjb25zdCB7bWFyZ2luLCB3aWR0aCwgbGVmdEtleSwgcmlnaHRLZXl9ID0gY29uZmlnXG4gIGNvbnN0IGhlaWdodCA9IChkYXRhLmxlbmd0aCAqIDMwKSArIG1hcmdpbi50b3AgKyBtYXJnaW4uYm90dG9tXG4gIGNvbnN0IHcgPSB3aWR0aCAtIG1hcmdpbi5sZWZ0IC0gbWFyZ2luLnJpZ2h0XG4gIGNvbnN0IGggPSBoZWlnaHQgLSBtYXJnaW4udG9wIC0gbWFyZ2luLmJvdHRvbVxuXG4gIC8vIGNhbGMgbWF4IHZhbHVlIGZvciB3aG9sZSBkYXRhIHNldFxuICBjb25zdCBtYXhWYWx1ZXMgPSBbXG4gICAgZDMubWF4KGRhdGEsIGQgPT4gZFtsZWZ0S2V5XSksXG4gICAgZDMubWF4KGRhdGEsIGQgPT4gZFtyaWdodEtleV0pXG4gIF1cbiAgLy8gY29uc3QgbWluVmFsdWVzID0gW1xuICAvLyAgIGQzLm1pbihkYXRhLCBkID0+IGRbbGVmdEtleV0pLFxuICAvLyAgIGQzLm1pbihkYXRhLCBkID0+IGRbcmlnaHRLZXldKVxuICAvLyBdXG5cbiAgLy8gYWNjZXNzIGxhYmVsc1xuICBjb25zdCBsYWJlbHMgPSBkYXRhLm1hcChkID0+IGRbY29uZmlnLmxhYmVsS2V5XSlcblxuICAvLyBzZXQgdXAgc2NhbGVzXG4gIGNvbnN0IGJhbmRTY2FsZSA9IGQzLnNjYWxlQmFuZCgpXG4gICAgLmRvbWFpbihsYWJlbHMpXG4gICAgLnJhbmdlKFsxMCwgaF0pXG4gICAgLnBhZGRpbmdJbm5lcigwLjIpXG4gIC8vIHNjYWxlIGZvciBiYXJzIGFuZCByaWdodCBheGlzXG4gIGNvbnN0IGJhcldpZHRoID0gZDMuc2NhbGVMaW5lYXIoKVxuICAgIC5kb21haW4oWzAsIGQzLm1heChtYXhWYWx1ZXMpXSlcbiAgICAucmFuZ2UoWzAsIHcgLyAyXSlcbiAgLy8gc2NhbGUgZm9yIGxlZnQgYXhpc1xuICBjb25zdCBsZWZ0QXhpc1NjYWxlID0gZDMuc2NhbGVMaW5lYXIoKVxuICAgIC5kb21haW4oWzAsIGQzLm1heChtYXhWYWx1ZXMpXSlcbiAgICAucmFuZ2UoW3cgLyAyLCAwXSlcblxuICBjb25zdCBzdmcgPSBkMy5zZWxlY3QoYmluZCkuYXBwZW5kKCdzdmcnKVxuICAgIC5hdHRyKCd3aWR0aCcsIHdpZHRoKVxuICAgIC5hdHRyKCdoZWlnaHQnLCBoZWlnaHQpXG4gICAgLmFwcGVuZCgnZycpXG4gICAgLmF0dHIoJ3RyYW5zZm9ybScsIGQgPT4gYHRyYW5zbGF0ZSgke21hcmdpbi5sZWZ0fSwgJHttYXJnaW4udG9wfSlgKVxuXG4gIGNvbnN0IGF4aXNSaWdodCA9IHN2Zy5hcHBlbmQoJ2cnKVxuICAgIC5hdHRyKCdjbGFzcycsICdheGlzLXJpZ2h0JylcbiAgICAuYXR0cigndHJhbnNmb3JtJywgZCA9PiBgdHJhbnNsYXRlKCR7dyAvIDJ9LCAkey1tYXJnaW4udG9wIC8gMn0pYClcbiAgY29uc3QgYXhpc0xlZnQgPSBzdmcuYXBwZW5kKCdnJylcbiAgICAuYXR0cignY2xhc3MnLCAnYXhpcy1sZWZ0JylcbiAgICAuYXR0cigndHJhbnNmb3JtJywgZCA9PiBgdHJhbnNsYXRlKCR7MH0sICR7LW1hcmdpbi50b3AgLyAyfSlgKVxuXG4gIGNvbnN0IGcgPSBzdmcuc2VsZWN0QWxsKCcuYmFyJylcbiAgICAuZGF0YShkYXRhKVxuICAgIC5lbnRlcigpLmFwcGVuZCgnZycpXG4gICAgLmF0dHIoJ2NsYXNzJywgJ2JhcicpXG4gICAgLmF0dHIoJ3RyYW5zZm9ybScsIGQgPT4gYHRyYW5zbGF0ZSgke3cgLyAyfSwgJHtiYW5kU2NhbGUoZFtjb25maWcubGFiZWxLZXldKX0pYClcbiAgLy8gYXBwZW5kIGxlZnQgYmFyc1xuICBnLmFwcGVuZCgncmVjdCcpXG4gICAgLmF0dHIoJ2NsYXNzJywgJ2Jhci1sZWZ0JylcbiAgICAuYXR0cigneCcsIDBcbiAgICAgIClcbiAgICAuYXR0cignd2lkdGgnLCAwKVxuICAgIC5hdHRyKCdoZWlnaHQnLCBiYW5kU2NhbGUuYmFuZHdpZHRoKCkpXG4gICAgLnN0eWxlKCdmaWxsJywgY29uZmlnLmJhckZpbGxMZWZ0KVxuICAgIC50cmFuc2l0aW9uKClcbiAgICAuZWFzZShkMy5lYXNlU2luT3V0KVxuICAgIC5kZWxheSgxMDAwKVxuICAgIC5kdXJhdGlvbigxMDAwKVxuICAgIC5hdHRyKCd4JywgZCA9PiAtKGJhcldpZHRoKGRbbGVmdEtleV0pKSlcbiAgICAuYXR0cignd2lkdGgnLCBkID0+IGJhcldpZHRoKGRbbGVmdEtleV0pKVxuICAvLyBhcHBlbmQgcmlnaHQgYmFyc1xuICBnLmFwcGVuZCgncmVjdCcpXG4gICAgLmF0dHIoJ2NsYXNzJywgJ2Jhci1yaWdodCcpXG4gICAgLmF0dHIoJ3dpZHRoJywgMClcbiAgICAuYXR0cignaGVpZ2h0JywgYmFuZFNjYWxlLmJhbmR3aWR0aCgpKVxuICAgIC5zdHlsZSgnZmlsbCcsIGNvbmZpZy5iYXJGaWxsUmlnaHQpXG4gICAgLnRyYW5zaXRpb24oKVxuICAgIC5lYXNlKGQzLmVhc2VTaW5PdXQpXG4gICAgLmR1cmF0aW9uKDE1MDApXG4gICAgLmF0dHIoJ3dpZHRoJywgZCA9PiBiYXJXaWR0aChkW3JpZ2h0S2V5XSkpXG4gIC8vIGNyZWF0ZSBhIHBpdm90IGxpbmUgKGFyb3VuZCB6ZXJvKVxuICBzdmcuYXBwZW5kKCdsaW5lJylcbiAgICAuYXR0cigneDEnLCB3IC8gMilcbiAgICAuYXR0cigneTEnLCAwKVxuICAgIC5hdHRyKCd4MicsIHcgLyAyKVxuICAgIC5hdHRyKCd5MicsIGgpXG4gICAgLnN0eWxlKCdzdHJva2UnLCBjb25maWcubGluZVN0cm9rZSlcbiAgICAuc3R5bGUoJ3N0cm9rZS13aWR0aCcsIGNvbmZpZy5saW5lU3Ryb2tlV2lkdGgpXG4gIC8vIGFwcGVuZCBhIGxhYmVsXG4gIGcuYXBwZW5kKCd0ZXh0JylcbiAgICAuYXR0cigneCcsIDUpXG4gICAgLmF0dHIoJ3knLCAxNSlcbiAgICAudGV4dChkID0+IGQubGFiZWwpXG4gIC8vIHJlbmRlciBhbiBheGlzIGZvciBlYWNoIHNpZGVcbiAgLy8gY2FuIHVzZSB0aGUgYmFyIHNjYWxlIGZvciB0aGUgcmlnaHRcbiAgY29uc3QgeEF4aXNSaWdodCA9IGQzLmF4aXNCb3R0b20oKS5zY2FsZShiYXJXaWR0aCkudGlja3MoNClcbiAgLy8gbmVlZCB0aGUgbGVmdCB0byBiZSByZXZlcnNlZFxuICBjb25zdCB4QXhpc0xlZnQgPSBkMy5heGlzQm90dG9tKCkuc2NhbGUobGVmdEF4aXNTY2FsZSkudGlja3MoNClcbiAgYXhpc1JpZ2h0LmNhbGwoeEF4aXNSaWdodClcbiAgYXhpc0xlZnQuY2FsbCh4QXhpc0xlZnQpXG4gIC8vIGhpZGUgdGhlIHJpZ2h0IHplcm8gKHNvIG5vIG92ZXJsYXApXG4gIHN2Zy5zZWxlY3QoJy5heGlzLXJpZ2h0IGcudGljaycpLnN0eWxlKCdkaXNwbGF5JywgJ25vbmUnKVxufVxuXG4vLyBydW4gdGhlIGNoYXJ0XG5zcGxpdEJhcignYm9keScsIHNhbXBsZURhdGEpXG5cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gc2NyaXB0LmpzIl0sIm1hcHBpbmdzIjoiOzs7O0FBQUE7QUFDQTs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFYQTtBQURBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQWVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFDQTtBQUFBO0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBSUE7QUFDQTtBQUdBO0FBQ0E7QUFDQTtBQUdBO0FBSUE7QUFBQTtBQUNBO0FBQ0E7QUFFQTtBQUFBO0FBQ0E7QUFFQTtBQUFBO0FBQ0E7QUFDQTtBQUlBO0FBQUE7QUFDQTtBQUNBO0FBV0E7QUFBQTtBQUNBO0FBQUE7QUFDQTtBQUNBO0FBUUE7QUFBQTtBQUNBO0FBQ0E7QUFPQTtBQUNBO0FBR0E7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///1\n")}]);
<!DOCTYPE html>
<title>blockup</title>
<link href='dist.css' rel='stylesheet' />
<link href="https://fonts.googleapis.com/css?family=Space+Mono" rel="stylesheet">
<body>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='dist.js'></script>
</body>
{
"standard": {
"globals": [
"d3"
]
}
}
export default [
{label: 'set-1', right: rInt(100, 300), left: rInt(20, 100)},
{label: 'set-2', right: rInt(100, 250), left: rInt(20, 80)},
{label: 'set-3', right: rInt(30, 150), left: rInt(5, 30)},
{label: 'set-4', right: rInt(2, 100), left: rInt(0, 50)},
{label: 'set-5', right: rInt(2, 50), left: rInt(0, 20)}
]
function rInt (min, max) {
return Math.floor(d3.randomUniform(min, max)())
}
import sampleData from './sampleData'
const d3 = window.d3
function splitBar (bind, data, config) {
config = {
margin: {top: 80, right: 10, bottom: 10, left: 10},
width: 960,
// height: 400, // calc height via datums so we don't get ugly size rects
barFillRight: '#e6c700', // colour of right bars
barFillLeft: '#f45844', // colour of left+ bars
lineStroke: '#35342f',
lineStrokeWidth: 4,
// be able to change the keys in data
leftKey: 'left',
rightKey: 'right',
labelKey: 'label',
...config
}
const {margin, width, leftKey, rightKey} = config
const height = (data.length * 30) + margin.top + margin.bottom
const w = width - margin.left - margin.right
const h = height - margin.top - margin.bottom
// calc max value for whole data set
const maxValues = [
d3.max(data, d => d[leftKey]),
d3.max(data, d => d[rightKey])
]
// const minValues = [
// d3.min(data, d => d[leftKey]),
// d3.min(data, d => d[rightKey])
// ]
// access labels
const labels = data.map(d => d[config.labelKey])
// set up scales
const bandScale = d3.scaleBand()
.domain(labels)
.range([10, h])
.paddingInner(0.2)
// scale for bars and right axis
const barWidth = d3.scaleLinear()
.domain([0, d3.max(maxValues)])
.range([0, w / 2])
// scale for left axis
const leftAxisScale = d3.scaleLinear()
.domain([0, d3.max(maxValues)])
.range([w / 2, 0])
const svg = d3.select(bind).append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', d => `translate(${margin.left}, ${margin.top})`)
const axisRight = svg.append('g')
.attr('class', 'axis-right')
.attr('transform', d => `translate(${w / 2}, ${-margin.top / 2})`)
const axisLeft = svg.append('g')
.attr('class', 'axis-left')
.attr('transform', d => `translate(${0}, ${-margin.top / 2})`)
const g = svg.selectAll('.bar')
.data(data)
.enter().append('g')
.attr('class', 'bar')
.attr('transform', d => `translate(${w / 2}, ${bandScale(d[config.labelKey])})`)
// append left bars
g.append('rect')
.attr('class', 'bar-left')
.attr('x', 0
)
.attr('width', 0)
.attr('height', bandScale.bandwidth())
.style('fill', config.barFillLeft)
.transition()
.ease(d3.easeSinOut)
.delay(1000)
.duration(1000)
.attr('x', d => -(barWidth(d[leftKey])))
.attr('width', d => barWidth(d[leftKey]))
// append right bars
g.append('rect')
.attr('class', 'bar-right')
.attr('width', 0)
.attr('height', bandScale.bandwidth())
.style('fill', config.barFillRight)
.transition()
.ease(d3.easeSinOut)
.duration(1500)
.attr('width', d => barWidth(d[rightKey]))
// create a pivot line (around zero)
svg.append('line')
.attr('x1', w / 2)
.attr('y1', 0)
.attr('x2', w / 2)
.attr('y2', h)
.style('stroke', config.lineStroke)
.style('stroke-width', config.lineStrokeWidth)
// append a label
g.append('text')
.attr('x', 5)
.attr('y', 15)
.text(d => d.label)
// render an axis for each side
// can use the bar scale for the right
const xAxisRight = d3.axisBottom().scale(barWidth).ticks(4)
// need the left to be reversed
const xAxisLeft = d3.axisBottom().scale(leftAxisScale).ticks(4)
axisRight.call(xAxisRight)
axisLeft.call(xAxisLeft)
// hide the right zero (so no overlap)
svg.select('.axis-right g.tick').style('display', 'none')
}
// run the chart
splitBar('body', sampleData)
*
box-sizing border-box
body
font-family: 'Space Mono', monospace
font-size: 11px
text
font-family: 'Space Mono', monospace
fill: #35342f
.axis-left path, .axis-left line
stroke: #f45844
.axis-left text
fill: #f45844
.axis-right path, .axis-right line
stroke: #e6c700
.axis-right text
fill: #e6c700
font-family: Consolas, monaco, monospace
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment