Skip to content

Instantly share code, notes, and snippets.

@emmasaunders
Last active August 6, 2016 17:52
Show Gist options
  • Save emmasaunders/75ce6bde05c4bd94771f to your computer and use it in GitHub Desktop.
Save emmasaunders/75ce6bde05c4bd94771f to your computer and use it in GitHub Desktop.
Spiral calendar (v3)

##Explanation This d3 spiral calendar block is based on the spiral created for John Hunter.

  • Progressively lighter blue/grey-filled circles indicate the year, starting October 2015 and ending December 2017.
  • The thin blue/grey spiral indicates the progression of time. The coloured partial spirals indicate periods of time, which are defined in the periods array.
  • The small white circles with the coloured border on top of the periods indicate particular days, or events, as defined in the specialDays array.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Calendar spiral in d3.js</title>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js"></script>
<style>
body {
font-family: 'Helvetica', sans-serif;
margin: 0px;
}
.axis path {
fill: none;
stroke: white;
stroke-width: 1px;
}
text.months {
font-size: 13px;
font-weight: 100;
stroke: lightslategray;
}
text.title {
font-size: 24px;
}
circle.tick {
fill: rgba(70,130,180,0.2);
stroke: #999;
stroke-dasharray: 2 3;
}
path.spiral {
fill: none;
stroke: lightslategray;
stroke-width: 1px;
}
path.spiral2 {
fill: none;
stroke: #79F347;
stroke-width: 5px;
}
path.spiral1 {
fill: none;
stroke: #E94685;
stroke-width: 5px;
}
path.spiral0 {
fill: none;
stroke: #419FF7;
stroke-width: 5px;
}
path.spiral3 {
fill: none;
stroke: #7941F7;
stroke-width: 5px;
}
circle.dot {
fill: white;
stroke-width: 1px;
}
circle.dot0 {
stroke: #419FF7;
}
circle.dot1 {
stroke: #E94685;
}
circle.dot2 {
stroke: #79F347;
}
circle.dot3 {
stroke: #7941F7;
}
text.year {
fill: white;
opacity: 0.8;
font-size: 40px;
font-weight: 400;
}
</style>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="spiral6.js"></script>
</body>
</html>
var width = 400,
height = 430,
num_axes = 12,
tick_axis = 9,
start = 0,
firstJan = 0.25,
end = 2.25,
startDate = new Date(2015,10,1);
var theta = function(r) {
return 2*Math.PI*r;
};
var months = ['Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar'];
var r = d3.min([width,height])/2-40;
var r2 = r;
var radius = d3.scale.linear()
.domain([start, end])
.range([0, r]);
var angle = d3.scale.linear()
.domain([0,num_axes])
.range([0,360])
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + (height/2+8) +")");
var pieces = d3.range(start, end+0.001, (end-start)/1000);
var spiral = d3.svg.line.radial()
.interpolate("cardinal")
.angle(theta)
.radius(radius);
svg.selectAll("circle.tick")
.data(d3.range(firstJan,(end+0.001),1))
.enter().append("circle")
.attr("class", "tick")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d) { return radius(d); });
svg.selectAll(".axis")
.data(d3.range(num_axes))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + -angle(d) + ")"; })
.call(radial_tick);
svg.selectAll("text.year")
.data(['2016','2017'])
.enter()
.append("text")
.attr("x","0")
.attr("y",function(d,i){ return (i*-80)-35; })
.attr("class","year")
.attr("text-anchor","middle")
.text(function(d){ return d; });
svg.selectAll("text.months")
.data(d3.range(num_axes))
.enter().append("text")
.attr("class","months")
.attr("y", radius(end)+13)
.text(function(d,i) { return months[i]; })
.attr("text-anchor", "middle")
.attr("transform", function(d,i) {
var tempX = Math.round(1.15*(r * Math.cos((angle(d)/180)*Math.PI)));
var tempY = Math.round(1.1*(r * Math.sin((angle(d)/180)*Math.PI))) - r - 6;
return "translate(" + tempX + ", " + tempY + ")";
});
svg.selectAll(".spiral")
.data([pieces])
.enter().append("path")
.attr("class", "spiral")
.attr("d", spiral)
.attr("transform", function(d) { return "rotate(" + (-90) + ")" });//starts in October
var periods = [];
periods[0] = {startDay:6, startMth:4, startYear:2016, endDay:31, endMth:1, endYear:2017};
periods[1] = {startDay:1, startMth:6, startYear:2016, endDay:1, endMth:3, endYear:2017};
periods[2] = {startDay:1, startMth:6, startYear:2016, endDay:1, endMth:6, endYear:2017};
periods[3] = {startDay:8, startMth:10, startYear:2016, endDay:5, endMth:11, endYear:2016};
var specialDays = [];
specialDays[0] = { day: 1, month: 6, year: 2016, ref:2 };
specialDays[1] = { day: 2, month: 3, year: 2017, ref:2 };
specialDays[2] = { day: 1, month: 6, year: 2017, ref:2};
specialDays[3] = { day: 1, month: 6, year: 2016, ref:1};
specialDays[4] = { day: 1, month: 3, year: 2017, ref:1};
specialDays[5] = { day: 6, month: 4, year: 2016, ref:0};
specialDays[6] = { day: 31, month: 1, year: 2017, ref:0};
specialDays[7] = { day: 8, month: 10, year: 2016, ref:3};
specialDays[8] = { day: 5, month: 11, year: 2016, ref:3};
for (var p=0; p<periods.length; p++) {
var date1 = new Date(periods[p].startYear, periods[p].startMth, periods[p].startDay);
var date2 = new Date(periods[p].endYear, periods[p].endMth, periods[p].endDay);
var timeDiff1 = Math.abs(date1.getTime() - startDate.getTime());
var diffDays1 = (Math.ceil(timeDiff1 / (1000 * 3600 * 24)))/365;
var timeDiff2 = Math.abs(date2.getTime() - startDate.getTime());
var diffDays2 = (Math.ceil(timeDiff2 / (1000 * 3600 * 24)))/365;
var pieces2 = d3.range(diffDays1, diffDays2+0.001, (diffDays2 - diffDays1)/1000);
(p==0) ? r2 = r * 1.025 : r2 = r * 1.06 ;
r = r2;
var theta2 = function(r2) {
return 2*Math.PI*r2;
};
var radius2 = d3.scale.linear()
.domain([start, end])
.range([0, r2]);
var spiral2 = d3.svg.line.radial()
.interpolate("cardinal")
.angle(theta2)
.radius(radius2);
svg.selectAll(".spiral"+p)
.data([pieces2])
.enter().append("path")
.attr("class", "spiral"+p)
.attr("d", spiral2)
.attr("transform", function(d) { return "rotate(" + (-90) + ")" });//starts in October
for (var q=0; q<specialDays.length; q++) {//specialDays.length
if (specialDays[q].ref==p) {
var date1 = new Date(specialDays[q].year, specialDays[q].month, specialDays[q].day);
var timeDiff1 = Math.abs(date1.getTime() - startDate.getTime());
var diffDays1 = (Math.ceil(timeDiff1 / (1000 * 3600 * 24)))/365;
var rad = radius2(diffDays1);
var ang = angle(specialDays[q].month + ((specialDays[q].day-1)/30))-30-90;
var x = rad * Math.cos((ang/180)*Math.PI);
var y = rad * Math.sin((ang/180)*Math.PI);
svg.append("circle").attr("cx",x).attr("cy",y).attr("r","3").attr("class","dot dot"+p);
}
}
}
function radial_tick(selection) {
selection.each(function(axis_num) {
d3.svg.axis()
.scale(radius)
.ticks(3)
.tickValues( axis_num == tick_axis ? [] : [])
.orient("top")(d3.select(this))
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment