Skip to content

Multiscale Composition

multiscale is a convenience macro for semantic zooming. It expands to a layer composition with generated zoom-driven opacity transitions between child views.

Example

{
  "description": "A three-stage semantic zoom using the multiscale composition operator.",
  "view": { "stroke": "lightgray" },

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

  "encoding": {
    "x": {
      "field": "x",
      "type": "quantitative",
      "scale": { "zoom": true }
    },
    "y": {
      "field": "y",
      "type": "quantitative"
    }
  },

  "stops": [1, 0.1],

  "multiscale": [
    {
      "data": { "values": [{}] },
      "mark": {
        "type": "text",
        "text": "Zoom in to see details",
        "size": 16
      },
      "encoding": {
        "x": { "value": 0.5 },
        "y": { "value": 0.5 },
        "color": { "value": "#666666" }
      }
    },
    {
      "data": {
        "sequence": { "start": 0, "stop": 4000, "step": 16, "as": "x" }
      },
      "transform": [
        {
          "type": "formula",
          "expr": "sin(datum.x / 40)",
          "as": "y"
        },
        {
          "type": "formula",
          "expr": "datum.x + 16",
          "as": "x2"
        }
      ],
      "mark": "rect",
      "encoding": {
        "x2": { "field": "x2" }
      }
    },
    {
      "data": {
        "sequence": { "start": 0, "stop": 4000, "step": 1, "as": "x" }
      },
      "transform": [
        {
          "type": "formula",
          "expr": "sin(datum.x / 40) + (random() - 0.5) * 0.2",
          "as": "y"
        }
      ],
      "mark": "point",
      "encoding": {
        "opacity": { "value": 0.7 }
      }
    }
  ]
}

How It Works

multiscale children are ordered from zoomed-out to zoomed-in. For N child views, stops must contain N - 1 values.

At compile time, multiscale is expanded into a regular layer: each child is wrapped with generated opacity ramps that cross-fade adjacent levels. Normal layer behavior still applies (inherited encodings/data, scale resolution, and opacity multiplication with manually specified child opacity).

By default, channel selection is automatic:

  1. If both x and y are available, the zoom metric is averaged.
  2. If only one is available, that one is used.
  3. Scales that are not visible at the multiscale scope (for example, independent descendant-local scales) are ignored.

For manual opacity control patterns, see layer zoom-driven opacity.

Schematic Two-Level Cross-Fade Example

This mirrors the layer cross-fading overview/detail example.

{
  "stops": [40000],
  "multiscale": [
    {
      "name": "Overview",
      "mark": "rect"
    },
    {
      "name": "Detail",
      "mark": "point"
    }
  ]
}

Properties

All other properties follow layer view semantics.

stops Required

Type: (number | ExprRef)[] | MultiscaleStops

Stop definition that controls transitions between the multiscale levels.

  • number[] is shorthand for { metric: "unitsPerPixel", values: ... }
  • (number | ExprRef)[] supports mixed constants and expressions
  • Object form allows configuring metric, channel, and fade.

MultiscaleStops

channel

Type: "x" | "y" | "auto"

Which positional channel controls the stop metric.

  • "auto" averages x and y when both are available.
  • "x" uses only the x channel.
  • "y" uses only the y channel.

Default value: "auto"

fade

Type: number

Relative transition width around each stop.

For each stop value s, the fade transition is evaluated in the range:

  • upper edge: s * (1 + fade)
  • lower edge: s * (1 - fade)

Default value: 0.5

values Required

Type: array

Stop values in descending order.

Array shorthand:

{
  "stops": [1, 0.1]
}

is equivalent to:

{
  "stops": {
    "metric": "unitsPerPixel",
    "values": [1, 0.1]
  }
}

Expression shorthands are also supported:

{
  "stops": [
    2000,
    { "expr": "windowSize / max(width, 1)" },
    { "expr": "0.2 * windowSize / max(width, 1)" }
  ]
}

unitsPerPixel means data-units per screen pixel. On genomic axes, this is typically base pairs per pixel.