Skip to content

Layering Views

The layer operator overlays multiple views in the same space. By default, layers share coordinate space and scales, which makes it straightforward to combine complementary marks (for example, bars and labels) into one composite view.

Example

{
  "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 }
    ]
  },
  "encoding": {
    "x": {
      "field": "a",
      "type": "nominal",
      "scale": { "padding": 0.1 },
      "axis": { "labelAngle": 0 }
    },
    "y": { "field": "b", "type": "quantitative" }
  },
  "layer": [
    {
      "name": "Bar",
      "mark": "rect"
    },
    {
      "name": "Label",
      "mark": { "type": "text", "dy": -9 },
      "encoding": {
        "text": { "field": "b" }
      }
    }
  ]
}

To specify multiple layers, use the layer property:

{
  "layer": [
    ...  // Single or layered view specifications
  ]
}

The provided array may contain both single view specifications and layer specifications. The encodings and data that are specified in a layer view propagate to its descendants. For example, in the above example, the "Bar" and "Label" views inherit the data and encodings for the x and y channels from their parent, the layer view.

Zoom-driven layer opacity

Layer (and unit) views support zoom-dependent opacity using opacity with unitsPerPixel and values. This is useful for semantic zooming where one layer is visible when zoomed out and another appears when zoomed in. unitsPerPixel means data-units per screen pixel. With genomic locus scales, you can read it as base pairs per pixel.

If layers are alternative zoom states (even just overview + detail), prefer multiscale. Use direct opacity when layers are additive and meant to be visible together.

{
  "opacity": {
    "unitsPerPixel": [100000, 40000],
    "values": [0, 1]
  }
}

The opacity is interpolated between the stops. In the example above, the layer is invisible at 100000 units/pixel and fully visible at 40000 units/pixel. Outside the range, the nearest stop value is used.

unitsPerPixel can also be expression-driven:

{
  "opacity": {
    "unitsPerPixel": [
      { "expr": "windowSize / max(width, 1)" },
      { "expr": "0.5 * windowSize / max(width, 1)" }
    ],
    "values": [0, 1]
  }
}

Cross-fading overview and detail layers

Use opposite stop orders in two layers to cross-fade between them while zooming:

{
  "layer": [
    {
      "name": "Overview",
      "opacity": {
        "unitsPerPixel": [100000, 40000],
        "values": [1, 0]
      },
      "mark": "rect"
    },
    {
      "name": "Detail",
      "opacity": {
        "unitsPerPixel": [100000, 40000],
        "values": [0, 1]
      },
      "mark": "point"
    }
  ]
}

Resolve

By default, layers share their scales and axes, unioning the data domains.

More examples

Lollipop plot

This example layers two marks to create a composite mark, a lollipop. Yet another layer is used for the baseline.

{
  "name": "The Root",
  "description": "Lollipop plot example",

  "layer": [
    {
      "name": "Baseline",
      "data": { "values": [0] },
      "mark": "rule",
      "encoding": {
        "y": { "field": "data", "type": "quantitative", "title": null },
        "color": { "value": "lightgray" }
      }
    },
    {
      "name": "Arrows",

      "data": {
        "sequence": {
          "start": 0,
          "stop": 6.284,
          "step": 0.39269908169,
          "as": "x"
        }
      },

      "transform": [
        { "type": "formula", "expr": "sin(datum.x)", "as": "sin(x)" }
      ],

      "encoding": {
        "x": { "field": "x", "type": "quantitative" },
        "y": {
          "field": "sin(x)",
          "type": "quantitative",
          "scale": { "padding": 0.1 }
        },
        "color": { "field": "sin(x)", "type": "quantitative" }
      },

      "layer": [
        {
          "name": "Arrow shafts",

          "mark": {
            "type": "rule",
            "size": 3
          }
        },
        {
          "name": "Arrowheads",

          "mark": {
            "type": "point",
            "size": 500,
            "filled": true
          },

          "encoding": {
            "shape": {
              "field": "sin(x)",
              "type": "nominal",
              "scale": {
                "type": "threshold",
                "domain": [-0.01, 0.01],
                "range": ["triangle-down", "diamond", "triangle-up"]
              }
            }
          }
        }
      ]
    }
  ]
}