A basic column chart, along with automatic data updates.
Credit owed to Mike Bostock's guides:
height: 450 | |
A basic column chart, along with automatic data updates.
Credit owed to Mike Bostock's guides:
letter | frequency | |
---|---|---|
A | .12102 | |
B | .01492 | |
C | .02782 | |
D | .04253 | |
E | .12702 | |
F | .02288 | |
G | .02015 | |
H | .06094 | |
I | .06966 | |
J | .00153 | |
K | .00772 | |
L | .04025 | |
M | .02406 | |
N | .06749 | |
O | .07507 | |
P | .01929 | |
Q | .00095 | |
R | .05987 | |
S | .06327 | |
T | .09056 | |
U | .02758 | |
V | .00978 | |
W | .02360 | |
X | .00150 | |
Y | .01974 | |
Z | .00074 |
<!DOCTYPE html> | |
<head> | |
<title>Updating column chart</title> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
padding: 0; | |
margin: 0; | |
font-family: helvetica, arial, sans-serif; | |
background-color: white; | |
} | |
.bar { fill: cornflowerblue; } | |
.exit { fill: indianred; } | |
.enter { fill: seagreen; } | |
.axis { font: 10px sans-serif; } | |
.axis path, | |
.axis line { | |
fill: none; | |
shape-rendering: crispEdges; | |
} | |
.axis path {stroke: black;} | |
.axis line { | |
stroke: gray; | |
stroke-opacity: 0.2; | |
stroke-width: 1px; | |
} | |
.x.axis path, | |
.y.axis path { | |
display: none; | |
} | |
.tick text { | |
font-size: 12px; | |
fill: black; | |
text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.3), | |
1px -1px 0 rgba(255, 255, 255, 0.3), | |
-1px 1px 0 rgba(255, 255, 255, 0.3), | |
-1px -1px 0 rgba(255, 255, 255, 0.3); | |
} | |
.attribution { | |
background-color: lightgray; | |
font-size: 12px; | |
color: #333; | |
padding: 5px 10px; | |
} | |
.source { | |
float: right; | |
} | |
</style> | |
</head> | |
<body> | |
<svg class="chart"></svg> | |
<div class="attribution"><span>The Lens/Thomas Thoren</span><span class="source">Source: Source</span></div> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script src="//d3js.org/topojson.v1.min.js"></script> | |
<script> | |
var margin = {top: 20, right: 30, bottom: 30, left: 30}, | |
width = 960 - margin.left - margin.right, | |
height = 400 - margin.top - margin.bottom; | |
var formatNumber = d3.format("%"); | |
var chart = d3.select(".chart") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
var attribution = d3.select(".attribution") | |
.style("width", width + margin.left + 'px'); | |
// Used to assign names to column placement values along the x-axis. | |
var x = d3.scale.ordinal() | |
.rangeRoundBands([0, width], 0.1) | |
var y = d3.scale.linear() | |
.range([height, 0]); // Map reverse order to return from top down. Could avoid this and rework how to calculate height and y below. | |
// Define axes | |
var xAxis = d3.svg.axis() | |
.scale(x) // Use the defined x ordinal scale. | |
.orient("bottom"); | |
var yAxis = d3.svg.axis() | |
.scale(y) | |
.orient("left") | |
.tickFormat(function(d) { | |
var s = Math.floor(d * 100); | |
return d === Math.floor(y.domain()[1] * 100) / 100 | |
? s + "% of occurrences" | |
: s; | |
}) | |
.tickSize(-width) | |
.tickPadding(10); | |
var xAxisLabels = chart.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + height + ")"); | |
var yAxisLabels = chart.append("g") | |
.attr("class", "y axis"); | |
d3.csv("data.csv", type, function(error, data) { | |
if (error) throw error; | |
x.domain(data.map(function(d) { return d.letter; })) // data.map creates array with names | |
y.domain([0, d3.max(data, function(d) { return d.frequency; })]) // Accessor function to map object values to an array. | |
// Draw axes | |
xAxisLabels.call(xAxis); | |
yAxisLabels.call(yAxis); | |
yAxisLabels.selectAll('text') | |
.style('text-anchor', 'start'); | |
yAxisLabels.selectAll('line') | |
.attr("x1", function(d, i) { | |
return d === Math.floor(y.domain()[1] * 100) / 100 // If last | |
? 100 | |
: 4; | |
}); | |
// Draw columns | |
chart.append('g').attr('id', 'columns').selectAll('.bar') // Group rect and text together | |
.data(data, function(d) { return d.letter; }) | |
.enter().append('rect') | |
.attr("class", "bar") | |
.attr("x", function(d) { return x(d.letter); }) | |
.attr("y", function(d) { return y(d.frequency); }) | |
.attr("width", x.rangeBand()) | |
.attr("height", function(d) { return height - y(d.frequency) + "px"; }) | |
.style("fill-opacity", 1); | |
// Move y-axis in front of columns so the labels aren't covered. | |
yAxisLabels.each(function() { | |
this.parentNode.appendChild(this); | |
}); | |
}); | |
function type(d) { | |
d.frequency = +d.frequency; // Coerce to number. By default, CSV values are strings. | |
return d; | |
} | |
function update(data) { | |
// Update y scale input domain values. | |
x.domain(data.map(function(d) { return d.letter; })) // data.map creates array with names | |
y.domain([0, d3.max(data, function(d) { return d.frequency; })]) // Accessor function to map object values to an array. | |
xAxis.scale(x); // Use the updated scales | |
yAxis.scale(y); | |
xAxisLabels.transition() // Transition to updated axes | |
.duration(750) | |
.call(xAxis); | |
yAxisLabels.transition() | |
.duration(750) | |
.call(yAxis); | |
yAxisLabels.selectAll('text') | |
.style('text-anchor', 'start'); | |
yAxisLabels.selectAll('line') | |
.attr("x1", function(d, i) { | |
return d === Math.floor(y.domain()[1] * 100) / 100 // If last | |
? 100 | |
: 4; | |
}); | |
// DATA JOIN | |
// Join new data with old elements, if any. | |
var bar = chart.selectAll('rect') // Group rect and text together | |
.data(data, function(d) { return d.letter; }) | |
// UPDATE | |
// Update old elements as needed. | |
bar.attr("class", "bar") | |
.transition() | |
.duration(750) | |
.attr("x", function(d) { return x(d.letter); }) | |
.attr("y", function(d) { return y(d.frequency); }) | |
.attr("width", x.rangeBand()) | |
.attr("height", function(d) { return height - y(d.frequency) + "px"; }); | |
// ENTER | |
// Create new elements as needed. | |
bar.enter().append("rect") | |
.attr("class", "enter") | |
.attr("x", function(d) { return x(d.letter); }) | |
.attr("y", "0") | |
.attr("width", x.rangeBand()) | |
.attr("height", function(d) { return height - y(d.frequency) + "px"; }) | |
.style("fill-opacity", 0) | |
.transition() | |
.duration(750) | |
.attr("y", function(d) { return y(d.frequency); }) | |
.style("fill-opacity", 1); | |
// EXIT | |
// Remove old elements as needed. | |
bar.exit() | |
.attr("class", "exit") | |
.transition() | |
.duration(750) | |
.attr("y", 0) | |
.style("fill-opacity", 0) | |
.remove(); | |
// Move y-axis in front of columns so the labels aren't covered. | |
yAxisLabels.each(function() { | |
this.parentNode.appendChild(this); | |
}); | |
} | |
// Automatically update with random data every 1.5 seconds. | |
setInterval(function() { | |
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); | |
var i; | |
var new_data = []; | |
for (i = 0; i < alphabet.length; i++) { | |
var datum = {}; | |
datum.letter = alphabet[i]; | |
datum.frequency = Math.random() / 9; | |
new_data.push(datum) | |
} | |
var number_to_keep = Math.floor(Math.random() * new_data.length); | |
if (number_to_keep < 3) { | |
number_to_keep = new_data.length; | |
} | |
new_data = d3.shuffle(new_data) | |
.slice(0, number_to_keep) | |
.sort(function(x, y) { return d3.ascending(x.letter, y.letter); }); | |
update(new_data); | |
}, 1500); | |
// Allows iframe on bl.ocks.org. | |
// d3.select(self.frameElement).style("height", height + "px"); | |
</script> | |
</body> | |
</html> |