Skip to content

Instantly share code, notes, and snippets.

@cpudney
Created March 30, 2012 07:11
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save cpudney/2248382 to your computer and use it in GitHub Desktop.
Easter Sunday
const FIRST_YEAR = 325;
const LAST_YEAR = 3000;
const WIDTH = 960;
const HEIGHT = 500;
const INSETS = {'left': 40,
'right': 10,
'top': 10,
'bottom': 60};
const SCALES = {};
// Tick-mark length.
const TICK_MARK_LENGTH = 8;
// Create the chart once the window has loaded.
//
window.onload = function() {
// Get the data and chart it.
plotChart(getEasterSundayData());
var thisYear = new Date().getFullYear();
// Populate menu.
var menu = d3.select("#year_menu");
for (var year = FIRST_YEAR; year <= LAST_YEAR; year++) {
var option = menu.append("option").text(year);
if (year == thisYear) {
option.attr("selected", "selected");
highlightYear(thisYear);
}
}
};
// Chart the data set.
//
// data: the Easter Sunday data.
//
function plotChart(data) {
// Day extents.
var dayExtents = d3.extent(data, function(d) {
return d.doy;
});
dayExtents[1]++;
// Scales.
SCALES.x = d3.scale.linear()
.domain(dayExtents)
.range([INSETS.left, WIDTH - INSETS.left - INSETS.right]);
SCALES.y = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.count;
})])
.range([0, HEIGHT - INSETS.bottom - INSETS.top]);
// Create the root SVG element.
var svg = d3.select("#chart").append("svg")
.attr("width", WIDTH)
.attr("height", HEIGHT);
// Plot the bar chart.
plotBars(svg, data);
plotXAxis(svg, data);
plotYAxis(svg);
}
// Plot chart bars.
//
// svg: root SVG element.
// data: Easter Sunday data set.
//
function plotBars(svg, data) {
// Bar width, y offset.
var width = SCALES.x(1) - SCALES.x(0);
var yOffset = HEIGHT - INSETS.bottom;
svg.selectAll("rect.bar")
.data(data)
.enter().append("svg:rect")
.attr("class", "bar")
.attr("width", width)
.attr("x", function(d) {
return SCALES.x(d.doy);
})
.attr("y", function(d) {
return yOffset - SCALES.y(d.count);
})
.attr("height", function(d) {
return SCALES.y(d.count);
});
}
// Add x-axis annotation.
//
// svg: root SVG element.
// data: Easter Sunday data set.
//
function plotXAxis(svg, data) {
var tickStart = SCALES.y.range()[1] + INSETS.top;
var tickEnd = tickStart + TICK_MARK_LENGTH;
// X-axis.
svg.append("svg:line")
.attr("class", "xAxis")
.attr("x1", SCALES.x.range()[0])
.attr("x2", SCALES.x.range()[1])
.attr("y1", tickStart)
.attr("y2", tickStart);
// Tick marks.
svg.selectAll('line.xTick')
.data(SCALES.x.ticks(data.length))
.enter().append('svg:line')
.attr('class', 'xTick')
.attr('x1', function(d) {
return SCALES.x(d);
})
.attr('x2', function(d) {
return SCALES.x(d);
})
.attr('y1', tickStart)
.attr('y2', tickEnd);
// Labels.
svg.selectAll('text.xLabel')
.data(data)
.enter().append('svg:text')
.attr('class', 'xLabel')
.attr('x', function(d) {
return SCALES.x(d.doy + 0.5);
})
.attr('y', tickEnd)
.attr('dy', '0.0em')
.attr('transform', function(d) {
return 'rotate(90, ' + SCALES.x(d.doy + 0.5) + ', ' + tickEnd + ')';
})
.text(function(d) {
return d.text;
});
}
// Add y-axis annotation.
//
// svg: root SVG element.
//
function plotYAxis(svg) {
var tickStart = SCALES.x.range()[0];
var tickEnd = tickStart - TICK_MARK_LENGTH;
var yOffset = HEIGHT - INSETS.bottom;
// Y-axis.
svg.append("svg:line")
.attr("class", "yAxis")
.attr("x1", tickStart)
.attr("x2", tickStart)
.attr("y1", yOffset - SCALES.y.range()[0])
.attr("y2", yOffset - SCALES.y.range()[1]);
// Tick marks.
svg.selectAll('line.yTick')
.data(SCALES.y.ticks(10))
.enter().append('svg:line')
.attr('class', 'yTick')
.attr('x1', tickStart)
.attr('x2', tickEnd)
.attr('y1', function(d) {
return yOffset - SCALES.y(d);
})
.attr('y2', function(d) {
return yOffset - SCALES.y(d);
});
// Labels.
svg.selectAll('text.yLabel')
.data(SCALES.y.ticks(10))
.enter().append('svg:text')
.attr('class', 'yLabel')
.attr('x', tickEnd)
.attr('y', function(d) {
return yOffset - SCALES.y(d);
})
.attr('dy', '0.4em')
.attr("text-anchor", "end")
.text(function(d) {
return d;
});
}
// Highlight a year.
//
// year: the year to highlight.
//
function highlightYear(year) {
var easter = easterSunday(year);
easter.setYear(FIRST_YEAR);
var doy = dayOfYear(easter);
d3.selectAll("rect")
.style("fill", function(d) {
return d.doy == doy ? "skyblue" : "steelblue";
});
d3.selectAll("text.xLabel")
.style("font-weight", function(d) {
return d.doy == doy ? "bold" : "normal";
});
}
// Get the Easter Sunday data.
//
function getEasterSundayData() {
var dates = {};
var data = [];
for (var year = FIRST_YEAR;
year < LAST_YEAR;
year++) {
// Calculate Easter for year.
var date = easterSunday(year);
// Count dates.
date.setYear(FIRST_YEAR);
if (date in dates) {
data[dates[date]].count++;
}
else {
var record = {};
record.count = 1;
record.doy = dayOfYear(date);
record.text = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][date.getMonth()] + ' ' + date.getDate();
data.push(record);
dates[date] = data.length - 1;
}
}
return data;
}
// Determines the date of Easter Sunday for a given year.
// See: http://javascript.about.com/library/bleaster1.htm
//
// year: the year to calculate.
//
function easterSunday(year) {
var a = year % 19;
var b = Math.floor(year / 100);
var c = year % 100;
var h = (19 * a + b - Math.floor(b / 4) - Math.floor((b - Math.floor((b + 8) / 25) + 1) / 3) + 15) % 30;
var k = (32 + 2 * (b % 4) + 2 * Math.floor(c / 4) - h - c % 4) % 7;
var m = Math.floor((a + 11 * h + 22 * k) / 451);
var month = Math.floor((h + k - 7 * m + 114) / 31) - 1;
var day = ((h + k - 7 * m + 114) % 31) + 1;
return new Date(year, month, day)
}
// Seconds per day.
const SECONDS_PER_DAY = 86400000;
// Calculates the day of the year for a given date.
// date: the date to calculate.
//
function dayOfYear(date) {
return Math.ceil((date - new Date(date.getFullYear(), 0, 1)) / SECONDS_PER_DAY);
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>When is Easter Sunday?</title>
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<link href="style.css" media="screen" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.8.1"></script>
<script type="text/javascript" src="eastersunday.js"></script>
</head>
<body>
<h1>When is Easter Sunday?</h1>
<p><label for="year_menu">Year</label>
<select id="year_menu" name="year_list" onchange="highlightYear(this.value);"></select>
</p>
<div id="chart"></div>
<p class="attrib">By <a href="http://vislives.com/" target="_blank">Chris Pudney</a>
<a href="http://creativecommons.org/licenses/by-sa/3.0/" target="_blank"><img align="top"
alt="Creative Commons License"
src="http://i.creativecommons.org/l/by-sa/3.0/80x15.png"/></a>
<a href="https://gist.github.com/2248382" target="_blank" title="Hosted on GitHub">Source Code</a>
</p>
</body>
</html>
body {
margin: 2;
padding: 0;
background-color: #000000;
font-family: sans-serif;
font-size: 12px;
color: white;
overflow: hidden;
}
a:link {
color: #ccccff;
}
a:visited {
color: #cccccc;
}
a:active {
color: #ffccff;
}
a.hover {
color: #ffcccc;
}
rect {
fill: steelblue;
stroke: white;
shape-rendering: crispEdges;
}
line {
stroke: white;
shape-rendering: crispEdges;
}
text {
fill: white;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment