Parameters¶
Parameters enable various dynamic behaviors in GenomeSpy visualizations, such as interactive selections, conditional encoding, and data filtering with expressions. They also enable parameterization when importing specification fragments from external files or named templates. Parameters in GenomeSpy are heavily inspired by the parameters concept of Vega-Lite.
Using Input Bindings¶
Parameters can be bound to input elements, such as sliders, dropdowns, and checkboxes. The GenomeSpy Core library shows the input elements below the visualization. In the GenomeSpy App, the input elements are shown in the View visibility menu, allowing the visualization author to provide configuration options to the end user.
Parameters with input bindings should have a unique name within the import
scope. While not enforced in core,
this is necessary for bookmarkable state in the GenomeSpy App.
By default, selection parameters and parameters with input bindings are
persisted in the GenomeSpy App's bookmarks and provenance history. Use
persist: false to opt out of persistence for ephemeral params (such as hover
selections) or when encoding.key is not defined for point selections.
For point selections, encoding.key should uniquely identify data objects.
You can use either a single field or a composite key (an array of field
definitions). When using a composite key, keep the field order stable across
bookmark creation and restore.
The following example shows how to bind parameters to input elements and use them to control the size, angle, and text of a text mark.
{
"description": "Parameter bindings example.",
"padding": 0,
"view": { "fill": "#cbeef3" },
"params": [
{
"name": "size",
"value": 80,
"bind": { "input": "range", "min": 1, "max": 300 }
},
{
"name": "angle",
"value": 0,
"bind": { "input": "range", "min": 0, "max": 360 }
},
{
"name": "text",
"value": "Params are cool!",
"bind": {
"input": "select",
"options": ["Params are cool!", "GenomeSpy", "Hello", "World"]
}
}
],
"data": {
"values": [{}]
},
"mark": {
"type": "text",
"font": "Lobster",
"text": { "expr": "text" },
"size": { "expr": "size" },
"angle": { "expr": "angle" }
}
}
Expressions¶
Parameters can be based on expressions, which can depend on other parameters. They are automatically re-evaluated when the dependent parameters change.
{
"description": "Expression parameter example.",
"view": { "stroke": "lightgray" },
"params": [
{
"name": "A",
"value": 2,
"bind": { "input": "range", "min": 0, "max": 10, "step": 1 }
},
{
"name": "B",
"value": 3,
"bind": { "input": "range", "min": 0, "max": 10, "step": 1 }
},
{ "name": "C", "expr": "A * B" }
],
"data": {
"values": [{}]
},
"mark": {
"type": "text",
"size": 30,
"text": { "expr": "'' + A + ' * ' + B + ' = ' + C" }
}
}
Selection Parameters¶
Parameters allow for defining interactive selections, which can be used in
conditional encodings and
"filter" transforms. GenomeSpy compiles the
conditional encoding rules into efficient GPU shader code, enabling fast
interactions in very large data sets.
Point Selection¶
The following example has been adapted from Vega-Lite's example
gallery
with slight modifications (GenomeSpy provides no "bar" mark). The
specification below is fully compatible with Vega-Lite. You can select multiple
bars by holding down the Shift key.
{
"description": "A bar chart with highlighting on hover and selecting on click. (Inspired by Tableau's interaction style.)",
"data": {
"values": [
{ "a": "A", "b": 28 },
{ "a": "B", "b": 55 },
{ "a": "C", "b": 43 },
{ "a": "D", "b": 91 },
{ "a": "E", "b": 81 },
{ "a": "F", "b": 53 },
{ "a": "G", "b": 19 },
{ "a": "H", "b": 87 },
{ "a": "I", "b": 52 }
]
},
"params": [
{
"name": "highlight",
"select": { "type": "point", "on": "pointerover" }
},
{ "name": "select", "select": "point" }
],
"mark": { "type": "rect", "fill": "#4C78A8", "stroke": "black" },
"encoding": {
"x": {
"field": "a",
"type": "ordinal",
"scale": { "type": "band", "padding": 0.2 }
},
"y": { "field": "b", "type": "quantitative" },
"fillOpacity": {
"value": 0.3,
"condition": { "param": "select", "value": 1 }
},
"strokeWidth": {
"value": 0,
"condition": [
{ "param": "select", "value": 2, "empty": false },
{ "param": "highlight", "value": 1, "empty": false }
]
}
}
}
Interval Selection¶
Interval selections allow for selecting a range of data points along one or two axes. By default, the start gesture depends on whether the brushed channels are zoomable:
- if any brushed channel is zoomable, start brushing with
Shift+ drag - otherwise, start brushing with plain drag
You can override this behavior with select.on, for example "on": "mousedown"
to always start brushing on plain drag. The selection can be cleared by clicking
outside the selected area.
Active interval selections can also be resized with the mouse wheel when the
pointer is over the selection rectangle. This is controlled by select.zoom.
By default, select.zoom is:
falsewhen any brushed channel uses a zoomable scale (to avoid wheel-gesture conflicts)trueotherwise
You can override the behavior with select.zoom: true/false or an explicit
wheel event definition such as "zoom": "wheel[event.altKey]".
{
"description": "Interval selection example.",
"params": [
{
"name": "brush",
"value": { "x": [2, 4] },
"select": { "type": "interval", "encodings": ["x"] }
}
],
"data": { "url": "data/sincos.csv" },
"mark": { "type": "point", "size": 100 },
"encoding": {
"x": {
"field": "x",
"type": "quantitative",
"scale": { "zoom": true }
},
"y": { "field": "sin", "type": "quantitative" },
"color": {
"condition": { "param": "brush", "value": "#38c" },
"value": "#ddd"
}
}
}
Selections can also drive "filter" transforms, allowing for aggregating or otherwise transforming only the selected data points. The example below shows how to aggregate only the brushed penguins from the Palmer Penguins dataset.
{
"description": [
"Aggregating brushed penguins",
"Data: Gorman KB, Williams TD, Fraser WR (2014) Ecological Sexual Dimorphism and Environmental Variability within a Community of Antarctic Penguins (Genus Pygoscelis). PLoS ONE 9(3): e90081. doi:10.1371/journal.pone.0090081"
],
"height": 350,
"padding": 10,
"data": {
"url": "vega-datasets/penguins.json"
},
"transform": [{ "type": "collect" }],
"params": [{ "name": "brush" }],
"resolve": {
"scale": { "color": "shared", "y": "independent" }
},
"spacing": 30,
"hconcat": [
{
"title": { "text": "Palmer Penguins", "style": "overlay-title" },
"name": "scatterPlot",
"params": [
{
"name": "brush",
"select": { "type": "interval", "encodings": ["x", "y"] },
"push": "outer"
}
],
"width": 350,
"mark": { "type": "point", "filled": false, "size": 40, "opacity": 0.7 },
"encoding": {
"x": {
"field": "Beak Length (mm)",
"type": "quantitative",
"scale": { "zero": false, "padding": 0.1 }
},
"y": {
"field": "Beak Depth (mm)",
"type": "quantitative",
"scale": { "zero": false, "padding": 0.1 }
},
"color": {
"condition": {
"param": "brush",
"field": "Species",
"type": "nominal",
"scale": {
"domain": ["Chinstrap", "Adelie", "Gentoo"],
"range": ["#BF5CCA", "#FF6C02", "#0F7574"]
}
},
"value": "lightgrey"
}
}
},
{
"name": "barCharts",
"width": 200,
"transform": [
{
"type": "filter",
"param": "brush",
"fields": { "x": "Beak Length (mm)", "y": "Beak Depth (mm)" }
}
],
"resolve": {
"scale": { "x": "independent", "color": "shared" }
},
"encoding": {
"y": { "field": "count", "type": "quantitative" }
},
"vconcat": [
{
"name": "speciesChart",
"transform": [{ "type": "aggregate", "groupby": ["Species"] }],
"mark": "rect",
"encoding": {
"x": {
"field": "Species",
"type": "nominal",
"scale": {
"domain": ["Chinstrap", "Adelie", "Gentoo"],
"padding": 0.3
},
"axis": { "labelAngle": 0 }
},
"color": { "field": "Species", "type": "nominal" }
}
},
{
"name": "sexChart",
"width": 200,
"transform": [
{
"type": "filter",
"expr": "datum['Sex'] == 'MALE' || datum['Sex'] == 'FEMALE'"
},
{ "type": "aggregate", "groupby": ["Sex"] }
],
"mark": "rect",
"encoding": {
"x": {
"field": "Sex",
"type": "nominal",
"scale": { "domain": ["MALE", "FEMALE"], "padding": 0.3 },
"axis": { "labelAngle": 0 }
},
"color": { "value": "gray" }
}
}
]
}
]
}
Linking Scale Domains Across Views¶
An interval selection can drive scale domains in sibling views. To make this work with GenomeSpy's hierarchical parameter scopes:
- Define an empty parameter in a common ancestor.
- Define the brushing selection in a child view with the same
name. - Add
"push": "outer"so selection updates are written to the ancestor parameter. - Reference the parameter in a linked view via
scale.domain, for example:{ "param": "brush" }.
If the linked scale is zoomable, GenomeSpy automatically keeps the domain and selection synchronized in both directions. For non-zoomable linked scales, the selection only drives the domain.
Use initial on the linked scale.domain object to provide the configured
starting domain while the selection is empty. initial is only supported on
zoomable linked scales:
{
"scale": {
"zoom": true,
"domain": {
"param": "brush",
"initial": [10, 20]
}
}
}
initial participates in the configured domain of the linked scale. If the
linked interval selection is later cleared, the scale returns to its
normal default or data-derived domain instead of restoring initial.
GenomeSpy App persistence
In the GenomeSpy App, selection parameters are persisted in bookmarks,
URL hash state, and provenance history by default. In overview+detail
setups, this means the linked domain is restored through the selection
state. Use persist: false when the brush is only auxiliary UI and should
not affect saved state.
For app-specific state sharing and persistence, see Visualizing Sample Collections.
Zoomable Linking Example¶
{
"description": "Two-way interval linking example.",
"params": [{ "name": "brush" }],
"resolve": {
"scale": { "x": "independent" }
},
"data": {
"sequence": { "start": 0, "stop": 101, "step": 1, "as": "x" }
},
"transform": [{ "type": "formula", "expr": "sin(datum.x / 7)", "as": "y" }],
"vconcat": [
{
"height": 80,
"params": [
{
"name": "brush",
"select": { "type": "interval", "encodings": ["x"] },
"push": "outer"
}
],
"mark": { "type": "point", "size": 20, "opacity": 0.35 },
"encoding": {
"x": { "field": "x", "type": "quantitative" },
"y": { "field": "y", "type": "quantitative" }
}
},
{
"mark": { "type": "point", "size": 55, "opacity": 0.75 },
"encoding": {
"x": {
"field": "x",
"type": "quantitative",
"scale": {
"zoom": true,
"domain": { "param": "brush" }
}
},
"y": { "field": "y", "type": "quantitative" }
}
}
]
}
Overview+detail Example¶
The example below shows an overview+detail view of a genome. The top view shows
the whole genome, while the bottom view shows a zoomed-in region.
"link" mark with a "diagonal" shape visually connects the
selected region in the overview to the detail view.
{
"description": [
"Genome overview + detail example.",
"The top view provides an overview of the genome, and the bottom view shows details for the brushed region in the overview.",
"The overview is implemented as a template that can be reused across different specs."
],
"assembly": "hg38",
"params": [{ "name": "brush" }],
"spacing": 0,
"vconcat": [
{ "import": { "template": "overview" } },
{
"name": "detail",
"data": {
"values": [
{ "chrom": "chr1", "start": 10000000, "end": 50000000 },
{ "chrom": "chr7", "start": 20000000, "end": 60000000 },
{ "chrom": "chr17", "start": 30000000, "end": 70000000 }
]
},
"encoding": {
"x": {
"chrom": "chrom",
"pos": "start",
"type": "locus",
"scale": {
"domain": {
"param": "brush",
"initial": [
{ "chrom": "chr6", "pos": 20000000 },
{ "chrom": "chr11", "pos": 40000000 }
]
}
},
"axis": {
"chromGrid": true
}
},
"x2": { "chrom": "chrom", "pos": "end" }
},
"mark": "rect",
"view": { "stroke": "lightgray" }
}
],
"templates": {
"overview": {
"resolve": { "scale": { "x": "excluded" } },
"spacing": 0,
"vconcat": [
{
"name": "chromosomes",
"height": 20,
"view": { "stroke": "#d0d0d0", "strokeZindex": 10 },
"cursor": "text",
"data": { "lazy": { "type": "axisGenome", "channel": "x" } },
"encoding": {
"x": {
"field": "continuousStart",
"type": "locus",
"axis": null,
"scale": { "zoom": false }
},
"x2": { "field": "continuousEnd" },
"text": { "field": "name" }
},
"layer": [
{
"encoding": {
"fill": {
"field": "odd",
"type": "nominal",
"scale": {
"domain": [true, false],
"range": ["#e8e8e8", "white"]
}
}
},
"mark": {
"type": "rect",
"tooltip": null
}
},
{
"mark": {
"type": "text",
"paddingX": 3,
"paddingY": 5,
"tooltip": null
}
}
],
"params": [
{
"name": "brush",
"persist": false,
"select": {
"type": "interval",
"encodings": ["x"],
"mark": {
"clip": false,
"zindex": 11,
"stroke": "#048",
"strokeOpacity": 0.6,
"fillOpacity": 0.02,
"fill": "#08F",
"shadowBlur": 5,
"shadowColor": "#08F",
"shadowOpacity": 0.8
}
},
"push": "outer"
}
]
},
{
"name": "link-decoration",
"height": 30,
"data": { "lazy": { "type": "axisGenome", "channel": "x" } },
"transform": [
{
"type": "aggregate",
"fields": ["continuousEnd"],
"ops": ["max"],
"as": ["genomeEnd"]
},
{ "type": "formula", "expr": "[0, datum.genomeEnd]", "as": "x2" },
{ "type": "flatten", "fields": ["x2"], "index": "side" },
{ "type": "collect" },
{
"type": "formula",
"expr": "brush.intervals.x ? brush.intervals.x[datum.side] : datum.x2",
"as": "x"
}
],
"encoding": {
"x": {
"field": "x",
"type": "locus",
"scale": { "zoom": false },
"axis": null
},
"x2": { "field": "x2" }
},
"mark": {
"type": "link",
"linkShape": "diagonal",
"y": 1,
"y2": 0,
"color": "#8CF"
}
}
]
}
}
}