Skip to content

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:

  • false when any brushed channel uses a zoomable scale (to avoid wheel-gesture conflicts)
  • true otherwise

You can override the behavior with select.zoom: true/false or an explicit wheel event definition such as "zoom": "wheel[event.altKey]".

In concatenated views, select.extent controls where the selection rectangle is drawn. The default "auto" draws one rectangle across the concat container when the selected channel uses aligned projections in the child views. Otherwise, it draws a rectangle in each participating view. "container" requires the spanning rectangle, and "view" always draws per-view rectangles.

{
  "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" },
    "legend": { "color": "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"]
            },
            "legend": { "orient": "top-right", "offset": 0 }
          },
          "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", "legend": null }
          }
        },
        {
          "name": "sexChart",
          "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:

  1. Define an empty parameter in a common ancestor.
  2. Define the brushing selection in a child view with the same name.
  3. Add "push": "outer" so selection updates are written to the ancestor parameter.
  4. Reference the parameter in a linked scale domain, for example: { "param": "brush" }. The domain can be placed in encoding.<channel>.scale.domain or, for composed views, in a view-level scales.<channel>.domain.

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 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. The linked domain is configured at the root using scales.x.domain, and its initial value sets the detail view's starting domain. The overview template uses resolve.scale.x: "excluded" so its own x scales are not affected by the detail domain. A "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.",
    "The overview's x scale is excluded from the root scale domain, which is linked to the brush parameter and initializes the detail view."
  ],

  "assembly": "hg38",

  "params": [{ "name": "brush" }],

  "scales": {
    "x": {
      "domain": {
        "param": "brush",
        "initial": [
          { "chrom": "chr6", "pos": 20000000 },
          { "chrom": "chr11", "pos": 40000000 }
        ]
      }
    }
  },

  "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",
          "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",
                  "legend": null,
                  "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"
          }
        }
      ]
    }
  }
}

Ruler Parameters

Ruler parameters track one domain coordinate per configured positional channel and display it as a guide in compatible views. They are useful for cursor readouts, genome-browser-style center coordinates, and sibling views that compute from a shared cursor coordinate.

Rulers are not selections. They store a tagged parameter value such as:

{
    type: "ruler",
    values: {
        x: 12.5
    }
}

Rulers can be pointer-driven, following the mouse pointer, or viewport-driven, following the center of the current scale viewport.

Pointer-driven rulers use ruler.on:

  • "mousemove" follows the pointer and clears on mouse leave by default.
  • "mousedown" updates on press and continues while dragging. Use an event filter such as "mousedown[event.shiftKey]" when plain drag is used for zoom or pan gestures.

Viewport-driven rulers use "source": "viewport" and track the center of the current scale viewport. A viewport ruler must not define on and does not clear on mouse leave.

Ruler Guides in Descendant Views

Rulers can be shown separately in each view with a compatible scale or drawn across a container (hconcat or vconcat) when the participating views share the relevant scale and layout alignment.

The example below shows a pointer-driven ruler drawn inside two views with independent x scales.

{
  "description": "Ruler parameter example.",

  "params": [
    {
      "name": "cursor",
      "persist": false,
      "ruler": {
        "encodings": ["x"],
        "mark": { "stroke": "red" }
      }
    }
  ],

  "data": {
    "values": [
      { "x": 10, "x2": 40, "y": 1 },
      { "x": 50, "x2": 90, "y": 1 },
      { "x": 30, "x2": 70, "y": 2 }
    ]
  },

  "encoding": {
    "x": { "field": "x", "type": "index", "scale": { "domain": [0, 100] } },
    "x2": { "field": "x2" },
    "y": { "field": "y", "type": "ordinal" }
  },

  "resolve": { "scale": { "x": "independent" } },

  "vconcat": [
    { "mark": "rect", "title": "Move your mouse over the plot!" },
    { "mark": "rect" }
  ],

  "config": {
    "view": { "stroke": "lightgray" },
    "scale": { "ordinal": { "padding": 0.2 } }
  }
}

Pushing Ruler Values to Ancestor Scopes

Like selection parameters, ruler parameters can use push: "outer" to update a parameter declared in an ancestor scope. This allows one view to provide the coordinate while a sibling view reads the same parameter in expressions, transforms, or encodings.

{
  "description": "A child ruler pushes a coordinate to an outer parameter used by a sibling view.",

  "params": [{ "name": "cursor" }],

  "vconcat": [
    {
      "height": 25,
      "data": { "values": [{}] },
      "mark": {
        "type": "text",
        "text": { "expr": "'x = ' + cursor.values.x" }
      }
    },
    {
      "title": {
        "text": "Move your mouse here!",
        "style": "overlay-title"
      },
      "data": {
        "values": [
          { "x": 1, "y": 2 },
          { "x": 2, "y": 4 },
          { "x": 3, "y": 3 },
          { "x": 4, "y": 7 },
          { "x": 5, "y": 5 }
        ]
      },
      "params": [
        {
          "name": "cursor",
          "push": "outer",
          "ruler": {
            "encodings": ["x"],
            "mark": { "stroke": "gray", "strokeDash": [4, 4] }
          }
        }
      ],
      "mark": "point",
      "encoding": {
        "x": { "field": "x", "type": "quantitative" },
        "y": { "field": "y", "type": "quantitative" }
      }
    }
  ],

  "config": { "view": { "stroke": "lightgray" } }
}

Properties

ruler supports the following properties:

clear

Type: mouseleave | mouseup | False

Event that clears the ruler, or false to keep the current value.

Default value: "mouseleave" for on: "mousemove", otherwise false.

display

Type: "line" | "center" | "band" | "none"

How the ruler is drawn for snapped index or locus coordinates.

"line" draws at the coordinate. "center" draws at the center of the coordinate band. "band" draws a rectangle covering the coordinate band. "none" tracks the ruler value without drawing a guide.

Default value: "center" for snapped index and locus scales, otherwise "line".

encodings

Type: array

Positional channels whose domain coordinates are tracked by the ruler.

Default value: ["x"]

extent

Type: "auto" | "view" | "container"

Visual extent of the ruler.

"view" draws one guide per participating view. "container" draws one spanning guide when participating projections align. "auto" chooses a spanning guide only when it is safe.

Default value: "auto"

mark

Type: RulerMarkConfig

Rule or band appearance. Has no effect when display is "none".

on

Type: "mousemove" | "mousedown" | RulerEventConfig | string

Event that updates a pointer-driven ruler.

"mousemove" follows the pointer. "mousedown" updates on press and continues while dragging. Event filters can require modifier keys.

Default value: "mousemove"

snap

Type: auto | integer | False

Quantization applied before writing the ruler value.

"auto" snaps index and locus scales to integer coordinates. "integer" snaps all numeric coordinates. false keeps the original coordinate.

Default value: "auto" for index and locus scales, otherwise false.

source

Type: "pointer" | "viewport"

Source of the ruler coordinate. "pointer" uses pointer events configured by on. "viewport" tracks the center of the current viewport.

Default value: "pointer"