Crossfilter Flights Example
Interactive cross-filtering of airline arrival delay, departure time and travel distance data for 200,000 flights. Uses the Vega crossfilter transform to perform efficient incremental updates. Click and drag to move or resize a brush, double-click to maximize, or use the scroll wheel to zoom the brush size. Based on the Crossfilter.js example by Mike Bostock.
Vega JSON Specification <>
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "Interactive cross-filtering among histograms of flight statistics.",
"width": 500,
"padding": 5,
"signals": [
{ "name": "chartHeight", "value": 100 },
{ "name": "chartPadding", "value": 50 },
{ "name": "height", "update": "(chartHeight + chartPadding) * 3" },
{ "name": "delayExtent", "value": [-60, 180] },
{ "name": "timeExtent", "value": [0, 24] },
{ "name": "distExtent", "value": [0, 2400] },
{ "name": "delayStep", "value": 10,
"bind": {"input": "range", "min": 2, "max": 20, "step": 1} },
{ "name": "timeStep", "value": 1,
"bind": {"input": "range", "min": 0.25, "max": 2, "step": 0.25} },
{ "name": "distStep", "value": 100,
"bind": {"input": "range", "min": 50, "max": 200, "step": 50} },
{ "name": "delayRange", "update": "delayExtent",
"on": [
{
"events": {"signal": "delayZoom"},
"update": "[(delayRange[0]+delayRange[1])/2 - delayZoom, (delayRange[0]+delayRange[1])/2 + delayZoom]"
},
{
"events": "@delay:dblclick!, @delayBrush:dblclick!",
"update": "[delayExtent[0], delayExtent[1]]"
},
{
"events": "[@delayBrush:pointerdown, window:pointerup] > window:pointermove!",
"update": "[delayRange[0] + invert('delayScale', x()) - invert('delayScale', xmove), delayRange[1] + invert('delayScale', x()) - invert('delayScale', xmove)]"
},
{
"events": "[@delay:pointerdown, window:pointerup] > window:pointermove!",
"update": "[min(delayAnchor, invert('delayScale', x())), max(delayAnchor, invert('delayScale', x()))]"
}
]
},
{ "name": "delayZoom", "value": 0,
"on": [{
"events": "@delay:wheel!, @delayBrush:wheel!",
"update": "0.5 * abs(span(delayRange)) * pow(1.0005, event.deltaY * pow(16, event.deltaMode))"
}]
},
{ "name": "delayAnchor", "value": 0,
"on": [{
"events": "@delay:pointerdown!",
"update": "invert('delayScale', x())"}
]
},
{ "name": "timeRange", "update": "timeExtent",
"on": [
{
"events": {"signal": "timeZoom"},
"update": "[(timeRange[0]+timeRange[1])/2 - timeZoom, (timeRange[0]+timeRange[1])/2 + timeZoom]"
},
{
"events": "@time:dblclick!, @timeBrush:dblclick!",
"update": "[timeExtent[0], timeExtent[1]]"
},
{
"events": "[@timeBrush:pointerdown, window:pointerup] > window:pointermove!",
"update": "[timeRange[0] + invert('timeScale', x()) - invert('timeScale', xmove), timeRange[1] + invert('timeScale', x()) - invert('timeScale', xmove)]"
},
{
"events": "[@time:pointerdown, window:pointerup] > window:pointermove!",
"update": "[min(timeAnchor, invert('timeScale', x())), max(timeAnchor, invert('timeScale', x()))]"
}
]
},
{ "name": "timeZoom", "value": 0,
"on": [{
"events": "@time:wheel!, @timeBrush:wheel!",
"update": "0.5 * abs(span(timeRange)) * pow(1.0005, event.deltaY * pow(16, event.deltaMode))"
}]
},
{ "name": "timeAnchor", "value": 0,
"on": [{
"events": "@time:pointerdown!",
"update": "invert('timeScale', x())"}
]
},
{ "name": "distRange", "update": "distExtent",
"on": [
{
"events": {"signal": "distZoom"},
"update": "[(distRange[0]+distRange[1])/2 - distZoom, (distRange[0]+distRange[1])/2 + distZoom]"
},
{
"events": "@dist:dblclick!, @distBrush:dblclick!",
"update": "[distExtent[0], distExtent[1]]"
},
{
"events": "[@distBrush:pointerdown, window:pointerup] > window:pointermove!",
"update": "[distRange[0] + invert('distScale', x()) - invert('distScale', xmove), distRange[1] + invert('distScale', x()) - invert('distScale', xmove)]"
},
{
"events": "[@dist:pointerdown, window:pointerup] > window:pointermove!",
"update": "[min(distAnchor, invert('distScale', x())), max(distAnchor, invert('distScale', x()))]"
}
]
},
{ "name": "distZoom", "value": 0,
"on": [{
"events": "@dist:wheel!, @distBrush:wheel!",
"update": "0.5 * abs(span(distRange)) * pow(1.0005, event.deltaY * pow(16, event.deltaMode))"
}]
},
{ "name": "distAnchor", "value": 0,
"on": [{
"events": "@dist:pointerdown!",
"update": "invert('distScale', x())"}
]
},
{ "name": "xmove", "value": 0,
"on": [{"events": "window:pointermove", "update": "x()"}]
}
],
"data": [
{
"name": "flights",
"url": "data/flights-200k.json",
"transform": [
{
"type": "bin", "field": "delay",
"extent": {"signal": "delayExtent"},
"step": {"signal": "delayStep"},
"as": ["delay0", "delay1"]
},
{
"type": "bin", "field": "time",
"extent": {"signal": "timeExtent"},
"step": {"signal": "timeStep"},
"as": ["time0", "time1"]
},
{
"type": "bin", "field": "distance",
"extent": {"signal": "distExtent"},
"step": {"signal": "distStep"},
"as": ["dist0", "dist1"]
},
{
"type": "crossfilter",
"signal": "xfilter",
"fields": ["delay", "time", "distance"],
"query": [
{"signal": "delayRange"},
{"signal": "timeRange"},
{"signal": "distRange"}
]
}
]
}
],
"scales": [
{
"name": "layout",
"type": "band",
"domain": ["delay", "time", "distance"],
"range": "height"
},
{
"name": "delayScale",
"type": "linear",
"round": true,
"domain": {"signal": "delayExtent"},
"range": "width"
},
{
"name": "timeScale",
"type": "linear",
"round": true,
"domain": {"signal": "timeExtent"},
"range": "width"
},
{
"name": "distScale",
"type": "linear",
"round": true,
"domain": {"signal": "distExtent"},
"range": "width"
}
],
"marks": [
{
"description": "Delay Histogram",
"name": "delay",
"type": "group",
"encode": {
"enter": {
"y": {"scale": "layout", "value": "delay", "offset": 20},
"width": {"signal": "width"},
"height": {"signal": "chartHeight"},
"fill": {"value": "transparent"}
}
},
"data": [
{
"name": "delay-bins",
"source": "flights",
"transform": [
{
"type": "resolvefilter",
"ignore": 1,
"filter": {"signal": "xfilter"}
},
{
"type": "aggregate",
"groupby": ["delay0", "delay1"],
"key": "delay0", "drop": false
}
]
}
],
"scales": [
{
"name": "yscale",
"type": "linear",
"domain": {"data": "delay-bins", "field": "count"},
"range": [{"signal": "chartHeight"}, 0]
}
],
"axes": [
{"orient": "bottom", "scale": "delayScale"}
],
"marks": [
{
"type": "rect",
"name": "delayBrush",
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "chartHeight"},
"fill": {"value": "#fcfcfc"}
},
"update": {
"x": {"signal": "scale('delayScale', delayRange[0])"},
"x2": {"signal": "scale('delayScale', delayRange[1])"}
}
}
},
{
"type": "rect",
"interactive": false,
"from": {"data": "delay-bins"},
"encode": {
"enter": {
"fill": {"value": "steelblue"}
},
"update": {
"x": {"scale": "delayScale", "field": "delay0"},
"x2": {"scale": "delayScale", "field": "delay1", "offset": -1},
"y": {"scale": "yscale", "field": "count"},
"y2": {"scale": "yscale", "value": 0}
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "chartHeight"},
"fill": {"value": "firebrick"}
},
"update": {
"x": {"signal": "scale('delayScale', delayRange[0])"},
"width": {"value": 1}
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "chartHeight"},
"fill": {"value": "firebrick"}
},
"update": {
"x": {"signal": "scale('delayScale', delayRange[1])"},
"width": {"value": 1}
}
}
},
{
"type": "text",
"interactive": false,
"encode": {
"enter": {
"y": {"value": -5},
"text": {"value": "Arrival Delay (min)"},
"baseline": {"value": "bottom"},
"fontSize": {"value": 14},
"fontWeight": {"value": 500},
"fill": {"value": "black"}
}
}
}
]
},
{
"description": "Time Histogram",
"name": "time",
"type": "group",
"encode": {
"enter": {
"y": {"scale": "layout", "value": "time", "offset": 20},
"width": {"signal": "width"},
"height": {"signal": "chartHeight"},
"fill": {"value": "transparent"}
}
},
"data": [
{
"name": "time-bins",
"source": "flights",
"transform": [
{
"type": "resolvefilter",
"ignore": 2,
"filter": {"signal": "xfilter"}
},
{
"type": "aggregate",
"groupby": ["time0", "time1"],
"key": "time0", "drop": false
}
]
}
],
"scales": [
{
"name": "yscale",
"type": "linear",
"domain": {"data": "time-bins", "field": "count"},
"range": [{"signal": "chartHeight"}, 0]
}
],
"axes": [
{"orient": "bottom", "scale": "timeScale"}
],
"marks": [
{
"type": "rect",
"name": "timeBrush",
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "chartHeight"},
"fill": {"value": "#fcfcfc"}
},
"update": {
"x": {"signal": "scale('timeScale', timeRange[0])"},
"x2": {"signal": "scale('timeScale', timeRange[1])"}
}
}
},
{
"type": "rect",
"from": {"data": "time-bins"},
"interactive": false,
"encode": {
"enter": {
"fill": {"value": "steelblue"}
},
"update": {
"x": {"scale": "timeScale", "field": "time0"},
"x2": {"scale": "timeScale", "field": "time1", "offset": -1},
"y": {"scale": "yscale", "field": "count"},
"y2": {"scale": "yscale", "value": 0}
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "chartHeight"},
"fill": {"value": "firebrick"}
},
"update": {
"x": {"signal": "scale('timeScale', timeRange[0])"},
"width": {"value": 1}
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "chartHeight"},
"fill": {"value": "firebrick"}
},
"update": {
"x": {"signal": "scale('timeScale', timeRange[1])"},
"width": {"value": 1}
}
}
},
{
"type": "text",
"interactive": false,
"encode": {
"enter": {
"y": {"value": -5},
"text": {"value": "Local Departure Time (hour)"},
"baseline": {"value": "bottom"},
"fontSize": {"value": 14},
"fontWeight": {"value": 500},
"fill": {"value": "black"}
}
}
}
]
},
{
"description": "Distance Histogram",
"name": "dist",
"type": "group",
"encode": {
"enter": {
"y": {"scale": "layout", "value": "distance", "offset": 20},
"width": {"signal": "width"},
"height": {"signal": "chartHeight"},
"fill": {"value": "transparent"}
}
},
"data": [
{
"name": "dist-bins",
"source": "flights",
"transform": [
{
"type": "resolvefilter",
"ignore": 4,
"filter": {"signal": "xfilter"}
},
{
"type": "aggregate",
"groupby": ["dist0", "dist1"],
"key": "dist0", "drop": false
}
]
}
],
"scales": [
{
"name": "yscale",
"type": "linear",
"domain": {"data": "dist-bins", "field": "count"},
"range": [{"signal": "chartHeight"}, 0]
}
],
"axes": [
{"orient": "bottom", "scale": "distScale"}
],
"marks": [
{
"type": "rect",
"name": "distBrush",
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "chartHeight"},
"fill": {"value": "#fcfcfc"}
},
"update": {
"x": {"signal": "scale('distScale', distRange[0])"},
"x2": {"signal": "scale('distScale', distRange[1])"}
}
}
},
{
"type": "rect",
"interactive": false,
"from": {"data": "dist-bins"},
"encode": {
"enter": {
"fill": {"value": "steelblue"}
},
"update": {
"x": {"scale": "distScale", "field": "dist0"},
"x2": {"scale": "distScale", "field": "dist1", "offset": -1},
"y": {"scale": "yscale", "field": "count"},
"y2": {"scale": "yscale", "value": 0}
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "chartHeight"},
"fill": {"value": "firebrick"}
},
"update": {
"x": {"signal": "scale('distScale', distRange[0])"},
"width": {"value": 1}
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "chartHeight"},
"fill": {"value": "firebrick"}
},
"update": {
"x": {"signal": "scale('distScale', distRange[1])"},
"width": {"value": 1}
}
}
},
{
"type": "text",
"interactive": false,
"encode": {
"enter": {
"y": {"value": -5},
"text": {"value": "Travel Distance (miles)"},
"baseline": {"value": "bottom"},
"fontSize": {"value": 14},
"fontWeight": {"value": 500},
"fill": {"value": "black"}
}
}
}
]
}
]
}