Skip to content

Instantly share code, notes, and snippets.

@micahstubbs
Last active August 22, 2016 22:30
Show Gist options
  • Save micahstubbs/087201246588e39adb305b594ba73b89 to your computer and use it in GitHub Desktop.
Save micahstubbs/087201246588e39adb305b594ba73b89 to your computer and use it in GitHub Desktop.
responsive bar chart
border: no
height: 333
license: CC0-1.0

a responsive bar chart. open in a new window ↗️
to enjoy the buttery responsiveness

built with window resize events from W

inspired by a conversation with @robcrock

an iteration on the bl.ock responsive d3 from benheubl

if you can figure out how to fix the selections and get v4.html to render the bars, that would be awesome. here are the most relevant d3 v4 docs I could find. when you get it, do shout at me on twitter @micahstubbs

letter frequency
A 0.08167
B 0.01492
C 0.02782
D 0.04253
E 0.12702
F 0.02288
G 0.02015
H 0.06094
I 0.06966
J 0.00153
K 0.00772
L 0.04025
M 0.02406
N 0.06749
O 0.07507
P 0.01929
Q 0.00095
R 0.05987
S 0.06327
T 0.09056
U 0.02758
V 0.00978
W 0.02360
X 0.00150
Y 0.01974
Z 0.00074
<!DOCTYPE html>
<head>
<meta charset='utf-8'>
<script src='w.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.10.3/babel.min.js'></script>
</head>
<body>
<script lang='babel' type='text/babel'>
const maxWidth = 5120;
const maxHeight = 2880;
const margin = {
top: 10,
right: 10,
bottom: 25,
left: 35
};
const xVariable = 'letter';
const yVariable = 'frequency';
const faintGray = '#ededed';
let xScale;
let yScale;
let xAxis;
let yAxis;
d3.select('body')
.style({
margin: 0,
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0
})
const svg = d3.select('body').append('svg')
.attr('width', '100%')
.attr('height', '100%');
const chartArea = svg.append('g')
.classed('chartArea', true)
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const barGroup = chartArea.append('g')
.classed('bars', true);
const xAxisG = chartArea.append('g')
.classed('axis', true)
.classed('x', true);
const yAxisG = chartArea.append('g')
.classed('axis', true)
.classed('y', true);
function type(d) {
// coerce to a Number from a String (or anything)
d[yVariable] = Number(d[yVariable]);
return d;
}
d3.csv('data.csv', type, (error, data) => {
console.log('data', data);
function initChart() {
const width = 100;
const height = 50;
// Initialise scales
xScale = d3.scale.ordinal()
.domain(data.map(d => d[xVariable]));
yScale = d3.scale.linear()
.domain([0, d3.max(data.map(d => d[yVariable]))]);
// Build the x-axis
xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
// Build the y-axis
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
}
function updateScales() {
const detectedWidth = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
const detectedHeight = window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;
const newWidth = d3.min([detectedWidth, maxWidth]) - margin.left - margin.right;
const newHeight = d3.min([detectedHeight, maxHeight]) - margin.top - margin.bottom;
xScale.rangeRoundBands([0, newWidth], 0.1);
yScale.range([newHeight, 0]);
}
function updateAxes(firstCall) {
const detectedHeight = window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;
const newHeight = d3.min([detectedHeight, maxHeight]);
// position the xAxisG before the transition the first time
if (typeof firstCall !== 'undefined') {
xAxisG
.attr('transform', `translate(0, ${newHeight - margin.top - margin.bottom})`);
}
xAxisG
.transition()
.duration(0)
.attr('transform', `translate(0, ${newHeight - margin.top - margin.bottom})`)
.call(xAxis);
yAxisG
.transition()
.duration(0)
.call(yAxis);
// style the axes
d3.selectAll('.axis text')
.style({
'font-family': 'sans-serif',
'font-size': '10px'
})
d3.selectAll('.axis path')
.style({
fill: 'none',
stroke: '#161616'
})
d3.selectAll('.axis line')
.style('stroke', 'black');
}
function updateBars() {
const updateSelection = barGroup.selectAll('rect')
.data(data);
updateSelection.enter()
.append('rect')
.classed('rect', true)
.style('fill', faintGray);
updateSelection.exit()
.remove();
updateSelection
.transition()
.duration(0)
.attr('x', function(d) {return xScale(d[xVariable]);})
.attr('width', xScale.rangeBand)
.attr('y', d => yScale(d[yVariable]))
.attr('height', d => yScale(0) - yScale(d[yVariable]));
updateSelection
.on('mouseover', function () {
d3.select(this)
.style({
'fill': 'steelblue',
'fill-opacity': 0.6
});
})
.on('mouseout', function () {
d3.select(this)
.style({
'fill': faintGray,
'fill-opacity': 1
});
});
}
function update(firstCall) {
updateScales();
updateAxes(firstCall);
updateBars();
}
function initEvents() {
// Set up event handler for resizes
W.addListener(update);
}
initChart();
update(true); // set parameter `firstCall` to true this once
initEvents();
});
</script>
</body>
<!DOCTYPE html>
<head>
<meta charset='utf-8'>
<script src='w.js'></script>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://d3js.org/d3-selection-multi.v0.4.min.js'></script>
<script src='https://npmcdn.com/babel-core@5.8.34/browser.min.js'></script>
</head>
<body>
<script>
const maxWidth = 5120;
const maxHeight = 2880;
const margin = {
top: 10,
right: 10,
bottom: 25,
left: 35
};
const xVariable = 'letter';
const yVariable = 'frequency';
const faintGray = '#ededed';
let xScale;
let yScale;
let xAxis;
let yAxis;
d3.select('body')
.styles({
margin: 0,
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0
})
const svg = d3.select('body').append('svg')
.attr('width', '100%')
.attr('height', '100%');
const chartArea = svg.append('g')
.classed('chartArea', true)
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const barGroup = chartArea.append('g')
.classed('bars', true);
const xAxisG = chartArea.append('g')
.classed('axis', true)
.classed('x', true);
const yAxisG = chartArea.append('g')
.classed('axis', true)
.classed('y', true);
function type(d) {
// coerce to a Number from a String (or anything)
d[yVariable] = Number(d[yVariable]);
return d;
}
d3.csv('data.csv', type, (error, data) => {
console.log('data', data);
function initChart() {
const width = 100;
const height = 50;
// Initialise scales
xScale = d3.scaleBand()
.domain(data.map(d => d[xVariable]));
yScale = d3.scaleLinear()
.domain([0, d3.max(data.map(d => d[yVariable]))]);
// Build the x-axis
xAxis = d3.axisBottom()
.scale(xScale);
// Build the y-axis
yAxis = d3.axisLeft()
.scale(yScale);
}
function updateScales() {
const newWidth = d3.min([W.getViewportWidth(), maxWidth]) - margin.left - margin.right;
const newHeight = d3.min([W.getViewportHeight(), maxHeight]) - margin.top - margin.bottom;
xScale
.range([0, newWidth])
.bandwidth(10);
yScale.range([newHeight, 0]);
}
function updateAxes(firstCall) {
const newHeight = d3.min([W.getViewportHeight(), maxHeight]);
// position the xAxisG before the transition the first time
if (typeof firstCall !== 'undefined') {
xAxisG
.attr('transform', `translate(0, ${newHeight - margin.top - margin.bottom})`);
}
xAxisG
.transition()
.duration(150)
.attr('transform', `translate(0, ${newHeight - margin.top - margin.bottom})`)
.call(xAxis);
yAxisG
.transition()
.duration(150)
.call(yAxis);
// style the axes
d3.selectAll('.axis text')
.styles({
'font-family': 'sans-serif',
'font-size': '10px'
})
d3.selectAll('.axis path')
.styles({
fill: 'none',
stroke: '#161616'
})
d3.selectAll('.axis line')
.style('stroke', 'black');
}
function updateBars() {
const updateSelection = barGroup.selectAll('rect')
.data(data);
updateSelection.enter()
.append('rect')
.classed('rect', true)
.style('fill', faintGray);
updateSelection.exit()
.remove();
updateSelection
.transition()
.duration(150)
.attr('x', function(d) {return xScale(d[xVariable]);})
.attr('width', xScale.rangeBand)
.attr('y', d => yScale(d[yVariable]))
.attr('height', d => yScale(0) - yScale(d[yVariable]));
updateSelection
.on('mouseover', function () {
d3.select(this)
.styles({
'fill': 'steelblue',
'fill-opacity': 0.6
});
})
.on('mouseout', function () {
d3.select(this)
.styles({
'fill': faintGray,
'fill-opacity': 1
});
});
}
function update(firstCall) {
updateScales();
updateAxes(firstCall);
updateBars();
}
function initEvents() {
// Set up event handler for resizes
W.addListener(update);
}
initChart();
update(true); // set parameter `firstCall` to true this once
initEvents();
});
</script>
</body>
;(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.W = factory();
}
}(this, function() {
/*! W 1.6.3 (https://github.com/pyrsmk/W) */
// Prepare
var listeners = [],
resize_trigger = false,
orientationchange = false,
orientationchange_trigger = false;
// Catch window resize event
if(window.addEventListener) {
if('onorientationchange' in window) {
orientationchange = true;
window.addEventListener('orientationchange', function() {
orientationchange_trigger = true;
}, false);
}
window.addEventListener('resize', function() {
resize_trigger = true;
}, false);
}
else{
window.attachEvent('onresize', function() {
resize_trigger = true;
});
}
// Verify resizes every 10ms
setInterval(function() {
var trigger = false;
if(orientationchange) {
if(orientationchange_trigger && resize_trigger) {
trigger = true;
}
}
else if(resize_trigger) {
trigger = true;
}
if(trigger && document.documentElement.clientWidth) {
orientationchange_trigger = false;
resize_trigger = false;
for(var i=0, j=listeners.length; i<j; ++i) {
listeners[i].func();
}
}
}, 10);
// Get screen orientation
function getOrientation() {
var landscape;
if('orientation' in window) {
// Mobiles
var orientation = window.orientation;
landscape = (orientation == 90 || orientation == -90);
}
else {
// Desktop browsers
landscape = window.innerWidth > window.innerHeight;
}
return landscape ? 'landscape' : 'portrait';
}
// Viewport resolution detection
function detectViewport(absolute) {
// Detect screen size
var screen_width = screen.width,
screen_height = screen.height;
if(getOrientation() == 'landscape' && screen_width < screen_height) {
screen_width = screen.height;
screen_height = screen.width;
}
// Absolute mode
if(absolute) {
return {
width: screen_width,
height: screen_height
};
}
// Relative mode
else {
var w = window.innerWidth,
h = window.innerHeight;
if(!w || !h || w > screen_width || h > screen_height || w == 980) {
w = window.outerWidth;
h = window.outerHeight;
}
if(!w || !h || w > screen_width || h > screen_height) {
w = screen.availWidth;
h = screen.availHeight;
}
return {width: w, height: h};
}
}
// Define W object
var W = {
getViewportDimensions: function(absolute) {
return detectViewport(absolute);
},
getViewportWidth: function(absolute) {
return detectViewport(absolute).width;
},
getViewportHeight: function(absolute) {
return detectViewport(absolute).height;
},
getOrientation: function() {
return getOrientation();
},
addListener: function(func, key) {
listeners.push({
func: func,
key: key
});
return func;
},
removeListener: function(key) {
for(var i=0, j=listeners.length; i<j; ++i) {
if(listeners[i].key == key) {
listeners.splice(i, 1);
break;
}
}
},
clearListeners: function() {
listeners = [];
},
trigger: function(key) {
for(var i=0, j=listeners.length; i<j; ++i) {
if(typeof key == 'undefined' || listeners[i].key == key) {
listeners[i].func();
}
}
}
};
return W;
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment