Skip to content

Instantly share code, notes, and snippets.

@NelsonMinar
Created September 10, 2011 00:57
Show Gist options
  • Save NelsonMinar/1207745 to your computer and use it in GitHub Desktop.
Save NelsonMinar/1207745 to your computer and use it in GitHub Desktop.
Month selector demo
<!DOCTYPE html>
<html><head>
<title>Month control</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
<script type="text/javascript" src="month-selector.js"></script>
<style type="text/css">
#monthControl { font: 16px sans-serif; }
#monthControl g.month { cursor: pointer; }
#monthControl g.month>path { fill: #ccc; stroke: black; stroke-width: 0.5px;}
#monthControl g.month>path.selected { fill: #ee3; }
#monthControl circle.center { fill: white; }
#monthControl g.month>text { text-anchor: middle; fill: #666; }
#monthControl circle.backdrop { fill: black; }
body { font-family: Helvetica, Arial, sans-serif; }
#display { font: 16px; font-family: Monaco, monospace; }
</style></head>
<body>
<div id="monthControlDiv"></div>
<div id="display"></div>
<div style="width: 15em;">
<p>A month selector control in D3, inspired by the
<a href="http://oakland.crimespotting.org/">CrimeSpotting</a> hour of day control.
Click each wedge to toggle the selection for that month.
Click the center to start playback.</p>
<p>By <a href="http://www.somebits.com/weblog/">Nelson Minar</a></p></div>
<script type="text/javascript">
// Hacky demo function for displaying the selection
function displaySelection(months) {
var t = "";
for (var i = 0; i < months.length; i++) {
t += months[i] ? "JFMAMJJASOND"[i] : "-";
}
d3.select("#display").text(t);
}
// Create the control
var control = new monthControl(displaySelection);
// And initialize our display to what is selected
displaySelection(control.selected());
// Install the control
control.install("#monthControlDiv");
</script>
</body></html>
// A radial selector for month of the year. Implemented in SVG using the D3 library.
// Inspired by CrimeSpotting's hour of day control.
// See http://bl.ocks.org/1207745 for an example of usage
// Copyright (C) 2011 Nelson Minar <[email protected]>
// Constructor: create a month control.
// Callback: called when the selection changes. Callback is passed the selected array
function monthControl(callback) {
var changeCallback = callback;
var selectedMonths = [true, true, true, true, true, true, true, true, true, true, true, true];
// Accessor for which months are currently selected. Returns an array of Booleans.
this.selected = function() { return selectedMonths; },
// Install the control in the named container. Container is a CSS selector, like "#monthSelector"
this.install = function(container) {
var firstMonthToggle = true;
// Toggle a month, updating state and changing style
function toggleMonth(oldState, month, element) {
if (firstMonthToggle) {
// The very first time the user clicks, erase the selection and set it to one month
firstMonthToggle = false;
selectedMonths = [false, false, false, false, false, false, false, false, false, false, false, false]
}
selectedMonths[month] = !selectedMonths[month];
updateSelectedMonths();
}
// Rotate the selection across all 12 months
function rotateSelection() {
selectedMonths.unshift(selectedMonths.pop());
updateSelectedMonths();
}
function updateSelectedMonths() {
d3.select("#monthControl").selectAll("g.month")
.data(selectedMonths);
d3.selectAll("#monthControl>g>path")
.attr("class", function(d, i) { return selectedMonths[i] ? "selected" : null });
if (changeCallback) { changeCallback(selectedMonths); }
}
var animationIntervalID = null;
function toggleAnimation() {
if (animationIntervalID != null) {
window.clearInterval(animationIntervalID);
animationIntervalID = null;
pause.attr("display", "none");
play.attr("display", null);
} else {
rotateSelection();
animationIntervalID = window.setInterval(rotateSelection, 1000);
play.attr("display", "none");
pause.attr("display", null)
}
}
// Actual installation code
// Create the SVG element
d3.selectAll(container)
.append("svg:svg")
.attr("width", "150").attr("height", "150")
// Create a G to hold the whole control
d3.select("#monthControlDiv>svg")
.append("svg:g").attr("id", "monthControl")
// Add a circle as a backdrop
d3.select("#monthControl").append("svg:circle")
.attr("class", "backdrop")
.attr("r", 71.5).attr("transform", "translate(75, 75)")
// Add a circle in the middle with a Fast Forward icon to act as a central button
d3.select("#monthControl")
.append("svg:g")
.on("click", toggleAnimation)
.on("touchmove", function() { d3.event.preventDefault(); })
.on("mousedown", function() { d3.event.preventDefault(); })
.append("svg:circle")
.attr("r", 20)
.attr("class", "center")
.attr("transform", "translate(75, 75)");
// Create some button controls as SVG drawings
// var ffwd = d3.select("#monthControl>g").append("svg:g").attr("transform", "translate(75, 75)");
// ffwd.append("svg:polygon").attr("points", "-8,-8 -8,8 0,0");
// ffwd.append("svg:polygon").attr("points", "2,-8 2,8 10,0");
var play = d3.select("#monthControl>g").append("svg:g").attr("transform", "translate(75, 75)");
play.append("svg:polygon").attr("points", "-6,-8, -6,8 9,0");
var pause = d3.select("#monthControl>g").append("svg:g").attr("transform", "translate(75, 75)");
pause.append("svg:rect").attr("x", -8).attr("y", -8).attr("height", "16").attr("width", "6");
pause.append("svg:rect").attr("x", 2).attr("y", -8).attr("height", "16").attr("width", "6");
pause.attr("display", "none")
// In the control's G, bind our data for selected months and great a bunch of Gs, one per month
d3.select("#monthControl").selectAll("g.month")
.data(selectedMonths)
.enter()
.append("svg:g")
.attr("class", "month")
// For each month, draw a wedge
d3.selectAll("g.month")
.attr("transform", "translate(75, 75)")
.append("svg:path")
.attr("d", d3.svg.arc()
.innerRadius(20).outerRadius(70)
.startAngle(function(d, i) { return i * Math.PI * 2 / 12 - Math.PI * 2 / 24 })
.endAngle(function(d, i) { return (i+1) * Math.PI * 2 / 12 - Math.PI * 2 / 24 }))
.attr("class", function(d, i) { return selectedMonths[i] ? "selected" : null });
// For each month, draw a label
d3.selectAll("g.month")
.append("svg:text")
.attr("transform", function(d, i) { return "rotate(" + (i * 30 - 90) + "), translate(58, 0), rotate(90)" })
.attr("dy", ".35em")
.text(function(d, i) { return ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"][i]});
// And bind a mouse click handler on the wedge to toggle state
d3.selectAll("g.month")
.on("mousedown", function() { d3.event.preventDefault(); })
.on("touchmove", function() { d3.event.preventDefault(); })
.on("click", function(d, i) { toggleMonth(d, i, d3.select(this)); });
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment