|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
body { |
|
font: 10px sans-serif; |
|
} |
|
|
|
.label { |
|
font-weight: bold; |
|
} |
|
|
|
.tile { |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
#interpolators { |
|
position: fixed; |
|
top: 1px; |
|
right: 1px; |
|
} |
|
|
|
</style> |
|
</head> |
|
<body> |
|
|
|
<svg id="chart"></svg> |
|
<select id="interpolators"></select> |
|
|
|
<script src="//d3js.org/d3.v4.min.js"></script> |
|
<script src="//d3js.org/d3-scale-chromatic.v0.3.min.js"></script> |
|
<script> |
|
|
|
var margin = {top: 20, right: 90, bottom: 30, left: 50}, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var parseDate = d3.timeParse("%Y-%m-%d"), |
|
formatDate = d3.timeFormat("%b %d"); |
|
|
|
var x = d3.scaleTime().range([0, width]), |
|
y = d3.scaleLinear().range([height, 0]), |
|
z = d3.scaleSequential(d3.interpolateViridis); |
|
|
|
// The size of the buckets in the CSV data file. |
|
// This could be inferred from the data if it weren't sparse. |
|
var xStep = 864e5, |
|
yStep = 100; |
|
|
|
var svg = 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 + ")"); |
|
|
|
d3.csv("data.csv", function(error, buckets) { |
|
if (error) throw error; |
|
|
|
// Coerce the CSV data to the appropriate types. |
|
buckets.forEach(function(d) { |
|
d.date = parseDate(d.date); |
|
d.bucket = +d.bucket; |
|
d.count = +d.count; |
|
}); |
|
|
|
// Compute the scale domains. |
|
x.domain(d3.extent(buckets, function(d) { return d.date; })); |
|
y.domain(d3.extent(buckets, function(d) { return d.bucket; })); |
|
z.domain([0, d3.max(buckets, function(d) { return d.count; })]); |
|
|
|
// Extend the x- and y-domain to fit the last bucket. |
|
// For example, the y-bucket 3200 corresponds to values [3200, 3300]. |
|
x.domain([x.domain()[0], +x.domain()[1] + xStep]); |
|
y.domain([y.domain()[0], y.domain()[1] + yStep]); |
|
|
|
// Display the tiles for each non-zero bucket. |
|
// See http://bl.ocks.org/3074470 for an alternative implementation. |
|
svg.selectAll(".tile") |
|
.data(buckets) |
|
.enter().append("rect") |
|
.attr("class", "tile") |
|
.attr("x", function(d) { return x(d.date); }) |
|
.attr("y", function(d) { return y(d.bucket + yStep); }) |
|
.attr("width", x(xStep) - x(0)) |
|
.attr("height", y(0) - y(yStep)) |
|
.style("fill", function(d) { return z(d.count); }); |
|
|
|
// Add a legend for the color values. |
|
var legend = svg.selectAll(".legend") |
|
.data(z.ticks(6).slice(1).reverse()) |
|
.enter().append("g") |
|
.attr("class", "legend") |
|
.attr("transform", function(d, i) { return "translate(" + (width + 20) + "," + (20 + i * 20) + ")"; }); |
|
|
|
legend.append("rect") |
|
.attr("width", 20) |
|
.attr("height", 20) |
|
.style("fill", z); |
|
|
|
legend.append("text") |
|
.attr("x", 26) |
|
.attr("y", 10) |
|
.attr("dy", ".35em") |
|
.text(String); |
|
|
|
svg.append("text") |
|
.attr("class", "label") |
|
.attr("x", width + 20) |
|
.attr("y", 10) |
|
.attr("dy", ".35em") |
|
.text("Count"); |
|
|
|
// Add an x-axis with label. |
|
svg.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(d3.axisBottom().scale(x).ticks(d3.timeDay).tickFormat(formatDate)) |
|
.append("text") |
|
.attr("class", "label") |
|
.attr("x", width) |
|
.attr("y", -6) |
|
.attr("text-anchor", "end") |
|
.text("Date"); |
|
|
|
// Add a y-axis with label. |
|
svg.append("g") |
|
.attr("class", "y axis") |
|
.call(d3.axisLeft().scale(y)) |
|
.append("text") |
|
.attr("class", "label") |
|
.attr("y", 6) |
|
.attr("dy", ".71em") |
|
.attr("text-anchor", "end") |
|
.attr("transform", "rotate(-90)") |
|
.text("Value"); |
|
|
|
var interpolators = [ |
|
|
|
// These are from d3-scale. |
|
"Viridis", |
|
"Inferno", |
|
"Magma", |
|
"Plasma", |
|
"Warm", |
|
"Cool", |
|
"Rainbow", |
|
"CubehelixDefault", |
|
|
|
// These are from d3-scale-chromatic |
|
"Blues", |
|
"Greens", |
|
"Greys", |
|
"Oranges", |
|
"Purples", |
|
"Reds", |
|
"BuGn", |
|
"BuPu", |
|
"GnBu", |
|
"OrRd", |
|
"PuBuGn", |
|
"PuBu", |
|
"PuRd", |
|
"RdPu", |
|
"YlGnBu", |
|
"YlGn", |
|
"YlOrBr", |
|
"YlOrRd" |
|
]; |
|
|
|
var autoCycle = setInterval(function (){ |
|
var node = d3.select("#interpolators").node(); |
|
var i = node.selectedIndex; |
|
i = (i + 1) % interpolators.length; |
|
setSelectedInterpolator(interpolators[i]); |
|
node.selectedIndex = i; |
|
}, 1000); |
|
|
|
d3.select("#interpolators") |
|
.on("change", function (){ |
|
setSelectedInterpolator(interpolators[this.selectedIndex]); |
|
clearInterval(autoCycle); |
|
}) |
|
.selectAll("option").data(interpolators) |
|
.enter().append("option") |
|
.attr("value", function (d){ return d; }) |
|
.text(function (d){ return d; }); |
|
|
|
function setSelectedInterpolator(name, transitionDuration){ |
|
|
|
if(typeof transitionDuration === "undefined"){ |
|
transitionDuration = 500; |
|
} |
|
|
|
z.interpolator(d3["interpolate" + name]); |
|
|
|
svg.selectAll(".tile") |
|
.transition().duration(transitionDuration) |
|
.style("fill", function(d) { return z(d.count); }); |
|
|
|
svg.selectAll(".legend rect") |
|
.transition().duration(transitionDuration) |
|
.style("fill", z); |
|
|
|
window.location.hash = name; |
|
d3.select("#interpolators").node().selectedIndex = interpolators.indexOf(name); |
|
} |
|
|
|
if(window.location.hash){ |
|
clearInterval(autoCycle); |
|
setSelectedInterpolator(window.location.hash.replace("#", ""), 0); |
|
} |
|
|
|
}); |
|
|
|
|
|
</script> |
|
</body> |
|
</html |