A Pen by James Barnett on CodePen.
Created
January 14, 2014 05:01
-
-
Save barnettjw/8413328 to your computer and use it in GitHub Desktop.
A Pen by James Barnett.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class = "container"> | |
<h1>Simple Treehouse API</h1> | |
<form class = "pure-form-aligned"> | |
<div class="pure-control-group"> | |
<label for = "username">Treehouse Username</label> | |
<input id = "username" class = "username" placeholder = "Treehouse username" tabindex = "1" autofocus = "autofocus"/> | |
</div> | |
<!--<div class="pure-control-group"> | |
<label for = "timeframe-length">Get data for the past</label> | |
<input id = "timeframe-length" class = "timeframe-length" placeholder = "7" tabindex = "2"/> | |
<div class="styled-select"> | |
<select id = "timeframe-unit" class = "timeframe-unit" tabindex = "3"> | |
<option>days</option> | |
<option>weeks</option> | |
<option>months</option> | |
</select> | |
</div> | |
</div>--> | |
<div class="pure-button pure-button-primary" tabindex = "4">Pull My Stats</div> | |
<!-------------------------------------------> | |
</form> | |
<img class = "spinner" id = "ac-spinner" src = "http://jamesbarnett.me/img/ajax-loader.gif" /> | |
<div class = "activity-chart"> | |
<h2>Treehouse Activity</h2> | |
<ol class = "days-of-week"> | |
<li>M</li> | |
<li>W</li> | |
<li>F</li> | |
</ol> | |
<div id = "month" class = "month clearfix"></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> | |
<img class = "spinner" id = "badges-spinner" src = "http://jamesbarnett.me/img/ajax-loader.gif" /> | |
<div class = "stats"> | |
<div class = "year-badges stats-box"> | |
<div id = "year-badges-num" class = "stats-box-title"></div> | |
<div class = "year-badges-date"> | |
<span id ="year-start" class = "date"></span> - | |
<span id ="year-end" class = "date"></span> | |
<p>Year of Badges</p> | |
</div> | |
</div> | |
<div class = "max-streak stats-box"> | |
<div id = "max-streak-num" class = "stats-box-title"></div> | |
<div class = "max-streak-date"> | |
<span id ="max-streak-start" class = "date"></span> - | |
<span id ="max-streak-end" class = "date"></span> | |
<p>Longest Streak</p> | |
</div> | |
</div> | |
<div class = "current-streak stats-box"> | |
<div id = "current-streak-num" class = "stats-box-title"></div> | |
<div class = "current-streak-date"> | |
<span id ="current-streak-start" class = "date"></span> - | |
<span id ="current-streak-end" class = "date"></span> | |
<p>Current Streak</p> | |
</div> | |
</div> | |
</div> | |
<div id = "badges" class = "badges"></div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(document).ready(function () { | |
var user = null; | |
/*var duration_num = null; | |
var duration_unit = null;*/ | |
$(".spinner").hide(); | |
getData(); | |
function getData() { | |
user = $("#username").val(); | |
/*duration_num = parseInt($("#timeframe-length").val(), 10); | |
duration_unit = $('#timeframe-unit').find('option:selected').text();*/ | |
user = "benjaminsimmons"; | |
//user = "jessicasideways"; | |
//user = "jackcrane"; | |
$(".activity-chart").hide(); | |
$(".stats").hide(); | |
$.getJSON("http://teamtreehouse.com/" + user + ".json", function (data) { | |
var start_time = moment().subtract("year", 1); | |
var dataDate = moment(day).format("MM-DD-YY"); | |
// stick collection of earned dates in an array | |
var dates = []; | |
// 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"); | |
} | |
} | |
// count number of occurrences of a date | |
var counted = compressArray(dates); | |
/****** draw months ******/ | |
var month = moment(); | |
var outputMonth = "<ol>"; | |
for (i = 0; i <= 12; i++) { | |
var durationMonth = moment.duration(1, 'months'); | |
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++) { | |
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 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(1, 'days'); | |
day = moment(day).subtract(duration); | |
} | |
output += "</div></ol>"; | |
document.getElementById("days").innerHTML = output; | |
document.getElementById("month").innerHTML = outputMonth; | |
$("#ac-spinner").hide(); | |
$(".activity-chart").show(); | |
$(".stats").css( "padding-bottom", "125px" ); | |
$(".stats").show(); | |
/****** parse which day was clicked ******/ | |
var selectedDay = moment().format("MM-DD-YY"); | |
$(".days li").click(function () { | |
selectedDay = ($(this).attr("data-date")); | |
var outputBadge = '<ul>'; | |
for (var d in data.badges) { | |
if ((moment(data.badges[d].earned_date)).isSame(moment(selectedDay), 'day')) { | |
outputBadge += "<li>"; | |
outputBadge += '<img src ="' + data.badges[d].icon_url + '" />'; | |
outputBadge += '<span class = "tooltip">' + data.badges[d].name + '</span>'; | |
outputBadge += "</li>"; | |
} | |
} | |
outputBadge += "</ul>"; | |
$("#badges-spinner").hide(); | |
document.getElementById("badges").innerHTML = outputBadge; | |
}); | |
/****** max streak count ******/ | |
var streakOverall = 0; | |
var streakMax = streakOverall; | |
var streakMaxEnd = null; | |
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 > streakMax) { | |
streakMax = streakOverall; | |
streakMaxEnd = next; //get date of end of streak | |
} | |
} | |
/****** current streak count ******/ | |
var mostRecent = counted[(counted.length-1)].value; | |
//console.log(mostRecent); | |
// If user has earned a badge today streakCurrent >= 1 | |
// else streakCurrent = 0 | |
var streakCurrent = 1; | |
if(moment(mostRecent).isSame(moment(), 'day')){ | |
streakCurrent = 1; | |
} | |
else { | |
streakCurrent = 0; | |
} | |
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; | |
} | |
// check if "previous" item in array is the same day as the "current" item - 1 day | |
var yesterday = moment(dayCurrent).subtract("days", 1); | |
if (moment(previous).isSame(moment(yesterday), 'day')) { | |
streakCurrent++; | |
} else { | |
break; | |
} | |
} | |
/***** output stats *****/ | |
document.getElementById("year-badges-num").innerHTML = countBadges + " Total"; | |
document.getElementById("year-start").innerHTML = moment(start_time).format("MMM DD YYYY"); | |
document.getElementById("year-end").innerHTML = moment().format("MMM DD YYYY"); | |
// Because when counting a streak "today" counts as 1, you need to subtract 1 day when doing date math to get a start/end date | |
document.getElementById("max-streak-num").innerHTML = streakMax + " Days"; | |
document.getElementById("max-streak-start").innerHTML = moment(streakMaxEnd).subtract("days", (streakMax - 1)).format("MMM DD YYYY"); | |
document.getElementById("max-streak-end").innerHTML = moment(streakMaxEnd).format("MMM DD YYYY"); | |
document.getElementById("current-streak-num").innerHTML = streakCurrent + " Days"; | |
document.getElementById("current-streak-start").innerHTML = moment().subtract("days", (streakCurrent - 1)).format("MMM DD YYYY"); | |
document.getElementById("current-streak-end").innerHTML = moment().format("MMM DD YYYY"); | |
}); | |
} | |
/****** button clicked ******/ | |
$(".pure-button-primary").keypress(function (e) { | |
if (e.which == 13) { | |
$("#ac-spinner").show(); | |
getData(); | |
} | |
}); | |
$(".pure-button-primary").click(function () { | |
$("#ac-spinner").show(); | |
getData(); | |
}); | |
/****** count occurrence of value, in this case earned dates ******/ | |
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; | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* resets */ | |
* { box-sizing: border-box; } | |
.clearfix:before, .clearfix:after { content: ""; display: table; } | |
.clearfix:after { clear: both; } | |
ol, li { padding: 0; margin: 0; list-style: none;} | |
/******/ | |
body { | |
background: #E0E0E0 url('http://subtlepatterns.com/patterns/creampaper.png'); | |
font-family: verdana, arial; | |
} | |
body, select { color: #525252; } | |
h1 { | |
font-size: 3em; | |
margin: 0 0 10px 10px; | |
font-weight: 600; | |
} | |
.container, .badges { | |
width: 900px; | |
background: #fff; | |
} | |
.container { | |
position: relative; | |
margin: 100px auto; | |
padding: 25px; | |
border-radius: 7px; | |
} | |
/*** form ***/ | |
form { margin-left: 5px; } | |
input { | |
margin: 10px; | |
height: 32px; | |
width: 250px; | |
padding-left: 10px; | |
} | |
.username { width: 225px; } | |
.timeframe-length { | |
width: 50px; | |
margin-right: 11px; | |
} | |
.container .pure-control-group label{ width: 200px; } | |
.container .pure-control-group label, .container select { font-size: 18px; } | |
.styled-select { display: inline-block; } | |
.styled-select, input { | |
border: 1px solid #ccc; | |
border-radius: 2px; | |
} | |
.pure-button-primary { | |
background: #64A7CE; | |
margin-left: 12px; | |
} | |
.pure-button-primary:focus { | |
outline: 2px solid goldenrod; | |
border-radius: 0; | |
} | |
/*** custom selectbox ***/ | |
.styled-select { | |
background: url(http://findicons.com/files/icons/2227/picol/32/arrow_sans_down_32.png) no-repeat 130px 8px #fff; | |
background-size: 20px; | |
width: 125px; | |
box-sizing: border-box; | |
} | |
.styled-select, .styled-select select { width: 158px; } | |
.styled-select select { | |
height: 35px; | |
padding: 0 5px; | |
background: transparent; | |
border: none; | |
-webkit-appearance: none; | |
} | |
.spinner { margin: 0 50%; } | |
/********* activity chart *********/ | |
h2 { | |
margin-left: 20px; | |
font-size: 1em; | |
font-weight: normal; | |
} | |
.days li, .day-key { background: #eee; } | |
.activity-chart { | |
position: relative; | |
height: 200px; | |
width: 790px; | |
margin: 50px auto 0 auto; | |
} | |
/*** month headings ***/ | |
.month ol { float: left; } | |
.month li:first-child{ margin-right: 0; } | |
.month li { | |
float: right; | |
margin-right: 37px; | |
font-size: 0.75em; | |
} | |
/*** day of week heading ***/ | |
.days-of-week { | |
width: 15px; | |
font-size: 0.7em; | |
margin: 0; | |
position: absolute; | |
top: 73px; | |
left: -6px; | |
} | |
.days-of-week li:nth-child(2){ margin: 15px 0; } | |
/*** draw days ***/ | |
/* 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 { | |
font-size: 0.75em; | |
position: absolute; | |
right: -45px; | |
top: 110px; | |
/*outline: solid;*/ | |
} | |
.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 ***/ | |
li .tooltip { | |
display: none; | |
font-size: 0.9em; | |
} | |
.days li:hover { | |
/*outline disabled due to firefox cross-broswer issue | |
outline: 1px solid #555;*/ | |
position: relative; | |
z-index: 3; | |
} | |
li:hover .tooltip { | |
display: block; | |
position: absolute; | |
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; | |
} | |
/*** days tooltips ***/ | |
.days li:hover .tooltip { | |
transform: rotate(-90deg); | |
/* top & left are reversed because the calendar is rotated 90 deg */ | |
left: -217px; /* actually top */ | |
width: 375px; | |
} | |
.days .tooltip:before { | |
bottom: -10px; | |
left: 195px; | |
border-left: 5px solid transparent; | |
border-right: 5px solid transparent; | |
border-top: 11px solid #333; | |
} | |
/********* display badges for time period *********/ | |
.badges { | |
padding-top: 25px; | |
margin-left: -25px; | |
border-bottom-left-radius: 10px; | |
border-bottom-right-radius: 10px; | |
} | |
.badges ul { | |
margin: auto; | |
width: 600px; | |
padding-bottom: 25px; | |
} | |
.badges li { | |
position: relative; | |
list-style-type: none; | |
display: inline-block; | |
width: 100px; | |
margin-right: 10px; | |
} | |
.badges li:nth-child(8n + 5) { margin-left: 55px; } | |
.badges img { width: 100%; } | |
.badges img:hover { | |
position: relative; | |
z-index: 2; | |
transform: scale(1.5); | |
} | |
.badges li:hover .tooltip { | |
top: 15px; | |
left: -185px; | |
width: 150px; | |
min-height: 40px; | |
} | |
.badges li:hover .tooltip:before { | |
top: 25%; | |
right: -10px; | |
border-top: 11px solid transparent; | |
border-bottom: 11px solid transparent; | |
border-left: 11px solid #333; | |
} | |
.badges li, .badges img { | |
margin: 0 10px 0 0; | |
padding: 0; | |
} | |
/****** stats ******/ | |
.stats { | |
width: 750px; | |
margin: auto; | |
} | |
.stats-box { | |
float: left; | |
width: 230px; | |
margin: 10px; | |
padding: 15px 10px; | |
background: #ccc; | |
text-align: center; | |
} | |
.stats-box-title { font-weight: bold; } | |
.stats-box p { margin: 0; } | |
.stats-box .date { | |
font-style: italic; | |
font-size: 0.75em; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment