Skip to content

Instantly share code, notes, and snippets.

@officeofjane
Last active April 16, 2023 10:00
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save officeofjane/47d2b0bfeecfcb41d2212d06d095c763 to your computer and use it in GitHub Desktop.
Save officeofjane/47d2b0bfeecfcb41d2212d06d095c763 to your computer and use it in GitHub Desktop.
Date slider - with play/pause button
license: mit
id date
1 11/24/04
2 03/22/05
3 06/02/05
4 06/14/05
5 07/01/05
6 08/31/05
7 09/01/05
8 10/01/05
9 11/14/05
10 12/09/05
11 02/14/06
12 04/06/06
13 05/26/06
14 06/14/06
15 07/07/06
16 08/03/06
17 09/14/06
18 10/25/06
19 11/20/06
20 12/21/06
21 01/23/07
22 01/30/07
23 02/25/07
24 03/16/07
25 04/13/07
26 05/24/07
27 06/16/07
28 07/23/07
29 08/13/07
30 09/06/07
31 10/19/07
32 11/12/07
33 12/11/07
34 01/01/08
35 02/06/08
36 03/01/08
37 04/01/08
38 06/17/08
39 07/03/08
40 09/18/08
41 10/08/08
42 11/19/08
43 12/18/08
44 01/20/09
45 02/13/09
46 03/20/09
47 04/10/09
48 07/20/09
49 08/10/09
50 09/15/09
51 10/19/09
52 11/06/09
53 12/17/09
54 02/16/10
55 03/14/10
56 04/14/10
57 05/05/10
58 06/19/10
59 07/01/10
60 08/24/10
61 09/01/10
62 10/19/10
63 11/06/10
64 12/27/10
65 01/18/11
66 02/22/11
67 03/11/11
68 04/26/11
69 05/16/11
70 07/14/11
71 09/19/11
72 11/16/11
73 03/28/12
74 04/19/12
75 05/04/12
76 07/19/12
77 08/10/12
78 09/16/12
79 10/21/12
80 11/15/12
81 12/03/12
82 01/15/13
83 03/24/13
84 04/17/13
85 05/13/13
86 06/07/13
87 07/03/13
88 08/23/13
89 09/22/13
90 10/23/13
91 11/14/13
92 12/06/13
93 02/04/14
94 04/15/14
95 07/03/14
96 08/05/14
97 09/19/14
98 10/28/14
99 11/05/14
100 12/19/14
101 02/06/15
102 03/01/15
103 04/10/15
104 05/18/15
105 06/29/15
106 08/20/15
107 09/19/15
108 10/22/16
109 12/13/16
110 02/16/17
111 03/24/17
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
font-family:"avenir next", Arial, sans-serif;
font-size: 12px;
color: #696969;
}
#play-button {
position: absolute;
top: 140px;
left: 50px;
background: #f08080;
padding-right: 26px;
border-radius: 3px;
border: none;
color: white;
margin: 0;
padding: 0 12px;
width: 60px;
cursor: pointer;
height: 30px;
}
#play-button:hover {
background-color: #696969;
}
.ticks {
font-size: 10px;
}
.track,
.track-inset,
.track-overlay {
stroke-linecap: round;
}
.track {
stroke: #000;
stroke-opacity: 0.3;
stroke-width: 10px;
}
.track-inset {
stroke: #dcdcdc;
stroke-width: 8px;
}
.track-overlay {
pointer-events: stroke;
stroke-width: 50px;
stroke: transparent;
cursor: crosshair;
}
.handle {
fill: #fff;
stroke: #000;
stroke-opacity: 0.5;
stroke-width: 1.25px;
}
</style>
</head>
<body>
<div id="vis">
<button id="play-button">Play</button>
</div>
<script>
var formatDateIntoYear = d3.timeFormat("%Y");
var formatDate = d3.timeFormat("%b %Y");
var parseDate = d3.timeParse("%m/%d/%y");
var startDate = new Date("2004-11-01"),
endDate = new Date("2017-04-01");
var margin = {top:50, right:50, bottom:0, left:50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("#vis")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
////////// slider //////////
var moving = false;
var currentValue = 0;
var targetValue = width;
var playButton = d3.select("#play-button");
var x = d3.scaleTime()
.domain([startDate, endDate])
.range([0, targetValue])
.clamp(true);
var slider = svg.append("g")
.attr("class", "slider")
.attr("transform", "translate(" + margin.left + "," + height/5 + ")");
slider.append("line")
.attr("class", "track")
.attr("x1", x.range()[0])
.attr("x2", x.range()[1])
.select(function() { return this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "track-inset")
.select(function() { return this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "track-overlay")
.call(d3.drag()
.on("start.interrupt", function() { slider.interrupt(); })
.on("start drag", function() {
currentValue = d3.event.x;
update(x.invert(currentValue));
})
);
slider.insert("g", ".track-overlay")
.attr("class", "ticks")
.attr("transform", "translate(0," + 18 + ")")
.selectAll("text")
.data(x.ticks(10))
.enter()
.append("text")
.attr("x", x)
.attr("y", 10)
.attr("text-anchor", "middle")
.text(function(d) { return formatDateIntoYear(d); });
var handle = slider.insert("circle", ".track-overlay")
.attr("class", "handle")
.attr("r", 9);
var label = slider.append("text")
.attr("class", "label")
.attr("text-anchor", "middle")
.text(formatDate(startDate))
.attr("transform", "translate(0," + (-25) + ")")
////////// plot //////////
var dataset;
var plot = svg.append("g")
.attr("class", "plot")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("circles.csv", prepare, function(data) {
dataset = data;
drawPlot(dataset);
playButton
.on("click", function() {
var button = d3.select(this);
if (button.text() == "Pause") {
moving = false;
clearInterval(timer);
// timer = 0;
button.text("Play");
} else {
moving = true;
timer = setInterval(step, 100);
button.text("Pause");
}
console.log("Slider moving: " + moving);
})
})
function prepare(d) {
d.id = d.id;
d.date = parseDate(d.date);
return d;
}
function step() {
update(x.invert(currentValue));
currentValue = currentValue + (targetValue/151);
if (currentValue > targetValue) {
moving = false;
currentValue = 0;
clearInterval(timer);
// timer = 0;
playButton.text("Play");
console.log("Slider moving: " + moving);
}
}
function drawPlot(data) {
var locations = plot.selectAll(".location")
.data(data);
// if filtered dataset has more circles than already existing, transition new ones in
locations.enter()
.append("circle")
.attr("class", "location")
.attr("cx", function(d) { return x(d.date); })
.attr("cy", height/2)
.style("fill", function(d) { return d3.hsl(d.date/1000000000, 0.8, 0.8)})
.style("stroke", function(d) { return d3.hsl(d.date/1000000000, 0.7, 0.7)})
.style("opacity", 0.5)
.attr("r", 8)
.transition()
.duration(400)
.attr("r", 25)
.transition()
.attr("r", 8);
// if filtered dataset has less circles than already existing, remove excess
locations.exit()
.remove();
}
function update(h) {
// update position and text of label according to slider scale
handle.attr("cx", x(h));
label
.attr("x", x(h))
.text(formatDate(h));
// filter data set and redraw plot
var newData = dataset.filter(function(d) {
return d.date < h;
})
drawPlot(newData);
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment