Skip to content

Instantly share code, notes, and snippets.

@piwodlaiwo
Created January 13, 2018 03:24
Show Gist options
  • Save piwodlaiwo/d90ab69cd5a4c573cf9139ca9e593c37 to your computer and use it in GitHub Desktop.
Save piwodlaiwo/d90ab69cd5a4c573cf9139ca9e593c37 to your computer and use it in GitHub Desktop.
Crossfilter with D3v4
license: mit
date delay distance origin destination
01010001 14 405 MCI MDW
01010530 -11 370 LAX PHX
01010540 5 389 ONT SMF
01010600 -5 337 OAK LAX
01010600 3 303 MSY HOU
01010605 5 236 LAS LAX
01010610 -4 405 MDW MCI
01010615 -2 188 RNO SJC
01010615 0 197 FLL TPA
01010615 0 399 SEA BOI
01010615 5 562 ELP DAL
01010620 -5 358 SMF BUR
01010620 0 491 BNA MCI
01010625 -6 361 ONT OAK
01010625 0 313 MCI OKC
01010625 1 271 MDW SDF
01010625 5 689 SLC SEA
01010630 -1 487 TPA MSY
01010630 -10 399 BOI SEA
01010630 -15 621 SJC PHX
01010630 -2 361 OAK ONT
01010630 -3 220 ISP BWI
01010630 -3 397 SMF LAS
01010630 -8 251 MDW STL
01010630 -8 417 SJC SAN
01010630 1 682 BWI BHM
01010630 10 370 PHX LAX
01010630 15 177 BHM BNA
01010630 2 562 DAL ELP
01010635 0 453 HOU TUL
01010635 1 601 RNO PHX
01010635 2 670 BNA HOU
01010635 9 677 ABQ LAX
01010640 -21 777 BDL MDW
01010640 -8 197 ONT LAS
01010640 3 833 OKC PHX
01010640 5 495 SDF BWI
01010645 -14 423 MDW OMA
01010645 -14 605 SEA SMF
01010645 -19 838 MHT MDW
01010645 -5 223 LAS BUR
01010645 -5 296 SJC BUR
01010645 5 405 MCI MDW
01010645 9 460 TPA BHM
01010647 17 293 LBB DAL
01010650 20 570 BHM HOU
01010650 7 588 BWI BNA
01010653 16 590 SLC LAX
01010655 -19 1797 LAX BNA
01010655 -28 1864 BWI SLC
01010700 -10 1481 PDX MCI
01010700 -10 361 OAK ONT
01010700 -28 1235 LAS HOU
01010700 -3 256 LAS PHX
01010700 -3 588 SLC OAK
01010700 -4 423 OMA MDW
01010700 -4 612 TPA BNA
01010700 -5 1090 AUS LAS
01010700 -5 296 BUR SJC
01010700 -5 369 PHX BUR
01010700 -5 407 OAK LAS
01010700 -6 325 OAK BUR
01010700 -7 236 LAS LAX
01010700 0 223 BUR LAS
01010700 0 303 MSY HOU
01010700 1 358 BUR SMF
01010700 12 550 MCO MSY
01010700 17 284 MDW CMH
01010700 4 307 CLE MDW
01010704 -2 457 DTW BNA
01010705 -1 471 MSY BNA
01010705 -10 443 RDU BNA
01010705 -15 677 LAX ABQ
01010705 -18 1246 HOU BWI
01010705 -5 188 SJC RNO
01010705 -9 386 SJC LAS
01010705 0 588 OAK SLC
01010705 13 365 JAX BHM
01010705 4 1072 MCO MCI
01010705 7 967 MCI BWI
01010706 -5 303 HOU MSY
01010708 -14 765 ISP MDW
01010710 -15 1959 OAK BNA
01010710 -26 2277 PVD PHX
01010710 -31 1599 SAN MSY
01010710 -8 251 STL MDW
01010710 0 1506 SDF PHX
01010710 2 842 TPA BWI
01010715 -12 515 IND BWI
01010715 -13 1076 TUL LAS
01010715 -2 447 SFO SAN
01010715 -25 2106 BWI LAS
01010715 -3 405 MCI MDW
01010715 -3 546 GEG SLC
01010715 -7 386 LAS SJC
01010715 24 938 MHT BNA
01010720 -13 1489 OAK MCI
01010720 -20 632 RDU MDW
01010720 -5 446 OAK SAN
01010720 -8 762 PDX LAS
01010720 13 479 SMF PDX
01010725 -12 852 BDL BNA
01010725 -8 935 PHX TUL
01010725 4 294 MAF AUS
01010730 -2 192 SAT HOU
01010730 -3 1591 IND LAS
01010730 -3 284 CMH MDW
01010730 -7 288 ALB BWI
01010730 0 308 SJC LAX
01010730 25 928 TPA AUS
01010730 4 410 BHM STL
01010730 6 673 FLL MSY
01010730 8 448 CLE BNA
01010733 -17 967 BWI MCI
01010733 -3 281 BUF BWI
01010735 -10 229 IND STL
01010735 -13 251 MDW STL
01010735 -25 440 DTW STL
01010735 -3 181 OKC DAL
01010735 -5 1497 TPA ABQ
01010735 -5 304 SAN PHX
01010735 23 377 MHT BWI
01010735 6 1011 BUF MCO
01010735 8 345 RNO LAS
01010738 5 370 LAX PHX
01010740 -10 303 HOU MSY
01010740 -11 718 MCI ABQ
01010740 -15 325 BUR OAK
01010740 -15 793 FLL BNA
01010740 -17 1448 PHX BNA
01010740 -2 294 AUS MAF
01010740 -2 347 ELP PHX
01010740 -25 1521 MDW LAS
01010740 -6 587 TPA RDU
01010740 -7 229 MDW DTW
01010740 -7 351 TUL STL
01010740 0 248 SAT DAL
01010744 10 308 LAX SJC
01010745 -2 223 BUR LAS
01010745 -6 904 RDU MCI
01010745 0 283 BDL BWI
01010745 2 507 PHX SLC
01010745 5 487 TPA MSY
01010745 9 395 BNA MDW
01010750 -12 646 PHX OAK
01010750 -2 256 PHX LAS
01010750 -5 367 TUS SAN
01010750 -8 351 STL TUL
01010750 -8 369 BUR PHX
01010750 4 1455 BHM PHX
01010753 -5 1363 LAX MCI
01010755 -10 419 OKC HOU
01010755 19 284 MDW CMH
01010755 4 587 MCO JAN
01010755 6 321 BHM MSY
01010757 -2 1814 LAX IND
01010757 -3 689 SEA SLC
01010800 -10 1162 RDU AUS
01010800 -10 337 LAX OAK
01010800 -10 671 SEA OAK
01010800 -10 989 MDW MCO
01010800 -11 237 STL MCI
01010800 -13 843 PHX SAT
01010800 -13 998 MSY BWI
01010800 -19 787 MCO BWI
01010800 -3 1073 ALB MCO
01010800 -3 197 LAS ONT
<!DOCTYPE html>
<meta charset="utf-8">
<title>Crossfilter D3v4</title>
<style>
@import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:400,700);
body {
font-family: "Helvetica Neue";
margin: 40px auto;
width: 960px;
min-height: 2000px;
}
#body {
position: relative;
}
footer {
padding: 2em 0 1em 0;
font-size: 12px;
}
h1 {
font-size: 96px;
margin-top: .3em;
margin-bottom: 0;
}
h1 + h2 {
margin-top: 0;
}
h2 {
font-weight: 400;
font-size: 28px;
}
h1, h2 {
font-family: "Yanone Kaffeesatz";
text-rendering: optimizeLegibility;
}
#body > p {
line-height: 1.5em;
width: 640px;
text-rendering: optimizeLegibility;
}
#charts {
padding: 10px 0;
}
.chart {
display: inline-block;
height: 151px;
margin-bottom: 20px;
}
.reset {
padding-left: 1em;
font-size: smaller;
color: #ccc;
}
.background.bar {
fill: #ccc;
}
.foreground.bar {
fill: steelblue;
}
.brush-handle {
fill: #eee;
stroke: #666;
}
#hour-chart {
width: 260px;
}
#delay-chart {
width: 230px;
}
#distance-chart {
width: 420px;
}
#date-chart {
width: 920px;
}
#flight-list {
min-height: 1024px;
}
#flight-list .date,
#flight-list .day {
margin-bottom: .4em;
}
#flight-list .flight {
line-height: 1.5em;
background: #eee;
width: 640px;
margin-bottom: 1px;
}
#flight-list .time {
color: #999;
}
#flight-list .flight div {
display: inline-block;
width: 100px;
}
#flight-list div.distance,
#flight-list div.delay {
width: 160px;
padding-right: 10px;
text-align: right;
}
#flight-list .early {
color: green;
}
aside {
position: absolute;
left: 740px;
font-size: smaller;
width: 220px;
}
</style>
<div id="body">
<div id="charts">
<div id="hour-chart" class="chart">
<div class="title">Time of Day</div>
</div>
<div id="delay-chart" class="chart">
<div class="title">Arrival Delay (min.)</div>
</div>
<div id="distance-chart" class="chart">
<div class="title">Distance (mi.)</div>
</div>
<div id="date-chart" class="chart">
<div class="title">Date</div>
</div>
</div>
<aside id="totals"><span id="active">-</span> of <span id="total">-</span> flights selected.</aside>
<div id="lists">
<div id="flight-list" class="list"></div>
</div>
</div>
<script src="https://alexmacy.github.io/crossfilter/crossfilter.v1.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
d3.csv("data.csv", function(error, flights) {
console.log(flights);
//d3.csv("https://alexmacy.github.io/crossfilter/flights-3m.json", function(error, flights) {
console.log(flights.length)
// Various formatters.
var formatNumber = d3.format(",d"),
formatChange = d3.format("+,d"),
formatDate = d3.timeFormat("%B %d, %Y"),
formatTime = d3.timeFormat("%I:%M %p");
// A nest operator, for grouping the flight list.
var nestByDate = d3.nest()
.key(function(d) {return d3.timeDay(d.date)});
// A little coercion, since the CSV is untyped.
flights.forEach(function(d, i) {
d.index = i;
d.date = parseDate(d.date);
d.delay = +d.delay;
d.distance = +d.distance;
});
// Create the crossfilter for the relevant dimensions and groups.
var flight = crossfilter(flights),
all = flight.groupAll(),
date = flight.dimension(function(d) {return d.date}),
dates = date.group(d3.timeDay),
hour = flight.dimension(function(d) {return d.date.getHours() + d.date.getMinutes() / 60}),
hours = hour.group(Math.floor),
delay = flight.dimension(function(d) {return Math.max(-60, Math.min(149, d.delay))}),
delays = delay.group(function(d) {return Math.floor(d / 10) * 10}),
distance = flight.dimension(function(d) {return Math.min(1999, d.distance)}),
distances = distance.group(function(d) {return Math.floor(d / 50) * 50});
var charts = [
barChart()
.dimension(hour)
.group(hours)
.x(d3.scaleLinear()
.domain([0, 24])
.rangeRound([0, 10 * 24])),
barChart()
.dimension(delay)
.group(delays)
.x(d3.scaleLinear()
.domain([-60, 150])
.rangeRound([0, 10 * 21])),
barChart()
.dimension(distance)
.group(distances)
.x(d3.scaleLinear()
.domain([0, 2000])
.rangeRound([0, 10 * 40])),
barChart()
.dimension(date)
.group(dates)
.round(d3.timeDay.round)
.x(d3.scaleTime()
.domain([new Date(2001, 0, 1), new Date(2001, 3, 1)])
.rangeRound([0, 10 * 90]))
.filter([new Date(2001, 1, 1), new Date(2001, 2, 1)])
];
// Given our array of charts, which we assume are in the same order as the
// .chart elements in the DOM, bind the charts to the DOM and render them.
// We also listen to the chart's brush events to update the display.
var chart = d3.selectAll(".chart")
.data(charts)
// Render the initial lists.
var list = d3.selectAll(".list")
.data([flightList]);
// Render the total.
d3.selectAll("#total")
.text(formatNumber(flight.size()));
renderAll();
// Renders the specified chart or list.
function render(method) {
d3.select(this).call(method);
}
// Whenever the brush moves, re-rendering everything.
function renderAll() {
chart.each(render);
list.each(render);
d3.select("#active").text(formatNumber(all.value()));
}
// Like d3.timeFormat, but faster.
function parseDate(d) {
return new Date(2001,
d.substring(0, 2) - 1,
d.substring(2, 4),
d.substring(4, 6),
d.substring(6, 8));
}
window.filter = function(filters) {
filters.forEach(function(d, i) {charts[i].filter(d)});
renderAll();
};
window.reset = function(i) {
charts[i].filter(null);
renderAll();
};
function flightList(div) {
var flightsByDate = nestByDate.entries(date.top(40));
div.each(function() {
var date = d3.select(this).selectAll(".date")
.data(flightsByDate, function(d) {return d.key});
date.exit().remove();
date.enter().append("div")
.attr("class", "date")
.append("div")
.attr("class", "day")
.text(function(d) {return formatDate(d.values[0].date)})
.merge(date);
var flight = date.order().selectAll(".flight")
.data(function(d) {return d.values}, function(d) {return d.index});
flight.exit().remove();
var flightEnter = flight.enter().append("div")
.attr("class", "flight");
flightEnter.append("div")
.attr("class", "time")
.text(function(d) {return formatTime(d.date)});
flightEnter.append("div")
.attr("class", "origin")
.text(function(d) {return d.origin});
flightEnter.append("div")
.attr("class", "destination")
.text(function(d) {return d.destination});
flightEnter.append("div")
.attr("class", "distance")
.text(function(d) {return formatNumber(d.distance) + " mi."});
flightEnter.append("div")
.attr("class", "delay")
.classed("early", function(d) {return d.delay < 0})
.text(function(d) {return formatChange(d.delay) + " min."});
flightEnter.merge(flight);
flight.order();
});
}
function barChart() {
if (!barChart.id) barChart.id = 0;
var margin = {top: 10, right: 10, bottom: 20, left: 10},
x,
y = d3.scaleLinear().range([100, 0]),
id = barChart.id++,
axis = d3.axisBottom(),
brush = d3.brushX(),
brushDirty,
dimension,
group,
round,
gBrush;
function chart(div) {
var width = x.range()[1],
height = y.range()[0];
brush.extent([[0, 0], [width, height]])
y.domain([0, group.top(1)[0].value]);
div.each(function() {
var div = d3.select(this),
g = div.select("g");
// Create the skeletal chart.
if (g.empty()) {
div.select(".title").append("a")
.attr("href", "javascript:reset(" + id + ")")
.attr("class", "reset")
.text("reset")
.style("display", "none");
g = div.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
g.append("clipPath")
.attr("id", "clip-" + id)
.append("rect")
.attr("width", width)
.attr("height", height);
g.selectAll(".bar")
.data(["background", "foreground"])
.enter().append("path")
.attr("class", function(d) {return d + " bar"})
.datum(group.all());
g.selectAll(".foreground.bar")
.attr("clip-path", "url(#clip-" + id + ")");
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(axis);
// Initialize the brush component with pretty resize handles.
gBrush = g.append("g")
.attr("class", "brush")
.call(brush);
gBrush.selectAll(".handle--custom")
.data([{type: "w"}, {type: "e"}])
.enter().append("path")
.attr("class", "brush-handle")
.attr("cursor", "ew-resize")
.attr("d", resizePath)
.style("display", "none")
}
// Only redraw the brush if set externally.
if (brushDirty != false) {
var filterVal = brushDirty;
brushDirty = false;
div.select(".title a").style("display", d3.brushSelection(div) ? null : "none");
if (!filterVal) {
g.call(brush)
g.selectAll("#clip-" + id + " rect")
.attr("x", 0)
.attr("width", width);
g.selectAll(".brush-handle").style("display", "none")
renderAll();
} else {
var range = filterVal.map(x)
brush.move(gBrush, range)
}
}
g.selectAll(".bar").attr("d", barPath);
});
function barPath(groups) {
var path = [],
i = -1,
n = groups.length,
d;
while (++i < n) {
d = groups[i];
path.push("M", x(d.key), ",", height, "V", y(d.value), "h9V", height);
}
return path.join("");
}
function resizePath(d) {
var e = +(d.type == "e"),
x = e ? 1 : -1,
y = height / 3;
return "M" + (.5 * x) + "," + y
+ "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6)
+ "V" + (2 * y - 6)
+ "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y)
+ "Z"
+ "M" + (2.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8)
+ "M" + (4.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8);
}
}
brush.on("start.chart", function() {
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", null);
});
brush.on("brush.chart", function() {
var g = d3.select(this.parentNode);
var brushRange = d3.event.selection || d3.brushSelection(this); // attempt to read brush range
var xRange = x && x.range(); // attempt to read range from x scale
var activeRange = brushRange || xRange; // default to x range if no brush range available
var hasRange = activeRange &&
activeRange.length === 2 &&
!isNaN(activeRange[0]) &&
!isNaN(activeRange[1]);
if (!hasRange) return; // quit early if we don't have a valid range
// calculate current brush extents using x scale
var extents = activeRange.map(x.invert);
// if rounding fn supplied, then snap to rounded extents
// and move brush rect to reflect rounded range bounds if it was set by user interaction
if (round) {
extents = extents.map(round);
activeRange = extents.map(x);
if (d3.event.sourceEvent &&
d3.event.sourceEvent.type === "mousemove") {
d3.select(this).call(brush.move, activeRange)
}
}
// move brush handles to start and end of range
g.selectAll(".brush-handle")
.style("display", null)
.attr("transform", function(d, i) {
return "translate(" + activeRange[i] + ", 0)"
});
// resize sliding window to reflect updated range
g.select("#clip-" + id + " rect")
.attr("x", activeRange[0])
.attr("width", activeRange[1] - activeRange[0]);
// filter the active dimension to the range extents
dimension.filterRange(extents);
// re-render the other charts accordingly
renderAll();
});
brush.on("end.chart", function() {
// reset corresponding filter if the brush selection was cleared
// (e.g. user "clicked off" the active range)
if (!d3.brushSelection(this)) {
reset(id);
}
});
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return x;
x = _;
axis.scale(x);
return chart;
};
chart.y = function(_) {
if (!arguments.length) return y;
y = _;
return chart;
};
chart.dimension = function(_) {
if (!arguments.length) return dimension;
dimension = _;
return chart;
};
chart.filter = function(_) {
if (!_) dimension.filterAll();
brushDirty = _;
return chart;
};
chart.group = function(_) {
if (!arguments.length) return group;
group = _;
return chart;
};
chart.round = function(_) {
if (!arguments.length) return round;
round = _;
return chart;
};
chart.gBrush = function() {
return gBrush
}
return chart;
}
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment