Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A visualization to explore timeseries of MIT wifi network data.
<!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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.