Brushing Scatter Plots Example
Brushing and linking is an interaction technique that highlights points based on linked selections across multiple views. Brushing and linking can be particularly useful for exploring relationships in multi-dimensional data. This example uses an interactive scatter plot matrix of penguin measurements, and echoes the original Brushing Scatterplots paper by Becker & Cleveland. Click and drag on the visualization to initiate a linked selection.
Vega JSON Specification <>
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "A scatter plot matrix of penguin data with interactive linked selections.",
"padding": 10,
"config": {
"axis": {
"tickColor": "#ccc"
}
},
"signals": [
{ "name": "chartSize", "value": 120 },
{ "name": "chartPad", "value": 20 },
{ "name": "chartStep", "update": "chartSize + chartPad" },
{ "name": "width", "update": "chartStep * 4" },
{ "name": "height", "update": "chartStep * 4" },
{
"name": "cell", "value": null,
"on": [
{
"events": "@cell:pointerdown", "update": "group()"
},
{
"events": "@cell:pointerup",
"update": "!span(brushX) && !span(brushY) ? null : cell"
}
]
},
{
"name": "brushX", "value": 0,
"on": [
{
"events": "@cell:pointerdown",
"update": "[x(cell), x(cell)]"
},
{
"events": "[@cell:pointerdown, window:pointerup] > window:pointermove",
"update": "[brushX[0], clamp(x(cell), 0, chartSize)]"
},
{
"events": {"signal": "delta"},
"update": "clampRange([anchorX[0] + delta[0], anchorX[1] + delta[0]], 0, chartSize)"
}
]
},
{
"name": "brushY", "value": 0,
"on": [
{
"events": "@cell:pointerdown",
"update": "[y(cell), y(cell)]"
},
{
"events": "[@cell:pointerdown, window:pointerup] > window:pointermove",
"update": "[brushY[0], clamp(y(cell), 0, chartSize)]"
},
{
"events": {"signal": "delta"},
"update": "clampRange([anchorY[0] + delta[1], anchorY[1] + delta[1]], 0, chartSize)"
}
]
},
{
"name": "down", "value": [0, 0],
"on": [{"events": "@brush:pointerdown", "update": "[x(cell), y(cell)]"}]
},
{
"name": "anchorX", "value": null,
"on": [{"events": "@brush:pointerdown", "update": "slice(brushX)"}]
},
{
"name": "anchorY", "value": null,
"on": [{"events": "@brush:pointerdown", "update": "slice(brushY)"}]
},
{
"name": "delta", "value": [0, 0],
"on": [
{
"events": "[@brush:pointerdown, window:pointerup] > window:pointermove",
"update": "[x(cell) - down[0], y(cell) - down[1]]"
}
]
},
{
"name": "rangeX", "value": [0, 0],
"on": [
{
"events": {"signal": "brushX"},
"update": "invert(cell.datum.x.data + 'X', brushX)"
}
]
},
{
"name": "rangeY", "value": [0, 0],
"on": [
{
"events": {"signal": "brushY"},
"update": "invert(cell.datum.y.data + 'Y', brushY)"
}
]
},
{
"name": "cursor", "value": "'default'",
"on": [
{
"events": "[@cell:pointerdown, window:pointerup] > window:pointermove!",
"update": "'nwse-resize'"
},
{
"events": "@brush:pointermove, [@brush:pointerdown, window:pointerup] > window:pointermove!",
"update": "'move'"
},
{
"events": "@brush:pointerout, window:pointerup",
"update": "'default'"
}
]
}
],
"data": [
{
"name": "penguins",
"url": "data/penguins.json",
"transform": [
{"type": "filter", "expr": "datum['Beak Length (mm)'] != null"}
]
},
{
"name": "fields",
"values": [
"Beak Length (mm)",
"Beak Depth (mm)",
"Flipper Length (mm)",
"Body Mass (g)"
]
},
{
"name": "cross",
"source": "fields",
"transform": [
{ "type": "cross", "as": ["x", "y"] },
{ "type": "formula", "as": "xscale", "expr": "datum.x.data + 'X'" },
{ "type": "formula", "as": "yscale", "expr": "datum.y.data + 'Y'" }
]
}
],
"scales": [
{
"name": "groupx",
"type": "band",
"range": "width",
"domain": {"data": "fields", "field": "data"}
},
{
"name": "groupy",
"type": "band",
"range": [{"signal": "height"}, 0],
"domain": {"data": "fields", "field": "data"}
},
{
"name": "color",
"type": "ordinal",
"domain": {"data": "penguins", "field": "Species"},
"range": "category"
},
{
"name": "Beak Length (mm)X", "zero": false, "nice": true,
"domain": {"data": "penguins", "field": "Beak Length (mm)"},
"range": [0, {"signal": "chartSize"}]
},
{
"name": "Beak Depth (mm)X", "zero": false, "nice": true,
"domain": {"data": "penguins", "field": "Beak Depth (mm)"},
"range": [0, {"signal": "chartSize"}]
},
{
"name": "Flipper Length (mm)X", "zero": false, "nice": true,
"domain": {"data": "penguins", "field": "Flipper Length (mm)"},
"range": [0, {"signal": "chartSize"}]
},
{
"name": "Body Mass (g)X", "zero": false, "nice": true,
"domain": {"data": "penguins", "field": "Body Mass (g)"},
"range": [0, {"signal": "chartSize"}]
},
{
"name": "Beak Length (mm)Y", "zero": false, "nice": true,
"domain": {"data": "penguins", "field": "Beak Length (mm)"},
"range": [{"signal": "chartSize"}, 0]
},
{
"name": "Beak Depth (mm)Y", "zero": false, "nice": true,
"domain": {"data": "penguins", "field": "Beak Depth (mm)"},
"range": [{"signal": "chartSize"}, 0]
},
{
"name": "Flipper Length (mm)Y", "zero": false, "nice": true,
"domain": {"data": "penguins", "field": "Flipper Length (mm)"},
"range": [{"signal": "chartSize"}, 0]
},
{
"name": "Body Mass (g)Y", "zero": false, "nice": true,
"domain": {"data": "penguins", "field": "Body Mass (g)"},
"range": [{"signal": "chartSize"}, 0]
}
],
"axes": [
{
"orient": "left", "scale": "Beak Length (mm)Y", "minExtent": 25,
"title": "Beak Length (mm)", "tickCount": 5, "domain": false,
"position": {"signal": "3 * chartStep"}
},
{
"orient": "left", "scale": "Beak Depth (mm)Y", "minExtent": 25,
"title": "Beak Depth (mm)", "tickCount": 5, "domain": false,
"position": {"signal": "2 * chartStep"}
},
{
"orient": "left", "scale": "Flipper Length (mm)Y", "minExtent": 25,
"title": "Flipper Length (mm)", "tickCount": 5, "domain": false,
"position": {"signal": "1 * chartStep"}
},
{
"orient": "left", "scale": "Body Mass (g)Y", "minExtent": 25,
"title": "Body Mass (g)", "tickCount": 5, "domain": false
},
{
"orient": "bottom", "scale": "Beak Length (mm)X",
"title": "Beak Length (mm)", "tickCount": 5, "domain": false,
"offset": {"signal": "-chartPad"}
},
{
"orient": "bottom", "scale": "Beak Depth (mm)X",
"title": "Beak Depth (mm)", "tickCount": 5, "domain": false,
"offset": {"signal": "-chartPad"}, "position": {"signal": "1 * chartStep"}
},
{
"orient": "bottom", "scale": "Flipper Length (mm)X",
"title": "Flipper Length (mm)", "tickCount": 5, "domain": false,
"offset": {"signal": "-chartPad"}, "position": {"signal": "2 * chartStep"}
},
{
"orient": "bottom", "scale": "Body Mass (g)X",
"title": "Body Mass (g)", "tickCount": 5, "domain": false,
"offset": {"signal": "-chartPad"}, "position": {"signal": "3 * chartStep"}
}
],
"legends": [
{
"fill": "color",
"title": "Species",
"offset": 0,
"encode": {
"symbols": {
"update": {
"fillOpacity": {"value": 0.5},
"stroke": {"value": "transparent"}
}
}
}
}
],
"marks": [
{
"type": "rect",
"encode": {
"enter": {
"fill": {"value": "#eee"}
},
"update": {
"opacity": {"signal": "cell ? 1 : 0"},
"x": {"signal": "cell ? cell.x + brushX[0] : 0"},
"x2": {"signal": "cell ? cell.x + brushX[1] : 0"},
"y": {"signal": "cell ? cell.y + brushY[0] : 0"},
"y2": {"signal": "cell ? cell.y + brushY[1] : 0"}
}
}
},
{
"name": "cell",
"type": "group",
"from": {"data": "cross"},
"encode": {
"enter": {
"x": {"scale": "groupx", "field": "x.data"},
"y": {"scale": "groupy", "field": "y.data"},
"width": {"signal": "chartSize"},
"height": {"signal": "chartSize"},
"fill": {"value": "transparent"},
"stroke": {"value": "#ddd"}
}
},
"marks": [
{
"type": "symbol",
"from": {"data": "penguins"},
"interactive": false,
"encode": {
"enter": {
"x": {
"scale": {"parent": "xscale"},
"field": {"datum": {"parent": "x.data"}}
},
"y": {
"scale": {"parent": "yscale"},
"field": {"datum": {"parent": "y.data"}}
},
"fillOpacity": {"value": 0.5},
"size": {"value": 36}
},
"update": {
"fill": [
{
"test": "!cell || inrange(datum[cell.datum.x.data], rangeX) && inrange(datum[cell.datum.y.data], rangeY)",
"scale": "color", "field": "Species"
},
{"value": "#ddd"}
]
}
}
}
]
},
{
"type": "rect",
"name": "brush",
"encode": {
"enter": {
"fill": {"value": "transparent"}
},
"update": {
"x": {"signal": "cell ? cell.x + brushX[0] : 0"},
"x2": {"signal": "cell ? cell.x + brushX[1] : 0"},
"y": {"signal": "cell ? cell.y + brushY[0] : 0"},
"y2": {"signal": "cell ? cell.y + brushY[1] : 0"}
}
}
}
]
}