Skip to content

Instantly share code, notes, and snippets.

@mph006
Last active March 2, 2016 02:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mph006/b8c8161390001dbf4e99 to your computer and use it in GitHub Desktop.
Save mph006/b8c8161390001dbf4e99 to your computer and use it in GitHub Desktop.
Basic Interactive Calendar
body{
font-family: Helvetica;
}
#calendar-title{
display: block;
position: relative;
width: 70%;
margin-left: 15%;
text-align: center;
border-bottom: thin solid black;
margin-top: 10px;
margin-bottom: 30px;
padding-bottom: 10px;
font-size: 20px;
}
#calendar-wrapper{
position: relative;
display: block;
height: 300px;
width:90%;
margin-left: 5%;
}
#graph-picklist-wrapper{
text-align: center;
}
#calendar-click-info{
position: absolute;
display: inline-block;
width: 66%;
text-align: center;
font-size: 18px;
bottom: 30px;
}
#calendar-legend{
position: absolute;
display: inline-block;
min-width:200px;
text-align: center;
font-size: 15px;
left:73%;
bottom: 30px;
}
.month-path{
fill: none;
stroke: black;
stroke-width: 5px;
}
.day {
stroke: black;
stroke-width:1px;
}
.month-label{
stroke:white;
}
.legend-box{
position: relative;
display: inline-block;
height: 20px;
width: 20px;
margin: 5px 5px -4px 5px;
}
#legend-box-white{
background-color: #b5cde1;
border:thin solid black;
}
#legend-box-light{
background-color: #588EBB;
border:thin solid black;
}
#legend-box-dark{
background-color: #386890;
border:thin solid black;
}
var today = new Date();
var animDuration =1000,
min = 0,
max=100;
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
d3.selection.prototype.moveToBack = function() {
return this.each(function() {
var firstChild = this.parentNode.firstChild;
if (firstChild) {
this.parentNode.insertBefore(this, firstChild);
}
});
};
function numberWithCommas(x) {
x = x.toString();
var pattern = /(-?\d+)(\d{3})/;
while (pattern.test(x))
x = x.replace(pattern, "$1,$2");
return x;
}
//http://stackoverflow.com/questions/10638529/how-to-parse-a-date-in-format-yyyymmdd-in-javascript
function dateParser(str) {
var y = str.substr(0,4),
m = str.substr(4,2) - 1,
d = str.substr(6,2);
var D = new Date(y,m,d);
return (D.getFullYear() == y && D.getMonth() == m && D.getDate() == d) ? D : 'invalid date';
}
//http://stackoverflow.com/questions/1144783/replacing-all-occurrences-of-a-string-in-javascript
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.replace(new RegExp(search, 'g'), replacement);
};
//http://stackoverflow.com/questions/1527803/generating-random-numbers-in-javascript-in-a-specific-range
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function prettyDate(dateObj){
var newObj = new Date(dateObj);
var day = newObj.getDate();
var monthIndex = newObj.getMonth();
var year = newObj.getFullYear();
var monthNames = ["January", "February", "March","April", "May", "June", "July","August", "September", "October","November", "December"];
return monthNames[monthIndex]+" "+day+", "+year;
}
function generateAttendeeData(min,max){
var returnArray=[];
var datePointer = new Date();
//off by 1 error here, not sure why
datePointer = new Date(datePointer.setDate(datePointer.getDate()+1));
while(parseInt(datePointer.getFullYear()) === parseInt(today.getFullYear())){
//Weekend values are less
if(datePointer.getDay() === 0 || datePointer.getDay() === 1){
returnArray.push({
date:datePointer,
hours:getRandomInt(0,getRandomInt(min,max/5))
});
}
else{
returnArray.push({
date:datePointer,
hours:getRandomInt(min,max)
});
}
datePointer = new Date(datePointer.setDate(datePointer.getDate()-1));
}
return returnArray;
}
function clickedDay(d){
d3.selectAll(".day")
.transition()
.duration(animDuration/5)
.style("fill",function(d){
var boxData = $("#box-id-"+dateParser(d).toDateString().replaceAll(" ","")).data();
var sel = d3.select(this);
sel.moveToBack();
return boxData.fill;
})
.style("opacity",function(d){
var boxData = $("#box-id-"+dateParser(d).toDateString().replaceAll(" ","")).data();
return boxData.opacity;
});
var sel =d3.select(this);
sel.moveToFront();
d3.select("#box-id-"+dateParser(d).toDateString().replaceAll(" ",""))
.transition()
.duration(animDuration/3)
.style("fill","#82B446")
.style("opacity","1");
var boxData = $("#box-id-"+dateParser(d).toDateString().replaceAll(" ","")).data();
if(boxData.value !== undefined){
$("#calendar-click-info").text(prettyDate(boxData.date)+" has a value of "+boxData.value+" units");
}
else{
$("#calendar-click-info").text("No information present for "+prettyDate(boxData.date));
}
}
function animateColorTransition(color,nestData){
d3.selectAll(".day")
.transition()
.duration(animDuration)
.delay(function(d,i){return i*20;})
.style("fill", function(d) {
return (color(nestData[dateParser(d).toDateString()])!=="#NaNNaNNaN")?
color(nestData[dateParser(d).toDateString()]) :
"#000000";
})
.attr("data-fill",function(d){
return (color(nestData[dateParser(d).toDateString()])!=="#NaNNaNNaN")?
color(nestData[dateParser(d).toDateString()]) :
"#000000";
})
.style("opacity",function(d){
return (color(nestData[dateParser(d).toDateString()])!=="#NaNNaNNaN")?
"1":
"0.25";
})
.attr("data-opacity",function(d){
return (color(nestData[dateParser(d).toDateString()])!=="#NaNNaNNaN")?
"1":
"0.25";
});
}
function renderCalendar(){
var width = $("#calendar-wrapper").width(),
height = $("#calendar-wrapper").height(),
cellSize = parseInt($(window).width()/62),
week_days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
month = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var day = d3.time.format("%w"),
week = d3.time.format("%U"),
percent = d3.format(".1%"),
format = d3.time.format("%Y%m%d");
parseDate = d3.time.format("%Y%m%d").parse;
var color = d3.scale.linear()
.range(["#b5cde1", '#386890'])
.domain([min, max])
var svg = d3.select("#calendar-wrapper").selectAll("svg")
.data(d3.range(parseInt(today.getFullYear()), parseInt(today.getFullYear())+1))
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("id","total-wrapper")
.attr("transform", "translate(" + ((width - cellSize * 53) / 2) + "," + (30) + ")");
for (var i=0; i<7; i++)
{
svg.append("text")
.attr("transform", "translate(8," + cellSize*(i+1) + ")")
.style("text-anchor", "end")
.attr("dy", "-.25em")
.style("stroke","black")
.style("font-size","12px")
.style("font-weight","100")
.text(function(d) { return week_days[i]; });
}
var legendWrapper = d3.select("#total-wrapper")
.append("g")
.attr("id","legend-wrapper")
.attr("transform","translate(80,-10)");
var legend = legendWrapper.selectAll(".legend")
.data(month)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(" + (((cellSize*4.3)*i)+(width/100)) + ",0)"; });
legend.append("text")
.attr("class","month-label")
.attr("id", function(d,i){ return month[i] })
.style("text-anchor", "end")
.style("font-size","12px")
.style("font-weight","100")
.style("stroke","black")
.text(function(d,i){ return month[i] });
var generatedData = generateAttendeeData(min,max);
var nestData = d3.nest()
.key(function(d) {return d.date.toDateString();})
.rollup(function(d){return d[0].hours})
.map(generatedData);
var rectWrapper = d3.select("#total-wrapper")
.append("g")
.attr("id","blocks-wrapper")
.attr("transform","translate(30,0)");
var rect = rectWrapper.selectAll(".day")
.data(function(d) { return d3.time.days(new Date(d, 0, 1), new Date(d, month.length, 1)); })
.enter()
.append("rect")
.attr("class", "day")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("x", function(d) { return week(d) * cellSize; })
.attr("y", function(d) { return day(d) * cellSize; })
.style("fill","#000000")
.style("opacity","0.25")
.attr("id",function(d){return "box-id-"+d.toDateString().replaceAll(" ","");})
.attr("data-value", function(d){return nestData[d.toDateString()];})
.attr("data-date",function(d){return d;})
.attr("data-fill","#000000")
.attr("data-opacity","0.25")
.datum(format)
.on("click", clickedDay);
animateColorTransition(color,nestData);
//Draws the month boundaries
rectWrapper.selectAll(".month")
.data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d, month.length, 1)); })
.enter().append("path")
.attr("class", "month-path")
.attr("id", function(d,i){ return month[i] })
.attr("d", monthPath);
//Thank you Mike Bostock
function monthPath(t0) {
var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
d0 = +day(t0), w0 = +week(t0),
d1 = +day(t1), w1 = +week(t1);
return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
+ "H" + w0 * cellSize + "V" + 7 * cellSize
+ "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
+ "H" + (w1 + 1) * cellSize + "V" + 0
+ "H" + (w0 + 1) * cellSize + "Z";
}
}
$( document ).ready(function() {
renderCalendar();
$("#calendar-title").text("Basic Interactive D3 Calendar - "+today.getFullYear());
});
<!DOCTYPE html>
<html>
<head>
<title>D3 Calendar</title>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="calendar.css"/>
</head>
<body>
<div id="calendar-title">Basic D3 Interactive Calendar</div>
<div id="calendar-wrapper">
<div id="calendar-click-info">Click on a Sqare For Details</div>
<div id="calendar-legend">Less
<div class="legend-box" id="legend-box-white"></div>
<div class="legend-box" id="legend-box-light"></div>
<div class="legend-box" id="legend-box-dark"></div> More
</div>
</div>
</body>
<script type="text/javascript" src="calendar.js"></script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment