Composition and Layering

Composition with *

In Deneb.jl, different components or sub-specifications of a Vega-Lite visualization can be defined separately and conveniently be composed using the * operator to build the final specification.

using Deneb
cars = Data(url="https://vega.github.io/vega-datasets/data/cars.json")
mark = Mark(:point)
encoding = Encoding("Horsepower:q", "Miles_per_Gallon:q", color=:Origin)

chart = cars * mark * encoding

New arbitrary properties of an existing spec can be added using the * operator. For instance, this examples adds a toplevel title property, title properties for the x and y encoding channels, and sets the shape of the marker to a triangle.

chart = chart * title("Consumption vs power") * Mark(shape=:triangle) * Encoding(
    x=(; title="Power [hp]"),
    y=(; title="Consumption [mi / gal]")
)

Order

Properties defined in the right-side spec have precedence over the left-side spec, meaning that if a given property is specified in both, then the result specification will use the property from the right-side spec.

chart * title("Power vs Displacement") * Encoding(
    x=field(:Displacement, title="Displacement [in³]")
)

Layering with +

Layered views, or charts that are superimposed one on top of another, can be conveniently created in Deneb.jl using the + operator.

stocks = Data(url="https://vega.github.io/vega-datasets/data/stocks.csv")
base = stocks * Encoding(
    "yearquarter(date):T",
    "mean(price):Q",
)

base * (Mark(:line) + Mark(:point) + Mark(:rule))

Alternatively, the layer method can also be used, with any number of specs as arguments.

base * layer(Mark(:line), Mark(:point), Mark(:rule))

Order

The order matters as it determines which spec is drawn first. For example, in spec1 + spec2, spec1 will appear below spec2.

movies = Data(url="https://vega.github.io/vega-datasets/data/movies.json")
heatmap = movies * Mark(:rect) * Encoding(
    x=field("IMDB Rating:Q", bin=true),
    y=field("Rotten Tomatoes Rating:Q", bin=true),
    color=field("count()", scale=(;scheme=:greenblue)),
)

points = movies * Mark(
    :circle, color=:black, size=5
) * Encoding(
    x="IMDB Rating:Q",
    y="Rotten Tomatoes Rating:Q",
)

heatmap + points

If we put the two layers in the opposite order, the points will be drawn first and will be obscured by the heatmap marks:

points + heatmap

Resolution

When you have different scales in different layers, the scale domains are unioned so that all layers can use the same scale. In the examples above, Vega-Lite automatically used common x- and y-axis. We can disable this by setting the resolve property.

The default resolutions for layer are shared scales, axes, and legends.

In Deneb.jl, the methods resolve_scale, resolve_axis, and resolve_legend are available to conveniently defined the desired resolution (shared or independent) of the respective channels. In the chart below, we set the y-scales of the different layers to be independent with resolve_scale(y=:independent).

base = cars * Encoding("year(Year):T") * Mark(:line)

line1 = Mark(color="#5276A7") * Encoding(
    y=field("average(Horsepower):Q", axis=(;titleColor="#5276A7"))
)

line2 = Mark(color="#F18727") * Encoding(
    y=field("average(Miles_per_Gallon):Q", axis=(;titleColor="#F18727"))
)

chart = base * (line1 + line2) * resolve_scale(y=:independent)

Layering multi-views

Multi-view layout specs (facet, repeat, concat) cannot be layered, but layered specs can be faceted/repeated/concatenated.

Examples

More example of layered visualizations can be found in the Layered-plots section of the Gallery.