This example builds a horizontal, heatmap calendar. See also Mike Bostock's Calendar View.
| day | count | |
|---|---|---|
| 2016-05-12 | 171 | |
| 2016-06-17 | 139 | |
| 2016-05-02 | 556 | |
| 2016-04-10 | 1 | |
| 2016-05-04 | 485 | |
| 2016-03-27 | 1 | |
| 2016-05-26 | 42 | |
| 2016-05-25 | 337 | |
| 2016-05-23 | 267 | |
| 2016-05-05 | 569 | |
| 2016-03-31 | 32 | |
| 2016-03-25 | 128 | |
| 2016-05-13 | 221 | |
| 2016-03-30 | 26 | |
| 2016-03-15 | 3 | |
| 2016-04-24 | 10 | |
| 2016-04-27 | 312 | |
| 2016-03-20 | 99 | |
| 2016-05-10 | 358 | |
| 2016-04-01 | 15 | |
| 2016-05-11 | 199 | |
| 2016-07-06 | 744 | |
| 2016-05-08 | 23 | |
| 2016-03-28 | 98 | |
| 2016-03-29 | 64 | |
| 2016-04-30 | 152 | |
| 2016-03-21 | 148 | |
| 2016-03-19 | 20 | |
| 2016-05-07 | 69 | |
| 2016-04-29 | 431 | |
| 2016-04-25 | 330 | |
| 2016-04-28 | 353 | |
| 2016-04-18 | 9 | |
| 2016-01-10 | 1 | |
| 2016-01-09 | 2 | |
| 2016-03-26 | 21 | |
| 2016-05-27 | 18 | |
| 2016-04-19 | 10 | |
| 2016-04-06 | 1 | |
| 2016-04-12 | 214 | |
| 2016-05-03 | 536 | |
| 2016-07-03 | 3 | |
| 2016-06-16 | 1 | |
| 2016-03-24 | 138 | |
| 2016-04-26 | 351 | |
| 2016-04-23 | 14 | |
| 2016-05-01 | 19 | |
| 2016-07-05 | 523 | |
| 2016-05-22 | 3 | |
| 2016-05-09 | 430 | |
| 2016-05-24 | 472 | |
| 2016-04-11 | 172 | |
| 2016-03-17 | 7 | |
| 2016-05-14 | 10 | |
| 2016-05-06 | 449 | |
| 2016-07-04 | 295 | |
| 2016-05-15 | 12 | |
| 2016-03-23 | 216 | |
| 2016-03-18 | 47 | |
| 2016-03-22 | 179 |
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| #calendar { | |
| margin: 20px; | |
| } | |
| .month { | |
| margin-right: 8px; | |
| } | |
| .month-name { | |
| font-size: 85%; | |
| fill: #777; | |
| font-family: Arial, Helvetica; | |
| } | |
| .day.hover { | |
| stroke: #6d6E70; | |
| stroke-width: 2; | |
| } | |
| .day.focus { | |
| stroke: #ffff33; | |
| stroke-width: 2; | |
| } | |
| </style> | |
| <body> | |
| <div id="calendar"></div> | |
| <script src="//d3js.org/d3.v4.min.js"></script> | |
| <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> | |
| <script> | |
| function drawCalendar(dateData){ | |
| var weeksInMonth = function(month){ | |
| var m = d3.timeMonth.floor(month) | |
| return d3.timeWeeks(d3.timeWeek.floor(m), d3.timeMonth.offset(m,1)).length; | |
| } | |
| var minDate = d3.min(dateData, function(d) { return new Date(d.day) }) | |
| var maxDate = d3.max(dateData, function(d) { return new Date(d.day) }) | |
| var cellMargin = 2, | |
| cellSize = 20; | |
| var day = d3.timeFormat("%w"), | |
| week = d3.timeFormat("%U"), | |
| format = d3.timeFormat("%Y-%m-%d"), | |
| titleFormat = d3.utcFormat("%a, %d-%b"); | |
| monthName = d3.timeFormat("%B"), | |
| months= d3.timeMonth.range(d3.timeMonth.floor(minDate), maxDate); | |
| var svg = d3.select("#calendar").selectAll("svg") | |
| .data(months) | |
| .enter().append("svg") | |
| .attr("class", "month") | |
| .attr("height", ((cellSize * 7) + (cellMargin * 8) + 20) ) // the 20 is for the month labels | |
| .attr("width", function(d) { | |
| var columns = weeksInMonth(d); | |
| return ((cellSize * columns) + (cellMargin * (columns + 1))); | |
| }) | |
| .append("g") | |
| svg.append("text") | |
| .attr("class", "month-name") | |
| .attr("y", (cellSize * 7) + (cellMargin * 8) + 15 ) | |
| .attr("x", function(d) { | |
| var columns = weeksInMonth(d); | |
| return (((cellSize * columns) + (cellMargin * (columns + 1))) / 2); | |
| }) | |
| .attr("text-anchor", "middle") | |
| .text(function(d) { return monthName(d); }) | |
| var rect = svg.selectAll("rect.day") | |
| .data(function(d, i) { return d3.timeDays(d, new Date(d.getFullYear(), d.getMonth()+1, 1)); }) | |
| .enter().append("rect") | |
| .attr("class", "day") | |
| .attr("width", cellSize) | |
| .attr("height", cellSize) | |
| .attr("rx", 3).attr("ry", 3) // rounded corners | |
| .attr("fill", '#eaeaea') // default light grey fill | |
| .attr("y", function(d) { return (day(d) * cellSize) + (day(d) * cellMargin) + cellMargin; }) | |
| .attr("x", function(d) { return ((week(d) - week(new Date(d.getFullYear(),d.getMonth(),1))) * cellSize) + ((week(d) - week(new Date(d.getFullYear(),d.getMonth(),1))) * cellMargin) + cellMargin ; }) | |
| .on("mouseover", function(d) { | |
| d3.select(this).classed('hover', true); | |
| }) | |
| .on("mouseout", function(d) { | |
| d3.select(this).classed('hover', false); | |
| }) | |
| .datum(format); | |
| rect.append("title") | |
| .text(function(d) { return titleFormat(new Date(d)); }); | |
| var lookup = d3.nest() | |
| .key(function(d) { return d.day; }) | |
| .rollup(function(leaves) { | |
| return d3.sum(leaves, function(d){ return parseInt(d.count); }); | |
| }) | |
| .object(dateData); | |
| var scale = d3.scaleLinear() | |
| .domain(d3.extent(dateData, function(d) { return parseInt(d.count); })) | |
| .range([0.4,1]); // the interpolate used for color expects a number in the range [0,1] but i don't want the lightest part of the color scheme | |
| rect.filter(function(d) { return d in lookup; }) | |
| .style("fill", function(d) { return d3.interpolatePuBu(scale(lookup[d])); }) | |
| .select("title") | |
| .text(function(d) { return titleFormat(new Date(d)) + ": " + lookup[d]; }); | |
| } | |
| d3.csv("dates.csv", function(response){ | |
| drawCalendar(response); | |
| }) | |
| </script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment