| <html> | |
|   <head> | |
|     <title>Stack Overflow - Calendar Heatmap</title> | |
|     <style> | |
|       .body { | |
|         height: 97%; | |
|       } | |
| 
 | |
|       svg { | |
|         height: 1800; | |
|         width: 97%; | |
|       } | |
|     </style> | |
|         <script | |
|         src="https://code.jquery.com/jquery-3.3.1.min.js" | |
|         integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" | |
|         crossorigin="anonymous"> | |
|     </script> | |
|     <script src="js/githubAPI.js"></script> | |
|     <script src="js/utilities.js"></script> | |
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.2/d3.js"></script> | |
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-color/1.2.1/d3-color.js"></script> | |
|   </head> | |
|   <body> | |
|     <h3>Stack Overflow - Calendar Heatmap</h3> | |
|     <h4>Daily Commits to RITlug/teleirc</h4> | |
|     <svg id="svg"></svg> | |
|     <script> | |
| 
 | |
| 
 | |
|       function countCommitsPerDay(commits) | |
|       { | |
|         var reduce = []; | |
|         console.log(commits); | |
| 
 | |
|         var current_date = new Date(commits[0].date); | |
|         current_date.setHours(0,0,0,0); | |
|         var current_count = 0; | |
|         for(var i = 1; i < commits.length; i++) | |
|         { | |
|           var d = new Date(commits[i].date); | |
|           d.setHours(0,0,0,0); | |
|           console.log(d); | |
|           if(current_date.getTime() == d.getTime()) | |
|           { | |
|             console.log("Stonks"); | |
|             current_count++; | |
|           } | |
|           else | |
|           { | |
|             reduce.push({Date:current_date.toISOString(), AnswerCount: current_count}); | |
|             current_count = 1; | |
|             current_date = d; | |
|           } | |
|           reduce.push({Date:current_date.toISOString(), AnswerCount: current_count}); | |
|         } | |
|         console.log(reduce); | |
|         return reduce; | |
|       } | |
| 
 | |
| 
 | |
|       function createHeatmapGraph(commits, containerID) | |
|       { | |
| 
 | |
|         var sample = countCommitsPerDay(commits); | |
| 
 | |
|         sample.sort((a, b) => new Date(a.Date) - new Date(b.Date)); | |
| 
 | |
|         const dateValues = sample.map(dv => ({ | |
|           date: d3.timeDay(new Date(dv.Date)), | |
|           value: Number(dv.AnswerCount) | |
|         })); | |
| 
 | |
|         const svg = d3.select("#" + containerID); | |
|         const { width, height } = document | |
|           .getElementById(containerID) | |
|           .getBoundingClientRect(); | |
| 
 | |
| 
 | |
|         function draw() { | |
|           const years = d3 | |
|             .nest() | |
|             .key(d => d.date.getUTCFullYear()) | |
|             .entries(dateValues) | |
|             .reverse(); | |
| 
 | |
|           const values = dateValues.map(c => c.value); | |
|           const maxValue = d3.max(values); | |
|           const minValue = d3.min(values); | |
| 
 | |
|           const cellSize = 15; | |
|           const yearHeight = cellSize * 7; | |
| 
 | |
|           const group = svg.append("g"); | |
| 
 | |
|           const year = group | |
|             .selectAll("g") | |
|             .data(years) | |
|             .join("g") | |
|             .attr( | |
|               "transform", | |
|               (d, i) => `translate(50, ${yearHeight * i + cellSize * 1.5})` | |
|             ); | |
| 
 | |
|           year | |
|             .append("text") | |
|             .attr("x", -5) | |
|             .attr("y", -30) | |
|             .attr("text-anchor", "end") | |
|             .attr("font-size", 16) | |
|             .attr("font-weight", 550) | |
|             .attr("transform", "rotate(270)") | |
|             .text(d => d.key); | |
| 
 | |
|           const formatDay = d => | |
|             ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"][d.getUTCDay()]; | |
|           const countDay = d => d.getUTCDay(); | |
|           const timeWeek = d3.utcSunday; | |
|           const formatDate = d3.utcFormat("%x"); | |
|           const colorFn = d3 | |
|             .scaleSequential(d3.interpolateBuGn) | |
|             .domain([Math.floor(minValue) -1, Math.ceil(maxValue)]); | |
|           const format = d3.format("+.2%"); | |
| 
 | |
|           year | |
|             .append("g") | |
|             .attr("text-anchor", "end") | |
|             .selectAll("text") | |
|             .data(d3.range(7).map(i => new Date(1995, 0, i))) | |
|             .join("text") | |
|             .attr("x", -5) | |
|             .attr("y", d => (countDay(d) + 0.5) * cellSize) | |
|             .attr("dy", "0.31em") | |
|             .attr("font-size", 12) | |
|             .text(formatDay); | |
| 
 | |
|           year | |
|             .append("g") | |
|             .selectAll("rect") | |
|             .data(d => d.values) | |
|             .join("rect") | |
|             .attr("width", cellSize - 1.5) | |
|             .attr("height", cellSize - 1.5) | |
|             .attr( | |
|               "x", | |
|               (d, i) => timeWeek.count(d3.utcYear(d.date), d.date) * cellSize + 10 | |
|             ) | |
|             .attr("y", d => countDay(d.date) * cellSize + 0.5) | |
|             .attr("fill", d => colorFn(d.value)) | |
|             .append("title") | |
|             .text(d => `${formatDate(d.date)}: ${d.value.toFixed(2)}`); | |
| 
 | |
|           const legend = group | |
|             .append("g") | |
|             .attr( | |
|               "transform", | |
|               `translate(10, ${years.length * yearHeight + cellSize * 4})` | |
|             ); | |
| 
 | |
|           const categoriesCount = 10; | |
|           const categories = [...Array(categoriesCount)].map((_, i) => { | |
|             const upperBound = (maxValue / categoriesCount) * (i + 1); | |
|             const lowerBound = (maxValue / categoriesCount) * i; | |
| 
 | |
|             return { | |
|               upperBound, | |
|               lowerBound, | |
|               color: d3.interpolateBuGn(upperBound / maxValue), | |
|               selected: true | |
|             }; | |
|           }); | |
| 
 | |
|           const legendWidth = 60; | |
| 
 | |
|           function toggle(legend) { | |
|             const { lowerBound, upperBound, selected } = legend; | |
| 
 | |
|             legend.selected = !selected; | |
| 
 | |
|             const highlightedDates = years.map(y => ({ | |
|               key: y.key, | |
|               values: y.values.filter( | |
|                 v => v.value > lowerBound && v.value <= upperBound | |
|               ) | |
|             })); | |
| 
 | |
|             year | |
|               .data(highlightedDates) | |
|               .selectAll("rect") | |
|               .data(d => d.values, d => d.date) | |
|               .transition() | |
|               .duration(500) | |
|               .attr("fill", d => (legend.selected ? colorFn(d.value) : "white")); | |
|           } | |
| 
 | |
|           legend | |
|             .selectAll("rect") | |
|             .data(categories) | |
|             .enter() | |
|             .append("rect") | |
|             .attr("fill", d => d.color) | |
|             .attr("x", (d, i) => legendWidth * i) | |
|             .attr("width", legendWidth) | |
|             .attr("height", 15) | |
|             .on("click", toggle); | |
| 
 | |
|           legend | |
|             .selectAll("text") | |
|             .data(categories) | |
|             .join("text") | |
|             .attr("transform", "rotate(90)") | |
|             .attr("y", (d, i) => -legendWidth * i) | |
|             .attr("dy", -30) | |
|             .attr("x", 18) | |
|             .attr("text-anchor", "start") | |
|             .attr("font-size", 11) | |
|             .text(d => `${d.lowerBound.toFixed(2)} - ${d.upperBound.toFixed(2)}`); | |
| 
 | |
|           legend | |
|             .append("text") | |
|             .attr("dy", -5) | |
|             .attr("font-size", 14) | |
|             .attr("text-decoration", "underline"); | |
|         } | |
| 
 | |
|         draw(); | |
|       } | |
| 
 | |
|       getRepoCommits("torvalds/linux", (data)=> | |
|       { | |
|         console.log(data); | |
|         createHeatmapGraph(data, "svg"); | |
|       }, | |
|       (error)=> | |
|       { | |
|         console.log(error); | |
|       }) | |
| 
 | |
| 
 | |
|     </script> | |
|   </body> | |
| </html>
 |