-
-
Save mayblue9/effb56c1cdd4ee5eb83d3b82240fbe9d to your computer and use it in GitHub Desktop.
A visualization to explore timeseries of MIT wifi network data.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>MIT Wifi - Timeseries</title> | |
<link rel="stylesheet" href="styles.css"> | |
<style> | |
#banner { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 320px; | |
background-color: white; | |
} | |
#instructions { | |
width: 170px; | |
position: fixed; | |
left: 0; | |
top: 0; | |
} | |
#single-graph .access-point .line { | |
fill: none; | |
stroke-width: 1.5px; | |
opacity: 1.0; | |
} | |
#single-graph { | |
width: 1000px; | |
height: 310px; | |
padding-top:10px; | |
margin: auto; | |
} | |
#single-graph-ui { | |
float: right; | |
padding-right: 10px; | |
text-decoration: none; | |
} | |
#small-multiples { | |
width: 1000px; | |
margin: 330px auto; | |
-webkit-column-width: 100px; | |
-webkit-column-gap: 0px; | |
-webkit-column-rule: none; | |
} | |
#small-multiples svg:hover, #small-multiples .selected { | |
background-color: #EEEEEE; | |
} | |
#small-multiples .access-point .line { | |
fill: none; | |
stroke-width: 1.5px; | |
opacity: 1.0; | |
} | |
#small-multiples ul { | |
list-style-type: none; | |
padding: 0; | |
} | |
#small-multiples ul li { | |
float: right; | |
padding: 0; | |
} | |
#small-multiples ul li .ap-id-text{ | |
opacity: 0.25; | |
font-family: sans-serif; | |
font-size: 1.25em; | |
} | |
#small-multiples ul li .ap-id-text:hover{ | |
opacity: 0.75; | |
} | |
body { | |
font: 10px sans-serif; | |
} | |
.axis path { | |
display: none; | |
} | |
.axis line { | |
shape-rendering: crispEdges; | |
stroke: #333; | |
} | |
.axis .minor line { | |
stroke: #CCC; | |
stroke-dasharray: 2,2; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="banner"> | |
<div id="instructions"> | |
<ol><label>Instructions:</label> | |
<li>Scroll through timerseries of unique devices connected to access points around campus.</li> | |
<li>Select/de-select a time series by clicking on it.</li> | |
<li>Access points for the same building are shown in the same color.</li> | |
</ol> | |
</div> | |
<div id="single-graph"> | |
<div id="single-graph-ui"> | |
<a href="#" onclick="UnselectAll()">clear</a> | |
</div> | |
</div> | |
</div> | |
<div id="small-multiples"> | |
<ul id="small-multiples-list"></ul> | |
</div> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script> | |
function SingleGraph() { | |
this.margin = {top: 30, right: 20, bottom: 20, left: 20}; | |
this.width = 1000 - this.margin.right - this.margin.left; | |
this.height = 300 - this.margin.top - this.margin.bottom; | |
this.x = d3.time.scale(). | |
range([0, this.width]). | |
domain([new Date('9/16/2014'), new Date('9/20/2014')]); | |
this.y = d3.scale.linear(). | |
range([this.height, 0]). | |
domain([0, 20]); | |
this.xAxis = d3.svg.axis() | |
.scale(this.x) | |
.ticks(d3.time.hours,6) | |
.tickSize(-this.height) | |
.orient("bottom"); | |
this.yAxis = d3.svg.axis() | |
.scale(this.y) | |
.tickSize(this.width) | |
.orient("right"); | |
}; | |
SingleGraph.prototype.Initialize = function() { | |
var svg = d3.select("#single-graph").append("svg"). | |
attr("width", this.width + this.margin.left + this.margin.right). | |
attr("height", this.height + this.margin.top + this.margin.bottom) | |
.append("g") | |
.attr("class", "graph") | |
.attr("transform", "translate(" + this.margin.left + "," + | |
this.margin.top + ")"); | |
var gx = svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + this.height + ")") | |
.call(this.xAxis) | |
gx.selectAll("g").filter(function(d) { return d; }) | |
.classed("minor", true); | |
var gy = svg.append("g") | |
.attr("class", "y axis") | |
.call(this.yAxis); | |
gy.selectAll("g").filter(function(d) { return d; }) | |
.classed("minor", true); | |
gy.selectAll("text") | |
.attr("x", 4) | |
.attr("dy", -4); | |
gy.append("text") | |
.attr("transform", "rotate(-90)") | |
.attr("y", -10) | |
.attr("dy", ".71em") | |
.style("text-anchor", "end") | |
.text("Unique Devices"); | |
}; | |
SingleGraph.prototype.Clear = function() { | |
d3.select("#single-graph svg .graph"). | |
selectAll(".access-point"). | |
data([]).exit().remove() | |
}; | |
SingleGraph.prototype.Update = function(selected_data) { | |
this.Clear(); | |
selected_data.series = d3.selectAll('.selected')[0].map(function(d) { | |
return d.__data__;}); | |
if (selected_data.series.length <= 0) {return true;}; | |
var y = this.y.domain([0, d3.max(selected_data.series.map(function(d) { | |
return d3.max(d.timeseries);}) | |
)]); | |
var x = this.x; | |
// Update axis | |
var yaxis = d3.select('#single-graph svg').select('.y.axis') | |
yaxis.call(this.yAxis); | |
yaxis.selectAll("g").filter(function(d) { return d; }) | |
.classed("minor", true); | |
yaxis.selectAll(".tick text") | |
.attr("x", 4) | |
.attr("dy", -4); | |
var dateArray = d3.time.scale() | |
.domain([new Date('9/16/2014'), new Date('9/20/2014')]) | |
.ticks(d3.time.minutes, 15) | |
var line = d3.svg.line(). | |
interpolate("basis"). | |
x(function(d, i) {return x(dateArray[i]);}). | |
y(function(d) {return y(d);}); | |
var access_points = d3.select("#single-graph svg .graph"). | |
selectAll(".access-point"). | |
data(selected_data.series). | |
enter(). | |
append("g"). | |
attr("class", "access-point"); | |
// Plot the timeseries | |
access_points.append("path"). | |
attr("class", "line"). | |
attr("d", function(d, i) {return line(d.timeseries, i);}). | |
attr("stroke", function(d) {return building_colors[d.building].toString();}); | |
// Add a label. | |
access_points.append("text"). | |
attr("class", "ap-label"). | |
attr("x", function(d) {return 900;}). | |
attr("y", function(d,i) {return i*12;}). | |
attr("text-anchor", "start"). | |
attr("fill", function(d) {return building_colors[d.building].toString();}). | |
text(function(d) { return d.ap_id.toUpperCase(); }); | |
}; | |
SingleGraph.prototype.UnselectAll = function() { | |
d3.selectAll(".selected").classed("selected",false); | |
this.Clear(); | |
selected_data = {'series': []}; | |
}; | |
var raw_data; | |
var timeseries_len; | |
var selected_data = {}; | |
var building_colors = {}; | |
var selected_idx = d3.range(4).map(function() {return d3.round(Math.random()*100);}); | |
var singleGraph = new SingleGraph(); | |
singleGraph.Initialize(); | |
d3.json("https://dl.dropboxusercontent.com/u/4035638/clean_timeseries_4day.json", function(error, json) { | |
if (error) return console.warn(error); | |
raw_data = json; | |
timeseries_len = raw_data.series[0].timeseries.length; | |
populate_building_colors(raw_data); | |
small_multiples(raw_data); | |
selected_data.series = raw_data.series.filter( | |
function(d,i) {return selected_idx.indexOf(i) >= 0;}); | |
singleGraph.Update(selected_data); | |
}); | |
function small_multiples(raw_data) { | |
var margin = {top: 10, right: 10, bottom: 10, left: 10}; | |
var width = 100 - margin.right - margin.left; | |
var height = 50 - margin.top - margin.bottom; | |
// Generate random timeseries data. | |
var timeseries_len = 24*4; | |
var x = d3.time.scale(). | |
range([0, width]). | |
domain([0, raw_data.series[0].timeseries.length]); | |
var y = d3.scale.linear(). | |
range([height, 0]). | |
domain([0, 100]); | |
var line = d3.svg.line(). | |
interpolate("basis"). | |
x(function(d, i) {return x(i);}). | |
y(function(d) {return y(d);}); | |
// Sort series by building number. | |
raw_data.series.sort(function(a, b) { | |
if (a.ap_id < b.ap_id) return -1; | |
else return 1; | |
}) | |
// Plot timeseries. | |
var access_point = d3.select("#small-multiples ul").selectAll("svg"). | |
data(raw_data.series.slice(0,1000)). | |
enter().append("li").append("svg"). | |
attr("width", width + margin.left + margin.right). | |
attr("height", height + margin.top + margin.bottom). | |
on("click", function(d) { | |
var clicked_data = this.__data__; | |
if (!d3.select(this).classed("selected")) { | |
d3.select(this).classed("selected", true); | |
} else { | |
selected_data.series = selected_data.series. | |
filter(function(d) { | |
return d.ap_id != clicked_data.ap_id; | |
}); | |
d3.select(this).classed("selected", false); | |
} | |
singleGraph.Update(selected_data); | |
}). | |
append("g"). | |
attr("class", "access-point"). | |
attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
// Add the spark line path. | |
access_point.append("path") | |
.attr("class", "line") | |
.attr("d", function(d, i) { | |
y.domain([0, d3.max(d.timeseries)]); | |
return line(d.timeseries, i); }). | |
attr("stroke", function(d) {return building_colors[d.building].toString();}); | |
// Add some text | |
access_point.append("text"). | |
attr("class","ap-id-text"). | |
attr("x", width/2). | |
attr("y", (height + margin.top)/2). | |
attr("text-anchor", "middle"). | |
text(function(d) { return (d.building+"-"+d.room).toUpperCase(); }); | |
}; | |
function generate_random_data(ntimeseries, len) { | |
/* | |
* Generates random timeseries data. | |
*/ | |
var data = d3.range(ntimeseries).map(function(d, i) { return { | |
'idx': i, | |
'building': Math.random() * 10, | |
'room': Math.random() * 300, | |
'ap_id': 'lorem-123', | |
'timeseries': generate_timeseries(len) | |
} | |
}); | |
return data; | |
}; | |
function generate_timeseries(len) { | |
/* | |
* Generates a single random timeseries. | |
*/ | |
return d3.range(len). | |
map(function() {return Math.floor(Math.random()*100);}); | |
}; | |
function populate_building_colors(raw_data) { | |
/* | |
* Assigns a unique, random color to each building | |
*/ | |
var buildings = d3.set(raw_data.series.map(function(d) { | |
return d.building; })).values(); | |
for(var i=0, building; building=buildings[i]; i++){ | |
building_colors[building] = d3.rgb(d3.round(Math.random()*255), | |
d3.round(Math.random()*255), | |
d3.round(Math.random()*255)) | |
} | |
}; | |
function UnselectAll() { | |
console.log(singleGraph); | |
singleGraph.UnselectAll(); | |
}; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment