Gets Treehouse badge data as JSON and uses moment.js to create a github-like activity calendar.
A Pen by James Barnett on CodePen.
<div class = "activity-chart"> | |
<h1>Treehouse Achievements Calendar</h1> | |
<ol class = "days-of-week"> | |
<li>M</li> | |
<li>W</li> | |
<li>F</li> | |
</ol> | |
<div id = "month" class = "month"></div> | |
<div id = "days" class = "days"></div> | |
<div class = "key"> | |
<span>Less</span> | |
<ul> | |
<li class = "activity-four"></li> | |
<li class = "activity-three"></li> | |
<li class = "activity-two"></li> | |
<li class = "activity"></li> | |
<li class = "day-key"></li> | |
</ul> | |
<span>More</span> | |
</div> | |
</div> |
$(document).ready(function() { | |
var user = "jessicasideways"; | |
//var user = "jamesbarnett"; | |
$.getJSON("http://teamtreehouse.com/" + user + ".json", function(data) { | |
var dates = []; | |
var start_time = moment().subtract("year", 1); | |
/* because we are filtering by date, don't use the for loop counter because it will add in undefined items to the arry for each of the filtered items*/ | |
var countBadges = 0; | |
for (var a in data.badges) { | |
if ((moment(data.badges[a].earned_date)).isAfter(moment(start_time))){ | |
countBadges++; | |
dates[countBadges] = moment(data.badges[a].earned_date).format("MM-DD-YY"); | |
} | |
} | |
var counted = compressArray(dates); | |
/*** draw months ***/ | |
var month = moment(); | |
var outputMonth = "<ol class = 'month'>"; | |
for (i = 0; i <= 12; i++) { | |
var durationMonth = moment.duration({'months' : 1}); | |
outputMonth += "<li>"; | |
outputMonth += moment(month).format("MMM"); | |
outputMonth += "</li>"; | |
month = moment(month).subtract(durationMonth); | |
} | |
outputMonth += "</ol>"; | |
var output = "<ol><div class = 'week'>"; | |
var day = moment(); | |
/* Calculate the offset for days of the week to line up correctly */ | |
var dayOfWeekOffset = 6 - (parseInt(moment().format("d"),10)); | |
/* draw offset */ | |
for (i = 0; i < (dayOfWeekOffset); i++) { output += "<li class = 'offset'></li>"; } | |
/*** draw calendar ***/ | |
var badgeHigh = 0; | |
for (i = 365; i >= 0; i--) { | |
var badges = "No"; | |
var activity4 = false; | |
var activity3 = false; | |
var activity2 = false; | |
var activity = false; | |
checkDay = moment(day).format("MM-DD-YY"); | |
/* find highest single day badge count */ | |
for( b = 0; b < counted.length; b++) { | |
if (counted[b].value === checkDay) | |
{ | |
badges = counted[b].count; | |
if( badges > badgeHigh){ | |
badgeHigh = badges; | |
} | |
}} | |
/* apply activity colors relative to highest single day badge count */ | |
for( c = 0; c < counted.length; c++) { | |
if (counted[c].value === checkDay) | |
{ | |
badges = counted[c].count; | |
if (badges >= (Math.floor(badgeHigh * 0.75))){ | |
activity4 = true; | |
} | |
else if (badges > (Math.floor(badgeHigh * 0.5))){ | |
activity3 = true; | |
} | |
else if (badges > (Math.floor(badgeHigh * 0.25))){ activity2 = true;} | |
else { | |
activity = true; | |
} | |
} | |
} | |
/* stick date in data attribute so we can parse it later for filtering purposes */ | |
var dataDate = moment(day).format("MM-DD-YY"); | |
var li = "<li data-date = '" + checkDay + "' class ="; | |
if(activity4 === true){ output += li + "'activity-four'>";} | |
else if(activity3 === true){ output += li + "'activity-three'>";} | |
else if(activity2 === true){ output += li + "'activity-two'>";} | |
else if(activity === true){ output += li + "'activity'>";} | |
else { output += li + "''>";} | |
output += '<span class = "tooltip"><span class = "bold">' + badges + " badges</span> earned on " + moment(day).format("MMMM Do YYYY") + '</span>'; | |
output += "</li>"; | |
var duration = moment.duration({'days' : 1}); | |
day = moment(day).subtract(duration); | |
} | |
output += "</div></ol>"; | |
document.getElementById("month").innerHTML = outputMonth; | |
document.getElementById("days").innerHTML = output; | |
/* parse which day was clicked */ | |
var selectedDay = moment().format("MM-DD-YY"); | |
$(".days li").click(function() { | |
selectedDay = ($(this).attr("data-date")); | |
console.log(selectedDay); | |
}); | |
/*** streak count ***/ | |
var streakOverall = 1; | |
var maxStreak = streakOverall; | |
for (var e = 0; e < counted.length; e++) { | |
var current = counted[e].value; | |
var next = current; | |
var tomorrow = moment(current).add("days", 1); | |
//don't overflow array by comparing last item | |
if((e + 1) < counted.length){ | |
next = counted[(e + 1)].value; | |
} | |
// check if "next" item in array is the same day as the "current" item + 1 day | |
if (moment(next).isSame(moment(tomorrow), 'day')) { | |
streakOverall++; | |
} | |
else { streakOverall = 1; } | |
// check if current streak is longer than previous max streak | |
if (streakOverall > maxStreak) { maxStreak = streakOverall; } | |
} | |
console.log("max streak: " + maxStreak); | |
//######################################3 | |
var streakCurrent = 1; | |
for (var f = counted.length - 1 ; f > 0; f--) { | |
var dayCurrent = counted[f].value; | |
var previous = dayCurrent; | |
//don't overflow array by comparing last item | |
if (f > 1){ previous = counted[(f - 1)].value; } | |
var yesterday = moment(dayCurrent).subtract("days", 1); | |
if (moment(previous).isSame(moment(yesterday), 'day')) { streakCurrent++; } | |
else { break; } | |
} | |
console.log("current streak: " + streakCurrent); | |
console.log("badge count: " + countBadges); | |
}); | |
function compressArray(original) { | |
var compressed = []; | |
var copy = original.slice(0); | |
for (var i = 0; i < original.length; i++) { | |
var myCount = 0; | |
for (var w = 0; w < copy.length; w++) { | |
if (original[i] == copy[w]) { | |
myCount++; | |
delete copy[w]; | |
} | |
} | |
if (myCount > 0) { | |
var a = {}; | |
a.value = original[i]; | |
a.count = myCount; | |
compressed.push(a); | |
} | |
} | |
return compressed; | |
} | |
}); |
ol, li { padding: 0; margin: 0; list-style: none;} | |
h1 { | |
font-size: 1.5em; | |
margin: 70px 75px; | |
} | |
.activity-chart, h1 {color: #525252;} | |
.days li, .day-key { background: #eee; } | |
.activity-chart { | |
width: 720px; | |
height: 205px; | |
padding-left: 110px; /* center in container */ | |
margin: 50px 150px; | |
position: relative; | |
/*outline: solid;*/ | |
} | |
/*** day of week heading ***/ | |
.days-of-week { | |
width: 15px; | |
position: absolute; | |
left: -10px; | |
top: 80px; | |
} | |
@-moz-document url-prefix() { | |
.days-of-week { left: 23px; } | |
} | |
.days-of-week { font-size: 0.7em; } | |
.days-of-week li:nth-child(2) { margin: 13px 0; } | |
/*** month headings ***/ | |
.month ol { | |
position: absolute; | |
top: 40px; | |
left: -30px; | |
} | |
.month li { | |
float: right; | |
margin-left: 39px; | |
font-size: 0.75em; | |
} | |
/*** draw days ***/ | |
.days { | |
font-size: 0.75em; | |
margin-top: 15px; | |
float: right; /* needed to float onto screen */ | |
} | |
/* offset so days of the week line up | |
over-specified to win specificity battle */ | |
.activity-chart .offset:hover { outline: none; } | |
.activity-chart .offset { background: none; } | |
/* create vertical weeks */ | |
.week { | |
width: 108px; | |
transform: rotate(90deg); | |
} | |
.days li, .key li { | |
width: 12px; | |
height: 12px; | |
float: right; /* order days starting at the bottom right */ | |
} | |
.days .bold { font-weight: bold; } | |
.days li { margin: 1.5px; } | |
/*** color-code by activity level ***/ | |
.activity-chart .activity { background: #d6e685; } | |
.activity-chart .activity-two { background: #8cc665; } | |
.activity-chart .activity-three { background: #44a340; } | |
.activity-chart .activity-four { background: #1e6823; } | |
.key { | |
position: absolute; | |
bottom: 0; | |
right: 55px; | |
} | |
.key ul { | |
display: inline-block; | |
margin: 0; | |
padding: 0; | |
} | |
.key li { margin: 0px 2px; } | |
/*** tooltips ***/ | |
.days li .tooltip { display: none; } | |
.days li:hover | |
{ | |
/*outline disabled due to firefox cross-broswer issue */ | |
/*outline: 1px solid #555;*/ | |
position: relative; | |
z-index: 3; | |
} | |
.days li:hover .tooltip { | |
transform: rotate(-90deg); | |
display: block; | |
position: absolute; | |
/* top & left are reversed because the calendar is rotated 90 deg */ | |
top: -13px; | |
left: -185px; | |
width: 300px; | |
padding: 10px 5px; | |
text-align: center; | |
background-color: #333; | |
color: #f1f1f1; | |
} | |
/*** little triangle on the tooltip ***/ | |
.tooltip:before { | |
content: ""; | |
position: absolute; | |
width: 0; | |
height: 0; | |
bottom: -10px; | |
right: 150px; | |
border-left: 5px solid transparent; | |
border-right: 5px solid transparent; | |
border-top: 11px solid #333; | |
} |
Gets Treehouse badge data as JSON and uses moment.js to create a github-like activity calendar.
A Pen by James Barnett on CodePen.