5. Sectors & circular coordinates
Named axis partitions, ring layouts, and chord diagrams — the genomics / Circos toolkit, from the same chart API. Every snippet on this page is executed on every build — the figures are its output.
Sectors: named partitions of an axis
c.sectors({...}) splits the x-axis into
named, length-weighted regions with walls and labels. Each region gets
its own local coordinates; a tag column routes every row to its
region.
import random
rng = random.Random(4)
regions = {"promoter": 100, "gene body": 400, "UTR": 150}
data = {"region": [], "pos": [], "signal": []}
for name, length in regions.items():
for _ in range(12):
data["region"].append(name)
data["pos"].append(rng.uniform(0, length))
data["signal"].append(rng.uniform(0.2, 0.9))
c = pt.chart(data, aes(x="pos", y="signal"), data_width=460,
data_height=130, ylim=(0, 1), ylabel="signal")
c.sectors(regions, column="region")
c.add_scatter(size=3, color="#534AB7")
Categorical sectors and gaps
Sectors can also group existing categories:
{"group": ["cat", ...]} partitions a categorical axis into
labeled blocks. A prebuilt pt.Sectors(...) value adds
control — gap= for whitespace between blocks,
divider=False / label=False to drop the
chrome.
df = {"cat": list("abcdefgh"), "val": [3, 5, 2, 6, 4, 7, 3, 5]}
c = pt.chart(df, aes(x="cat", y="val", fill="cat"), data_width=420,
data_height=170, ylabel="count")
c.sectors({"G1": ["a", "b", "c"], "G2": ["d", "e"],
"G3": ["f", "g", "h"]}, axis="x")
c.add_bar(palette="Set2", legend=False)
Stacked tracks
Declared on a layout, .sectors(...)
applies to every track at once — combined with
.share_x("col") this is the genome-browser shape: aligned
tracks, one sector chrome.
import random
rng = random.Random(4)
regions = {"chr1": 100, "chr2": 80, "chr3": 60}
pts = {"chrom": [], "pos": [], "score": []}
for name, length in regions.items():
for _ in range(10):
pts["chrom"].append(name)
pts["pos"].append(rng.uniform(0, length))
pts["score"].append(rng.uniform(0.1, 0.9))
cov = {"chrom": pts["chrom"], "pos": pts["pos"],
"cov": sorted(pts["score"])}
t1 = pt.chart(pts, aes(x="pos", y="score"), data_width=460,
data_height=70, ylabel="score", ylim=(0, 1))
t1.add_scatter(size=3, color="#534AB7")
t2 = pt.chart(cov, aes(x="pos", y="cov", group="chrom"),
data_width=460, data_height=70, ylabel="coverage",
ylim=(0, 1))
t2.add_line(color="#1D9E75")
c = (pt.grid([[t1], [t2]])
.share_x("col")
.sectors(regions, column="chrom"))
Wrap it on a circle
A coordinate system is a per-panel property.
c.coordinate(pt.CircularCoordinate()) wraps the same chart
onto a ring — x becomes angle, y becomes radius, and every artist
warps through: bars, lines, boxplots, even dendrograms.
df = {"cat": list("abcdefgh"), "val": [3, 5, 2, 6, 4, 7, 3, 5]}
c = pt.chart(df, aes(x="cat", y="val", fill="cat"))
c.coordinate(pt.CircularCoordinate())
c.add_bar(legend=False)
Tuning the ring
r_inner= reserves a center hole (0
→ full disc, 0.9 → thin band);
wrap_gap_deg= leaves a wedge of whitespace at 12
o’clock so the axis has visible ends. Sectors compose with the
warp — wedges, walls and per-sector arcs.
import math
xs = [i / 48 for i in range(49)]
wave = {"x": xs, "y": [0.5 + 0.35 * math.sin(4 * math.pi * x)
for x in xs]}
c = pt.chart(wave, aes(x="x", y="y"), xlim=(0, 1), ylim=(0, 1),
title="r_inner=0.5, wrap_gap_deg=20")
c.coordinate(pt.CircularCoordinate(r_inner=0.5, wrap_gap_deg=20))
c.add_line(color="#1D9E75")
Chords through the center
The hole is usable space: pass another chart as
inner= and it renders in the central disc, inheriting the
sector partition — that’s where
add_chord_links (arcs) and add_chord_ribbon
(filled ribbons) live in a Circos-style figure. See the
chord recipe for the full recipe.
sectors = pt.Sectors(names=["A", "B", "C"], lengths=[30, 25, 20], gap=4)
span = (0, sectors.total())
links = {"src": ["A", "A", "B"], "dst": ["B", "C", "C"],
"x1a": [0, 18, 0], "x1b": [10, 28, 15],
"x2a": [0, 0, 0], "x2b": [10, 8, 12]}
arcs = pt.chart(links, aes(x1_start="x1a", x1_end="x1b",
x2_start="x2a", x2_end="x2b",
x1_sector="src", x2_sector="dst",
color="src"), xlim=span)
arcs.sectors(sectors, column="src", label=False)
arcs.add_chord_ribbon(alpha=0.6)
ring = pt.chart(xlim=span, ylim=(0, 1))
ring.sectors(sectors, column="x")
c = pt.grid([[ring]]).coordinate(
pt.CircularCoordinate(r_inner=0.85, inner=arcs))