Skip to content

Instantly share code, notes, and snippets.

@saraquigley
Last active September 2, 2017 18:51
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 saraquigley/b926071f6187335c5d2f233c8fe66892 to your computer and use it in GitHub Desktop.
Save saraquigley/b926071f6187335c5d2f233c8fe66892 to your computer and use it in GitHub Desktop.
the enrollment cycle
license: mit
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href='http://fonts.googleapis.com/css?family=Lato:300,400,700' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Sans:200,300,400,700' rel='stylesheet' type='text/css'>
<title>Calendar spiral in d3.js</title>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js"></script>
<style>
body {
font-family: 'PT Sans', sans-serif;
/* margin: auto; */
}
.axis path {
fill: none;
stroke: #999;
stroke-width: 1px;
stroke-dasharray: 2 3;
}
text.months {
font-size: 13px;
font-weight: 100;
fill: lightslategray;
}
text.title {
font-size: 24px;
}
circle.tick {
/*fill: rgba(153, 153, 153, 0.2);*/
stroke: #999;
stroke-dasharray: 2 3;
}
path.spiral {
fill: none;
stroke: white;
stroke-width: 1px;
}
path.spiral0,
path.spiral201 {
fill: none;
stroke: #abd9e9;
stroke-width: 14px;
}
path.spiral1,
path.spiral211 {
fill: none;
stroke: #74add1;
stroke-width: 14px;
}
path.spiral2,
path.spiral221 {
fill: none;
stroke: #4575b4;
stroke-width: 14px;
}
path.spiral3,
path.spiral231 {
fill: none;
stroke: #313695;
stroke-width: 14px;
}
path.spiral4,
path.spiral24 {
fill: none;
stroke: #66bd63;
stroke-width: 14px;
}
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: #525252;
/* opacity: 0.5;*/
font-size: 32px;
font-weight: 800;
}
text.labelText2, text.labelText3,
text.labelText22, text.labelText23,
text.labelText4, text.labelText24
{
fill: #eee;
font-size: 13px;
}
text.labelText0, text.labelText1,
text.labelText20, text.labelText21 {
fill: #eee;
font-size: 13px;
}
.termText {
fill: #888;
font-size: 16px;
}
#chart {
margin-top: 300px;
margin-left: -10px;
}
</style>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="spiral6.js"></script>
</body>
</html>
var width = 900,
height = 1000,
num_axes = 12,
tick_axis = 9,
start = 0,
firstJan = 0.25,
end = 2.35,
startDate = new Date(2016,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 labels1 = ['Targets','Admits','SIR/Melt Tracking','Est. YAHC from Fall Actuals/Spring Forecast','YAHC → ']
var labels2 = ['Target Setting with UCOP','New Admits Notified','Estimated YAHC using Admit SIR and Melt Data',
' Estimated YAHC is based on Fall Actuals and Forecasted Spring Headcounts','Actual YAHC → '];
var r = d3.min([width,height])/2-100;
var r2 = r;
var radius = d3.scale.linear()
.domain([start, end])
.range([0, r]);
var radius_axis = d3.scale.linear()
.domain([start, end])
.range([0, r+34]);
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)+50) + "," + ((height/2.1)+8) +")");
svg.append("rect")
.attr("width", width-108)
.attr("height", height-8)
// .style("stroke","#000")
.style("stroke","none") // switch these two to show the bounding box for taking snapshots
.style("fill","none")
.attr("transform", "translate(" + (-(width/2)+50) + "," + (-(height/2.1)) +")");
// -- -----------------------------------------------------------
// add the donut chart for terms
// -- -----------------------------------------------------------
var termdata = [{"name": "", "value": 3},
{"name": "Spring Term", "value": 18},
{"name": "", "value": 1},
{"name": "Summer Session", "value": 8},
{"name": "", "value": 1},
{"name": "Fall Term", "value": 18},
{"name": "", "value": 3}
];
var color2 = d3.scale.ordinal()
.range(["#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"]);
var arc = d3.svg.arc()
.outerRadius(348)
.innerRadius(338);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var g = svg.selectAll(".arc")
.data(pie(termdata))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", "url(#whitecarbon)")
// .attr("class", "lightstripe")
.attr("id", function(d,i) {return "arc" + i;})
.style("fill-opacity", function(d) { return d.data.name === "" ? 0 : 0.75; });
// g.append("text")
// .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
// .attr("dy", ".35em")
// .text(function(d) { return d.data.name; });
g.selectAll(".termText")
.data(pie(termdata))
.enter().append("text")
// .attr("x",100)
// .attr("x",function(d,i) { return (d.endAngle - d.startAngle)/2; })
// .attr("dy", function(d,i) { return (d.endAngle > 90 * Math.PI/180 ? 18 : -11); })
.attr("dy", -8)
.attr("class", "termText")
.append("textPath")
// .attr("startOffset","50%")
.style("text-anchor","start")
.attr("xlink:href",function(d,i){return "#arc" + i;})
.text(function(d) { return d.data.name;} );
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("fill","#525252")
.attr("fill-opacity",function(d,i) {return (i+1)/(12*i);})
.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(['','Year 1','Year 2','Year 3'])
.enter()
.append("text")
.attr("x","0")
.attr("y",function(d,i){ return (i*-100)+56; })
.style("fill-opacity", function(d,i) {return 1/(i+1.5);})
.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.15*(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 year1 = [];
year1[0] = {startDay:1, startMth:10, startYear:2016, endDay:20, endMth:2, endYear:2017};
year1[1] = {startDay:21, startMth:2, startYear:2017, endDay:15, endMth:6, endYear:2017};
year1[2] = {startDay:16, startMth:6, startYear:2017, endDay:15, endMth:10, endYear:2017};
year1[3] = {startDay:16, startMth:10, startYear:2017, endDay:15, endMth:3, endYear:2018};
year1[4] = {startDay:16, startMth:3, startYear:2018, endDay:12, endMth:4, endYear:2018};
var year2 = [];
year2[0] = {startDay:1, startMth:10, startYear:2017, endDay:20, endMth:2, endYear:2018};
year2[1] = {startDay:21, startMth:2, startYear:2018, endDay:15, endMth:6, endYear:2018};
year2[2] = {startDay:16, startMth:6, startYear:2018, endDay:15, endMth:10, endYear:2018};
year2[3] = {startDay:16, startMth:10, startYear:2018, endDay:15, endMth:3, endYear:2019};
year2[4] = {startDay:16, startMth:3, startYear:2019, endDay:12, endMth:4, endYear:2019};
var specialDays = [];
// specialDays[0] = { day: 1, month: 11, 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: 2017, ref:0};
// specialDays[6] = { day: 31, month: 1, year: 2017, ref:0};
// specialDays[7] = { day: 8, month: 10, year: 2017, ref:3};
// specialDays[8] = { day: 5, month: 11, year: 2017, ref:3};
for (var p=0; p<year1.length; p++) {
var date1 = new Date(year1[p].startYear, year1[p].startMth, year1[p].startDay);
var date2 = new Date(year1[p].endYear, year1[p].endMth, year1[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 * 0.90 : r2 = r * 1.0 ;
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)
.style("stroke", color2(p))
.style("fill", "none")
.attr("id", "spiral"+p) //Give each slice a unique ID
.attr("transform", function(d) { return "rotate(" + (-90) + ")" });//starts in October
svg.selectAll(".labelText"+p)
.data([pieces2])
.enter().append("text")
.attr("x",3)
.attr("dy",4)
.attr("class", "labelText"+p)
.append("textPath")
.attr("xlink:href",function(d,i){return "#spiral"+p;})
.text(labels1[p]);
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);
}
}
}
var r = d3.min([width,height])/2-60;
var r2 = r;
for (var p=0; p<year2.length; p++) {
var date1 = new Date(year2[p].startYear, year2[p].startMth, year2[p].startDay);
var date2 = new Date(year2[p].endYear, year2[p].endMth, year2[p].endDay);
var color = d3.scale.category10();
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 * 0.90 : r2 = r * 1.0 ;
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(".spiral2"+p)
.data([pieces2])
.enter().append("path")
.attr("class", "spiral2"+p)
.style("stroke", color2(p))
.style("stroke-width",16)
.style("fill","none")
.attr("d", spiral2)
.attr("id", "spiral2"+p) //Give each slice a unique ID
.attr("transform", function(d) { return "rotate(" + (-90) + ")" });//starts in October
svg.selectAll(".labelText2"+p)
.data([pieces2])
.enter().append("text")
.attr("x",3)
.attr("dy",4)
.attr("class", "labelText2"+p)
.append("textPath")
.attr("xlink:href",function(d,i){return "#spiral2"+p;})
.text(labels2[p]);
}
function radial_tick(selection) {
selection.each(function(axis_num) {
d3.svg.axis()
.scale(radius_axis)
.ticks(4)
.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