Last active
February 13, 2016 02:18
-
-
Save stevemandl/02febfc129131db79adf to your computer and use it in GitHub Desktop.
UpdatingCrossfilter Demo
This file contains hidden or 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
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/2.0.0-beta.20/dc.js"></script> | |
<script src="updatingCrossfilter.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dc/2.0.0-beta.20/dc.css" /> | |
<div> DC Version: <span id="version"></span></div> | |
<div id="timechart"></div> | |
<div id="histogram"></div> | |
<script> | |
var getData = function (d) { return {ts: d, y: Math.random()* 2 -1 }; }, //get a data point | |
size = 400, // the sample size | |
binCount = 20, //number of bins in the histogram | |
bins = d3.range(-1,1,1/binCount), | |
step = 50, // milliseconds between data points | |
th = new Date().getTime(), // [tl,th] is the visible time window | |
tl = th - size * step, | |
data = d3.range(tl, th, step).map(getData), //fetch the data | |
trend = updatingCrossfilter(data), //use updatingCrossfilter as you would a regular crossfilter | |
dim = trend.dimension(function (d) { return d.ts; }), | |
//grp sums y values - even though there is only one per timestamp in this example | |
grp = dim.group().reduceSum(function (d) { return d.y; }), | |
//yDim is used for the histogram | |
yDim = trend.dimension(function(d){ return Math.floor(d.y*binCount)/binCount;}), | |
yGrp = yDim.group(), // sums the number of data points in this bin | |
colors = d3.scale.linear() //pretty colors | |
.domain([-1.0, -.2, 0.0, 0.2, 1.0]) | |
.range(["red", "blue", "green", "blue", "red"]) | |
.interpolate(d3.interpolateHcl); | |
yGrp._all = yGrp.all; //need to replace all() to ensure empty bins are always there | |
yGrp.all = function () {//add artificial empty bins | |
var result = d3.map(); | |
bins.forEach( function(d){ result.set(d,0); }); | |
yGrp._all().forEach(function(d) { | |
result.set(d.key,d.value); | |
}); | |
return result.entries(); | |
} | |
var chart = dc.barChart("#timechart") | |
.width(500) | |
.height(400) | |
.dimension(dim) | |
.group(grp) | |
.zoomOutRestrict(false) | |
.brushOn(false) | |
.transitionDuration(0) | |
.y(d3.scale.linear().domain([-1, 1])) | |
.x(d3.time.scale().domain([tl, th])) | |
.colors(colors) | |
.colorAccessor(dc.pluck("value")), | |
histogram = dc.rowChart("#histogram") | |
.width(300) | |
.height(400) | |
.gap(1) | |
.x(d3.scale.linear().domain([0, 100]).range([0,200])) | |
.transitionDuration(0) | |
.group(yGrp) | |
.dimension(yDim) | |
.elasticX(false) | |
.ordinalColors(bins.map(colors)) | |
.ordering( function(d){return -d.key;} ), | |
// refresh is called periodically to add, delete, and update data from the crossfilter | |
refresh = function () { | |
trend.add([getData(th + step)]); //add a new data point | |
tl += step, th += step; //slide the window | |
dim.filter(function (d) { return d < tl; }); // filter the off-screen older data | |
trend.remove(); // and remove it from the crossfilter | |
dim.filter(); // clear the filter | |
// update half the data points with modified data | |
trend.update(dim.bottom(size/2).map( | |
function(d){ | |
d.y = d.y * .99; | |
return d; | |
} | |
)); | |
}; | |
chart.xAxis().ticks(5); | |
// when the updatingCrossfilter gets updated, refresh and refocus the affected charts | |
trend.on("update", function(){ | |
chart.focus([tl,th]); | |
histogram.redraw(); | |
}); | |
d3.select("#version").text(dc.version); | |
dc.renderAll(); | |
setInterval(refresh, step); | |
</script> |
This file contains hidden or 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
updatingCrossfilter = function(cbk){ | |
var c = crossfilter(); //the underlying crossfilter | |
//function to get a serial ID | |
var _getID = (function(){var id = 0; return function(){ return id++;}})(), | |
//allows the client to subscribe to update events | |
_listener = d3.dispatch("update", "startUpdate"); | |
// registers an event handler | |
c.on = function (event, listener) { | |
_listener.on(event, listener); | |
return c; | |
}; | |
//the unique ids of the data elements of the crossfilter | |
c._ids = d3.set(); | |
//the dimension to track the ids | |
c._idDim = c.dimension(function(d) { return d._id; }); | |
//backup add and remove for later | |
c._add = c.add; | |
c._remove = c.remove; | |
// add new data | |
c.add = function(newData){ | |
newData.forEach(function(d){ | |
d._id = _getID(); | |
this._ids.add(d._id);}, this); | |
return c._add(newData); | |
}; | |
//remove data matching the current filter(s) | |
c.remove = function(){ | |
this._idDim | |
.top(Infinity) | |
.forEach(function(d){this._ids.remove(d._id);}, this); | |
return c._remove(); | |
}; | |
//update newData by replacing the elements with matching id's | |
c.update = function(newData){ | |
_listener.startUpdate(); | |
c.liftFilters(); | |
newData.forEach(function(d){ | |
c._idDim.filter(d._id); | |
c.remove(); | |
}, this); | |
c._idDim.filterAll(); | |
c.add(newData); | |
c.restoreFilters() | |
_listener.update(); | |
return c; | |
}; | |
//temporarily lift filters from all charts' dimensions, saving them to for restoreFilters() later | |
//TODO: listen for filter changes so this is not dependent on dc | |
c.liftFilters = function(){ | |
dc.chartRegistry.list().forEach(function(d){ | |
d._liftedFilters = d.filters(); | |
d.filterAll(); | |
}); | |
return c; | |
}; | |
//restore filters to charts' dimensions previously saved by liftFilters() | |
c.restoreFilters = function(){ | |
dc.chartRegistry.list().forEach(function(d){ | |
if (d._liftedFilters){ | |
d._liftedFilters.map(d.filter); | |
delete d._liftedFilters; | |
} | |
}); | |
return c; | |
}; | |
//sanitize cbk as a function | |
if (cbk && typeof arguments[0] != "function"){ | |
var o = cbk; | |
cbk = function(c){return c.add(o);}; | |
} | |
cbk && cbk(c); | |
return c; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment