plotlet v0.6.2

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)
["a","b","c","d","e","f","g","h"]

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)
["a","b","c","d","e","f","g","h"]

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))

← Multi-panel composition  ·  Color & themes →