Skip to content

Conditional Encoding

Conditional encoding changes a visual channel when a selection parameter matches a row. The channel's own definition is the fallback:

{
  "encoding": {
    "color": {
      "condition": { "param": "brush", "value": "#3a86ff" },
      "value": "#d9d9d9"
    }
  }
}

Conditional encoding works with color, opacity, size, shape, and other visual channels. Point selections suit clicks and hovering:

{
  "description": [
    "Hover and click bar selection",
    "Highlights bars on hover and selects them 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 }
      ]
    }
  }
}

Empty Selections

An empty selection matches every row by default. Add "empty": false to a condition when its style should apply only after a selection, for example:

{ "param": "select", "empty": false, "value": 2 }

Combining Selections

Use test to combine selections with nested and, or, and not. Each leaf can set its own empty policy:

{
  "test": {
    "or": [
      { "param": "hover", "empty": false },
      { "and": [{ "param": "sourceBrush" }, { "param": "targetBrush" }] }
    ]
  },
  "value": "#3a86ff"
}

A leaf's empty defaults to true and is evaluated before the Boolean operators. A single "test": { "param": "brush" } is equivalent to the direct "param": "brush" condition.

A two-axis brush is empty until both intervals are active. Declare a one-axis brush to select on one axis.

For a simple union, use param.or inside test:

{
  "test": { "param": { "or": ["select", "brush"] }, "empty": true },
  "value": "#3a86ff"
}

The union matches rows in any active selection. With empty: true (the default), it also matches every row while all selections are empty. Set empty: false inside test to keep the fallback in that case.

Reusing Tests in a Unit View

When several channels use the same test, define it once in the unit view's predicates and refer to it from each condition, including order:

{
  "mark": "point",
  "predicates": {
    "highlighted": {
      "or": [
        { "param": "hover", "empty": false },
        { "param": "brush", "empty": false }
      ]
    }
  },
  "encoding": {
    "fillOpacity": {
      "condition": { "test": { "ref": "highlighted" }, "value": 1 },
      "value": 0.2
    },
    "order": {
      "condition": { "test": { "ref": "highlighted" }, "value": 1 },
      "value": 0
    }
  }
}

Each consuming unit must define names referenced by its encodings, including inherited encodings. Selection parameters and projected channels resolve in that unit. Named predicates cannot refer to other named predicates.

Matching Categories Across Views

A category selection declared with select.encodings: ["color"] tests the consuming view's color input by default. Views can use different field names for the same category concept without an explicit mapping. To read that concept from another categorical channel, map the stored component to the consumer input:

{ "param": "activation_states", "project": { "color": "shape" }, "value": 1 }

Here shape must encode activation state. project only chooses the input to compare; it does not convert rendered colors into shapes or change stored membership. Comparing activation states to unrelated cell-type labels does not join the concepts. Use two named selections with test.and for their intersection.

Categorical positional channels work the same way. For a selection declared with select.encodings: ["x"] on a band scale, "project": {"x": "shape"} links the selected x categories to shapes in another plot. Likewise, categories selected through color can be tested against a categorical y input with "project": {"color": "y"}.

Each mapped input must be an unconditional nominal or ordinal field encoding with a discrete scale. Identity uses raw typed values, independently of scale domains, palettes, and shape codes. These rules also apply to conditional order and named/logical predicates. Interval mappings retain the same-axis endpoint and data-type requirements below.

An interval normally tests the matching positional channel and, on ranged marks, its second endpoint according to the mark's hit-test mode. Use "project": { "x": "x2" } to test the selected x interval against x2 alone. Predicates may test the same selection against different endpoints.

project is a GenomeSpy extension. Map every component declared by the selection to an unconditional field encoding on the same axis and of the same type. Quantitative, index, and locus types are supported.

In this example, the upper brush tests each link's x2 (target), and the lower brush tests x (source). Links keep their color when they match both; an empty brush leaves its endpoint unconstrained.

{
  "description": "Brush both ends of a link",

  "data": {
    "values": [
      { "source": 80, "target": 730 },
      { "source": 230, "target": 420 },
      { "source": 380, "target": 880 },
      { "source": 530, "target": 230 },
      { "source": 680, "target": 580 },
      { "source": 830, "target": 100 }
    ]
  },

  "params": [{ "name": "targetBrush" }, { "name": "sourceBrush" }],

  "scales": { "x": { "domain": [0, 1000] } },

  "resolve": { "scale": { "x": "shared" }, "axis": { "x": "shared" } },

  "spacing": 0,

  "vconcat": [
    {
      "height": 30,
      "cursor": "text",
      "params": [
        {
          "name": "targetBrush",
          "push": "outer",
          "select": {
            "type": "interval",
            "encodings": ["x"],
            "on": "mousedown"
          }
        }
      ],
      "mark": { "type": "point", "size": 120 },
      "encoding": { "x": { "field": "target", "type": "index" } }
    },
    {
      "name": "links",
      "mark": { "type": "link", "linkShape": "diagonal" },
      "encoding": {
        "x": { "field": "source", "type": "index" },
        "x2": { "field": "target" },
        "y": { "value": 0 },
        "y2": { "value": 1 },
        "color": {
          "condition": {
            "test": {
              "and": [
                { "param": "targetBrush", "project": { "x": "x2" } },
                { "param": "sourceBrush", "project": { "x": "x" } }
              ]
            },
            "value": "#2962a3"
          },
          "value": "#d8dbe0"
        }
      }
    },
    {
      "height": 30,
      "cursor": "text",
      "params": [
        {
          "name": "sourceBrush",
          "push": "outer",
          "select": {
            "type": "interval",
            "encodings": ["x"],
            "on": "mousedown"
          }
        }
      ],
      "mark": { "type": "point", "size": 120 },
      "encoding": { "x": { "field": "source", "type": "index" } }
    }
  ]
}

For endpoint brushes, hover, and several conditional channels, see the PISA Squid Plot.

Multiple Conditions

Conditions in an array are tested in order. The channel's main definition is the final fallback:

{
  "condition": [
    { "param": "select", "empty": false, "value": 2 },
    { "param": "highlight", "empty": false, "value": 1 }
  ],
  "value": 0
}

See Also

  • Marks for the general encoding model
  • Parameters for defining selection and input-bound params