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 |
|
Applies to every point |
Encoding |
|
Reads a value from each row |
View |
|
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 |
|---|---|---|
Individual observations and distributions |
||
Bands, intervals, and heatmaps |
||
Ranges, boundaries, and reference lines |
||
Compact positions along one axis |
||
Labels or values shown as text |
||
Connections between two positions |
||
Directed connections or events |
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.
Links and arrows connect rows¶
Links and arrows also use primary and secondary positions, but they connect the endpoints instead of showing a range along one axis:
relations = [
{"x": 1, "y": 1, "x2": 4, "y2": 3, "kind": "observed"},
{"x": 2, "y": 4, "x2": 6, "y2": 2, "kind": "predicted"},
{"x": 5, "y": 1, "x2": 8, "y2": 4, "kind": "observed"},
]
relation_base = gs.Chart(relations).encode(
x=gs.X("x:Q").scale(domain=[0, 9]).title("Source x"),
x2=gs.X2("x2"),
y=gs.Y("y:Q").scale(domain=[0, 5]).title("Source y"),
y2=gs.Y2("y2"),
color=gs.Color("kind:N").legend(title="Relation"),
)
link_chart = relation_base.mark_link(linkShape="diagonal", size=2).properties(
title="Links connect two positions"
)
arrow_chart = relation_base.mark_arrow(size=5, headWidth=3).properties(
title="Arrows add direction"
)
Use a link when the relationship has no direction. Use an arrow when the source-to-target direction matters. Both examples reuse the same encoded base; only their marks and view properties differ.
Mark methods return new chart objects, so a base can be reused safely as shown above. Repeated defaults across many charts can instead be set with GenomeSpy configuration, which is covered later in the user guide.