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]".

{
  "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:

  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 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"
          }
        }
      ]
    }
  }
}