Scales, axes, and legends

An encoding says which data field controls a visual channel. A scale performs the conversion from data values to visible values. Axes and legends are guides that explain the conversion to the user.

Start with automatic defaults

GenomeSpy chooses scales and guides from the channel and data type. This chart does not configure any of them explicitly:

import genome_spy as gs

measurements = [
    {"sample": "control", "time": 1, "value": 2.1},
    {"sample": "control", "time": 2, "value": 2.8},
    {"sample": "control", "time": 3, "value": 3.2},
    {"sample": "treated", "time": 1, "value": 2.4},
    {"sample": "treated", "time": 2, "value": 3.5},
    {"sample": "treated", "time": 3, "value": 4.3},
]

automatic_chart = (
    gs.Chart(measurements)
    .mark_point(filled=True, size=90)
    .encode(
        x="time:Q",
        y="value:Q",
        color="sample:N",
    )
    .properties(title="Automatic scales and guides")
)

The quantitative x and y fields create continuous positional scales and axes. The nominal color field creates a discrete color scale and a legend. These defaults are usually the best starting point.

Domain and range

Every scale maps a domain to a range:

  • the domain contains data values, such as times from 1 to 3 or the sample names control and treated;

  • the range contains visible outputs, such as pixel positions or the colors blue and red.

For an x scale, the chart width supplies the pixel range. For a color scale, the range is a palette. GenomeSpy normally derives the domain from the data and chooses a suitable range for the channel.

Use .scale(...) when the automatic choice does not express the intended comparison. This example fixes the visible domains, assigns stable group colors, and makes the horizontal scale zoomable:

customized_chart = (
    gs.Chart(measurements)
    .mark_point(filled=True, size=90)
    .encode(
        x=gs.X("time:Q")
        .scale(domain=[0.5, 3.5], zoom=True)
        .axis(tickMinStep=1, grid=False)
        .title("Time point"),
        y=gs.Y("value:Q")
        .scale(domain=[1.5, 4.5], zero=False)
        .axis(grid=True, tickCount=4)
        .title("Response"),
        color=gs.Color("sample:N")
        .scale(
            domain=["control", "treated"],
            range=["#4c78a8", "#e45756"],
        )
        .legend(title="Sample group", orient="top", direction="horizontal"),
    )
    .properties(title="Selected scale and guide options")
)

The color mapping is now deterministic: [control, treated][blue, red]. This is useful when the same category must retain the same color across figures. The positional domain represents the intended analytical window rather than merely the smallest and largest values present in this small table.

Set only the options that communicate intent. For example, .scale(zero=False) prevents an otherwise useful zero baseline from flattening variation in measurements far from zero, while leaving the other scale choices automatic. The GenomeSpy documentation lists the scale types and every scale option in scale.

Axes explain positional scales

GenomeSpy creates axes for scaled x and y field encodings. Axis options control the guide, not the data mapping:

gs.Y("value:Q").axis(grid=True, tickCount=4).title("Response")

Common axis adjustments include:

  • grid=True to support comparison across the plot;

  • tickCount=4 to request a readable tick density;

  • tickMinStep=1 when fractional steps would be misleading;

  • orient="right" or orient="top" to move the guide;

  • format=".1f" to format numeric labels.

tickCount is a request rather than an exact count. GenomeSpy may choose a nearby set of round, readable values. A channel .title(...) supplies the default axis title; .axis(title=...) can override it when necessary. See ticks, labels, and grid lines for the remaining axis options.

Zoomable positional scales

Set zoom=True on a positional scale to allow its visible domain to change interactively:

gs.X("time:Q").scale(domain=[0.5, 3.5], zoom=True)

Zooming applies to quantitative, index, and locus scales. Nominal and ordinal positional scales represent categorical data and do not support interactive zooming, so leave zoom unset for them.

The customized chart above uses a zoomable horizontal scale. Zoom extent and domain transitions are described in zooming and panning.

Legends explain visual scales

Color, size, shape, and opacity encodings can produce legends. Use .legend(...) to adjust presentation without changing the scale:

gs.Color("sample:N").legend(
    title="Sample group",
    orient="top",
    direction="horizontal",
)

The title states what the categories mean, orient selects the side of the plot, and direction controls how entries are arranged within the legend. Continuous quantitative colors use a gradient legend; discrete categories use symbols. Symbol and gradient legends, placement, and styling are documented in legend.

Remove a guide with None

An axis or legend can be redundant when the same information is already clear from labels, surrounding tracks, or another shared guide:

minimal_chart = (
    gs.Chart(measurements)
    .mark_point(filled=True, size=90)
    .encode(
        x=gs.X("time:Q").axis(None),
        y=gs.Y("value:Q").scale(zero=False).title("Response"),
        color=gs.Color("sample:N").legend(None),
    )
    .properties(title="Guides can be removed independently")
)

.axis(None) and .legend(None) hide guides but retain their scales. The points therefore keep their positions and colors. By contrast, .scale(None) disables the scale itself and sends values directly to the visual channel; use that only when the data already contains suitable visual values.

Removing a guide also removes its explanation. Keep at least one clear label or guide for every visual distinction the user must interpret. The GenomeSpy documentation covers the alternatives in disabling legends.