Inspired by Trulia Trends - but with code and using SVG.
Example data shows concurrent user sessions over time, taken from a development environment.
MIT Licensed, see LICENSE.MD.
forked from tjdecke's block: Day / Hour Heatmap
xxxxxxxxxx
<meta charset="utf-8">
<html>
<head>
<style>
rect.bordered {
stroke: #E6E6E6;
stroke-width:2px;
}
text.mono {
font-size: 9pt;
font-family: avenir, avenir;
fill: #aaa;
}
text.axis-workweek {
fill: #000;
}
text.axis-worktime {
fill: #000;
}
</style>
<script src="https://d3js.org/d3.v3.js"></script>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript">
var margin = { top: 112, right: 2, bottom: 78, left: 73 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
gridSize = Math.floor(width / 8),
legendElementWidth = gridSize/15,
buckets = 10,
colors = ["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5",
"#7bccc4","#4eb3d3","#2b8cbe","#08589e"],
days = ["Sunday","Monday", "Tuesday",
"Wednesday", "Thursday", "Friday", "Saturday"],
times = ["12am","1a", "2a", "3a", "4a", "5a", "6a",
"7a", "8a", "9a", "10a", "11a", "12pm", "1p",
"2p", "3p", "4p", "5p", "6p", "7p", "8p",
"9p", "10p", "11p"];
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("text")
.attr("x", -65)
.attr("y", -84)
.attr("dy", "0.6532em")
.text("SFPD Incidents by Hour and Day of Week")
.style("font", "25px avenir")
.style("fill", "#000000");
svg.append("text")
.attr("x", 380)
.attr("y", -50)
.attr("dy", "1.2496em")
.text("Date")
.style("font", "12px avenir")
.style("fill", "#000000");
svg.append("text")
.attr("x", -65)
.attr("y", -20)
.attr("dy", "1.2496em")
.text("Hour of Time")
.style("font", "10px avenir")
.style("fill", "#000000");
svg.append("text")
.attr("x", 0)
.attr("y", 332)
.attr("dy", "0em")
.style("font", "12px avenir")
.style("fill", "#000000")
.text("This heatmap highlights the frequency in which indicidents take place at a certain time and day. The lower right portion of the map has a much ");
svg.append("text")
.attr("x", 0)
.attr("y", 332)
.attr("dy", "1em")
.style("font", "12px avenir")
.style("fill", "#000000")
.text("darker hue than the rest of the visualization, depicting a higher number of incidents. This group includes Thursday, Friday, and Saturday between");
svg.append("text")
.attr("x", 0)
.attr("y", 332)
.attr("dy", "2em")
.style("font", "12px avenir")
.style("fill", "#000000")
.text("working hours (bolded) .");
svg.append("text")
.attr("x", 0)
.attr("y", 332)
.attr("dy", "4em")
.style("font", "12px avenir")
.style("fill", "#000000")
.text("By Anaelia Ovalle")
.style("font-weight", "bold");
var dayLabels = svg.selectAll(".dayLabel")
.data(days)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", function(d, i) { return i * gridSize; })
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(60," + gridSize/-10 + ")")
.attr("class", "dayLabel mono axis")
.attr("class", function(d, i) { return ((i == "Sunday" || i == "Saturday") ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis"); });
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d) { return d; })
.attr("x", -9)
.attr("y", function (d, i) { return i * gridSize/8.5; })
.attr("dy", "1.05em")
.style("text-anchor", "end")
.attr("class", function(d, i) { return ((i >= 7 && i <= 17) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); });
var heatmapChart = function(csvFile) {
d3.csv(csvFile,
function(d) {
return {
day: +d.day,
hour: +d.hour,
value: +d.value
};
},
function(error, data) {
var colorScale = d3.scale.quantile()
.domain([0, buckets-1, d3.max(data, function (d) { return d.value; })])
.range(colors);
var cards = svg.selectAll(".hour")
.data(data, function(d) {return d.hour+':'+d.day;});
cards.append("title");
cards.enter().append("rect")
.attr("x", function(d) { return (d.day-1) * gridSize; }) .attr("y", function(d) { return (d.hour ) * gridSize/8.5; })
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", height/24)
.style("fill", colors[0]);
cards.transition().duration(1000)
.style("fill", function(d) { return colorScale(d.value); });
cards.select("title").text(function(d) { return d.value; });
cards.exit().remove();
var legend = svg.selectAll(".legend")
.data([0].concat(colorScale.quantiles()), function(d) { return d; });
legend.enter().append("g")
.attr("class", "legend");
legend.append("rect")
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", 0)
.attr("width", legendElementWidth)
.attr("height", gridSize / 7)
.attr("transform", "translate(" + 790 + "," + 0+ ")")
.style("fill", function(d, i) { return colors[i]; });
legend.append("text")
.attr("x",782)
.attr("y",-5)
.text("Number of Records")
.style("font", "10px avenir")
.style("fill", "#000000");
legend.append("text")
.attr("x",780)
.attr("y",11)
.text(0)
.style("font", "10px avenir")
.style("fill", "#000000");
legend.append("text")
.attr("x",855)
.attr("y",11)
.text("161")
.style("font", "10px avenir")
.style("fill", "#000000");
legend.exit().remove();
});
};
heatmapChart("new_heat.csv");
</script>
</body>
</html>
Modified http://d3js.org/d3.v3.js to a secure url
https://d3js.org/d3.v3.js