Multiscale Composition¶
multiscale is a convenience macro for semantic zooming. It expands to a
layer composition with generated 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. Normal layer behavior still applies (inherited
encodings/data, scale resolution, and opacity multiplication with manually
specified child opacity).
By default, each child is wrapped with a zoom-driven opacity ramp that cross-fades adjacent levels. The ramp is tied directly to the zoom metric, so two adjacent levels remain partly visible while the zoom level is between their stops.
By default, channel selection is automatic:
- If both
xandyare available, the zoom metric is averaged. - If only one is available, that one is used.
- Scales that are not visible at the
multiscalescope (for example, independent descendant-local scales) are ignored.
For manual opacity control patterns, see
layer zoom-driven opacity.
Transitioned Stops¶
Set stops.transition to select one detail level at each stop and cross-fade
only while the selected level changes. See numeric
transitions for the transition options.
Unlike the default opacity ramp, this is a time-based cross-fade rather than a
persistent blend across a zoom range. After it settles, the selected level is
fully visible and all other levels are hidden.
Transitioned stops require an explicit "x" or "y" channel and cannot use
fade:
{
"stops": {
"channel": "x",
"values": [40000],
"transition": { "type": "lerp", "halfLife": 60 }
},
"multiscale": [
{ "name": "Overview", "mark": "rect" },
{ "name": "Detail", "mark": "point" }
]
}
{
"description": "A three-stage semantic zoom that cross-fades after selecting a discrete multiscale level.",
"view": { "stroke": "lightgray" },
"resolve": {
"scale": { "x": "shared" }
},
"encoding": {
"x": {
"field": "x",
"type": "quantitative",
"scale": { "zoom": true }
},
"y": { "field": "y", "type": "quantitative" }
},
"stops": {
"channel": "x",
"values": [1, 0.1],
"transition": { "type": "lerp", "halfLife": 100 }
},
"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 }
}
}
]
}
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.
stopsRequired- Type: (number | ExprRef)[] | FadedMultiscaleStops | TransitionedMultiscaleStops
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.
Stop Object Forms¶
FadedMultiscaleStops¶
channel- Type:
"x"|"y"|"auto"Which positional channel controls the stop metric.
-
"auto"averagesxandywhen both are available. -"x"uses only thexchannel. -"y"uses only theychannel.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 valuesRequired- Type: array
Stop values in descending order.
TransitionedMultiscaleStops¶
channelRequired- Type:
"x"|"y"Positional channel that controls the stop metric.
transitionRequired- Type: ParamTransition
Cross-fades stages in time after a stop selects a new detail level. The selected stage settles fully visible and all other stages settle hidden. This differs from
fade, which keeps adjacent stages partly visible within a zoom range.Transitioned stops require
channelto be either"x"or"y"and cannot be combined withfade. valuesRequired- 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.