genome-spy-python is a Python interface for
GenomeSpy,
a grammar for interactive and scalable genomic visualization. It lets Python
users build GenomeSpy specifications with a declarative, fluent API, serialize
them to JSON, and display them in Jupyter or Marimo notebooks.
Read the documentation for the getting-started guide, user guide, examples, and API reference.
Altair is the project's main source of inspiration. This codebase follows Altair's approach of combining schema-backed specification objects with a small handwritten Python API for marks, encodings, composition, and rendering. It adapts that model to GenomeSpy's genomics-native grammar: locus scales, genomic data sources, and coordinated genomic views.
The project is under active development. The current focus is the reusable GenomeSpy Core grammar and notebook rendering; GenomeSpy App-specific features will come later.
The package requires Python 3.11 or newer.
From PyPI:
pip install genome-spy-pythonFrom source:
pip install uv
git clone https://github.com/genome-spy/genome-spy-python.git
cd genome-spy-python
uv syncFor notebook use, install with Arrow support. This includes PyArrow for dataframe transport:
pip install "genome-spy-python[arrow]"See creating and updating charts in notebooks for supported tables and live updates.
See the getting-started guide for the first example.
A zoomable Manhattan plot using the bundled HapMap data (requires pandas):
import genome_spy as gs
from genome_spy.datasets import load_dataset
chart = (
# Load data.
gs.Chart(load_dataset("hapmap_gwas"))
# Format chromosome names.
.transform_formula(expr="datum.CHR == 23 ? 'chrX' : 'chr' + datum.CHR", as_="chrom")
# Calculate −log10 p.
.transform_formula(expr="-log(datum.P) / log(10)", as_="neglogp")
# Draw variants.
.mark_point(size=12)
# Set positions and colors.
.encode(
x=gs.Locus("chrom", "BP").scale(assembly="hg18"),
y=gs.Y("neglogp:Q").title("−log10 p"),
color=gs.Color("CHR:N").scale(range=["#5b8fd6", "#8f98a3"]).legend(None),
)
)
chartExplore the full example and data provenance.
A sequence logo and aligned sequences with shared horizontal zoom:
import genome_spy as gs
logo = (
gs.Chart()
# Count bases at each position.
.transform_aggregate(groupby=["pos", "sequence"])
# Handle gaps.
.transform_formula(expr="datum.sequence == '-' ? null : datum.sequence", as_="base")
# Stack bases by information content.
.transform_stack(
field="count",
groupby=["pos"],
offset="information",
baseField="base",
as_=["y0", "y1"],
)
# Draw logo letters.
.mark_text(logoLetters=True, fitToBand=True, fontWeight="bold")
# Set stack bounds.
.encode(
y=gs.Y("y0:Q").scale(domain=[0, 2], zoom=False).title("Bits"),
y2="y1:Q",
text="base:N",
)
.properties(height=100)
)
# Create sequence rows.
rows = gs.Chart().encode(y=gs.Y("identifier:N").scale(zoom=False).axis(None))
# Add base labels.
letters = rows.mark_text(size=11, fitToBand=True, opacity=0.7).encode(
text="sequence:N", color=gs.value("black")
)
# Layer tiles and labels.
sequences = (rows.mark_rect() + letters).properties(
height=gs.step(16), viewportHeight=160
)
chart = (
# Combine panels.
(logo & sequences)
# Load data.
.properties(
data=gs.Data(
url="https://data.genomespy.app/sample-data/16SRNA_Deino_87seq.aln",
format=gs.data_format(type="fasta"),
)
)
# Split sequences into bases.
.transform_flatten_sequence()
# Set positions and colors.
.encode(
x=gs.X("pos:I").scale(domain=[190, 310], zoom=True).title(None),
color=gs.Color("sequence:N")
.scale(
domain=list("ACTGN-"),
range=["#4FBF45", "#4D96E8", "#E85F78", "#E8B322", "#BDBDBD", "#f5f5f5"],
)
.legend(None),
)
# Share zoom and colors.
.resolve_scale(x="shared", color="shared")
.resolve_axis(x="shared")
)
chartCharts can be serialized to a portable GenomeSpy specification or standalone HTML:
# Export JSON.
chart.to_json()
# Save HTML.
chart.save("chart.html")For reactive Jupyter or Marimo notebooks, create a widget with an explicitly named dataset and replace that dataset as inputs change. The browser keeps the existing GenomeSpy instance, so view state such as zoom is preserved.
chart = (
# Create an empty chart.
gs.Chart(data={"name": "table"}, datasets={"table": []})
.mark_point()
.encode(x="x:Q", y="y:Q")
)
# Show widget.
view = chart.widget()
# Update data.
view.set_dataset("table", updated_dataframe)See creating and updating charts in notebooks for the Marimo pattern.
Contributions are welcome. See CONTRIBUTING.md for development setup, testing, code generation, documentation, gallery, and pull-request guidelines.
- GenomeSpy — the upstream visualization grammar and JavaScript renderer.
- Altair — a schema-wrapper design reference.
- Gos — a related grammar and Python-wrapper design reference for genomics visualization.
Portions of the schema-wrapper implementation and selected tests are adapted from Altair under its BSD-3-Clause license. See Third-party notices for the exact sources and license.

