Charts and marks

A Chart combines data, one geometric shape called a mark, and encodings that control where and how those marks are drawn. A chart with one mark type is also called a unit view.

For most marks, GenomeSpy draws one mark for every data row. Six rows therefore produce six points in this example:

import genome_spy as gs

observations = [
    {"sample": "A", "time": 1, "value": 2.2, "amount": 18},
    {"sample": "A", "time": 2, "value": 3.1, "amount": 32},
    {"sample": "A", "time": 3, "value": 3.8, "amount": 24},
    {"sample": "B", "time": 1, "value": 1.7, "amount": 22},
    {"sample": "B", "time": 2, "value": 2.5, "amount": 15},
    {"sample": "B", "time": 3, "value": 3.3, "amount": 36},
]

point_chart = (
    gs.Chart(observations)
    .mark_point(filled=True, stroke="white", strokeWidth=1)
    .encode(
        x=gs.X("time:O").title("Time"),
        y=gs.Y("value:Q").scale(zero=False).title("Value"),
        color=gs.Color("sample:N").legend(title="Sample"),
        size=gs.Size("amount:Q").legend(title="Amount"),
    )
    .properties(
        title="Measurements by sample",
        description="Six measurements grouped by sample.",
    )
)

The call to mark_point() chooses the geometry. The calls inside encode() connect data fields to visual channels such as position, color, and size.

Static properties and data-driven encodings

The location of a property determines whether it is constant or varies with the data:

Level

Example above

Effect

Mark

filled=True, stroke="white"

Applies to every point

Encoding

color="sample:N", size="amount:Q"

Reads a value from each row

View

title=..., description=...

Describes the chart area as a whole

Put a visual value directly in the mark method when every instance should look the same. Put it in encode() when the value should represent a field. properties() is for view-level settings such as width, height, title, and the accessibility-oriented description. The GenomeSpy documentation lists the properties shared by all marks in marks.

Choose a mark for the visual task

Different marks emphasize different aspects of the data:

Mark method

Useful for

GenomeSpy reference

mark_point()

Individual observations and distributions

Point

mark_rect()

Bands, intervals, and heatmaps

Rect

mark_rule()

Ranges, boundaries, and reference lines

Rule

mark_tick()

Compact positions along one axis

Tick

mark_text()

Labels or values shown as text

Text

mark_link()

Connections between two positions

Link

mark_arrow()

Directed connections or events

Arrow

Marks can often express related tasks, so choose the one that makes the intended reading most direct. Each reference page lists the properties that mark supports.

The point mark and rect heatmap examples show two of them in full.

Ranged marks use secondary positions

A point needs one position on each axis. An interval needs a start and an end. The secondary channels x2 and y2 supply that second endpoint:

intervals = [
    {"feature": "A", "start": 1, "end": 4, "group": "first"},
    {"feature": "B", "start": 3, "end": 8, "group": "second"},
    {"feature": "C", "start": 7, "end": 11, "group": "first"},
]

interval_chart = (
    gs.Chart(intervals)
    .mark_rule(size=8, strokeCap="round")
    .encode(
        x=gs.X("start:Q").scale(domain=[0, 12]).title("Position"),
        x2=gs.X2("end"),
        y=gs.Y("feature:N").title("Feature"),
        color=gs.Color("group:N").legend(title="Group"),
    )
    .properties(title="Intervals have two endpoints")
)

Here, each rule begins at start and ends at end. A rectangle can likewise use x with x2, y with y2, or both pairs to fill an area. A tick is a compact rule centered on a single encoded position. Which marks support a second endpoint is documented in secondary channels.

Text is also a mark

Text becomes data-driven through the text encoding. The mark’s size=13 is constant, while each rendered label comes from the sample field:

text_chart = (
    gs.Chart(observations)
    .mark_text(size=13)
    .encode(
        x=gs.X("time:O").title("Time"),
        y=gs.Y("value:Q").scale(zero=False).title("Value"),
        text="sample:N",
        color="sample:N",
    )
    .properties(title="Text can represent a field")
)

Text marks work well for short labels. Dense labels can overlap, so points or rectangles are usually better for large datasets.