Basic responsive bar chart following pattern described by Russell Goldenberg on The Pudding live coding stream.
Code for bar chart partly based on Hannah Recht's example.
forked from officeofjane's block: Basic responsive bar chart
| license: mit |
Basic responsive bar chart following pattern described by Russell Goldenberg on The Pudding live coding stream.
Code for bar chart partly based on Hannah Recht's example.
forked from officeofjane's block: Basic responsive bar chart
| <!DOCTYPE html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <script src='https://d3js.org/d3.v5.min.js'></script> | |
| <style> | |
| body { | |
| font-family: sans-serif; | |
| font-size: 12px; | |
| margin: 0; | |
| } | |
| .chart { | |
| max-width: 960px; | |
| margin: 0 auto; | |
| padding:15px; | |
| } | |
| .chart__title{ | |
| /* background: #ffdc32; */ | |
| } | |
| .chart__svg { | |
| /* background:#dcdcdc; */ | |
| } | |
| rect.bar { | |
| fill:#2e8b57; | |
| } | |
| text { | |
| font-size:12px; | |
| } | |
| text.label { | |
| text-anchor:end; | |
| fill:#fff; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class='chart'> | |
| <div class='chart__inner'> | |
| <h1 class='chart__title'>Chart title</h1> | |
| </div> | |
| </div> | |
| <script> | |
| const margin = { top: 20, right: 30, bottom: 25, left: 65 }; | |
| // $ to indicate a DOM element | |
| const $chart = d3.select('.chart'); | |
| const $chartInner = d3.select('.chart__inner'); | |
| const $svg = $chart.append('svg') | |
| .attr('class', 'chart__svg'); | |
| const $plot = $svg.append('g') | |
| .attr('class', 'plot') | |
| .attr('transform', `translate(${margin.left}, ${margin.top})`) | |
| const x = d3.scaleBand(); | |
| const y = d3.scaleLinear(); | |
| // decouple data joining and data rendering into separate functions | |
| // width and height are dimensions of the plot excluding margins and axes | |
| function resize() { | |
| const w = $chartInner.node().offsetWidth - margin.left - margin.right; | |
| render(w); | |
| } | |
| function render(width) { | |
| const $bars = $plot.selectAll('.bar'); | |
| const $labels = $plot.selectAll('.label'); | |
| const height = width * 0.48 - margin.top - margin.bottom; | |
| x.range([0, width]).padding(0.15); | |
| y.range([0, height]); | |
| const xAxis = d3.axisBottom() | |
| .scale(x) | |
| .tickSize(0) | |
| .tickPadding(8); | |
| const yAxis = d3.axisLeft() | |
| .scale(y) | |
| .ticks(5); | |
| $svg | |
| .attr('width', width + margin.left + margin.right) | |
| .attr('height', height + margin.top + margin.bottom) | |
| //draw bars | |
| $bars | |
| .attr('x', d => x(d.name)) | |
| .attr('y', d=> y(d.value)) | |
| .attr('width', d => x.bandwidth()) | |
| // .attr('height', 12) | |
| .attr('height', d=> y(0) - y(d.value)); | |
| // draw axes | |
| $plot.select('.axis.x') | |
| .attr('transform', `translate(0, ${height})`) | |
| .call(xAxis) | |
| .select('.domain').remove(); | |
| $plot.select('.axis.y') | |
| .call(yAxis) | |
| .select('.domain').remove(); | |
| // draw value text labels | |
| // $labels | |
| // .attr('x', d => x(d.value) - 5) | |
| // .attr('y', d => y(d.name) + y.bandwidth() / 2) | |
| // .attr('dy', '0.35em') | |
| // .text(d => d.value); | |
| } | |
| // init only handles data and binding data to DOM elements | |
| function init() { | |
| const data = [ | |
| { "name": "Apples", "value": 20 }, | |
| { "name": "Bananas", "value": 12 }, | |
| { "name": "Grapes", "value": 19 }, | |
| { "name": "Lemons", "value": 5 }, | |
| { "name": "Limes", "value": 16 }, | |
| { "name": "Oranges", "value": 26 }, | |
| { "name": "Pears", "value": 30 } | |
| ]; | |
| // data.sort((a, b) => d3.descending(a.value, b.value)); | |
| x.domain(data.map(d => d.name)); | |
| y.domain([d3.max(data, d => d.value),0]); | |
| $plot | |
| .append('g') | |
| .attr('class', 'bars') | |
| .selectAll('.bar') | |
| .data(data) | |
| .enter() | |
| .append('rect') | |
| .attr('class', 'bar'); | |
| $plot | |
| .append('g') | |
| .attr('class', 'labels') | |
| .selectAll('.label') | |
| .data(data) | |
| .enter() | |
| .append('text') | |
| .attr('class', 'label'); | |
| $plot | |
| .append('g') | |
| .attr('class', 'axis x') | |
| $plot | |
| .append('g') | |
| .attr('class', 'axis y') | |
| window.addEventListener('resize', debounce(resize, 200)); | |
| resize(); | |
| } | |
| init(); | |
| // https://github.com/component/debounce | |
| function debounce(func, wait, immediate){ | |
| var timeout, args, context, timestamp, result; | |
| if (null == wait) wait = 100; | |
| function later() { | |
| var last = Date.now() - timestamp; | |
| if (last < wait && last >= 0) { | |
| timeout = setTimeout(later, wait - last); | |
| } else { | |
| timeout = null; | |
| if (!immediate) { | |
| result = func.apply(context, args); | |
| context = args = null; | |
| } | |
| } | |
| }; | |
| var debounced = function(){ | |
| context = this; | |
| args = arguments; | |
| timestamp = Date.now(); | |
| var callNow = immediate && !timeout; | |
| if (!timeout) timeout = setTimeout(later, wait); | |
| if (callNow) { | |
| result = func.apply(context, args); | |
| context = args = null; | |
| } | |
| return result; | |
| }; | |
| debounced.clear = function() { | |
| if (timeout) { | |
| clearTimeout(timeout); | |
| timeout = null; | |
| } | |
| }; | |
| debounced.flush = function() { | |
| if (timeout) { | |
| result = func.apply(context, args); | |
| context = args = null; | |
| clearTimeout(timeout); | |
| timeout = null; | |
| } | |
| }; | |
| return debounced; | |
| }; | |
| </script> | |
| </body> |