Based on https://bl.ocks.org/alexmacy/ebe599703421757852d36bcf71174dfc
xxxxxxxxxx
<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>
https://alexmacy.github.io/crossfilter/crossfilter.v1.min.js
https://d3js.org/d3.v4.min.js