Skip to content

Instantly share code, notes, and snippets.

@officeofjane
Last active December 19, 2018 07:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save officeofjane/930211cddd43b980ab981f6777d0c3a2 to your computer and use it in GitHub Desktop.
Save officeofjane/930211cddd43b980ab981f6777d0c3a2 to your computer and use it in GitHub Desktop.
Basic responsive bar chart
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src='https://d3js.org/d3.v5.min.js'></script>
<style>
body {
font-family: "avenir next", Arial, sans-serif;
font-size: 12px;
margin: 0;
}
.chart {
max-width: 960px;
margin: 0 auto;
background:#ffffe0;
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.scaleLinear();
const y = d3.scaleBand();
// 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]);
y.range([0, height]).padding(0.15);
const xAxis = d3.axisBottom()
.scale(x)
.ticks(5);
const yAxis = d3.axisLeft()
.scale(y)
.tickSize(0)
.tickPadding(8);
$svg
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
// draw bars
$bars
.attr('x', x(0))
.attr('y', d => y(d.name))
.attr('width', d => x(d.value) - x(0))
.attr('height', y.bandwidth());
// 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([0, d3.max(data, d => d.value)]);
y.domain(data.map(d => d.name));
$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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment