Click any figure for the code that produces it. Every example simulates with ZOMBI2 and plots with Phylustrator, a separate package — so to run one you need both:
pip install zombi2 phylustrator

Pure birth, no extinction — a forward tree of 100 lineages.
birth
The full history behind 50 survivors — their branches solid, extinct lineages dashed.
birth · death
Speciation runs slow, then fast, then slow — the burst packs branches between the two dashed regime lines.
birth · changing_at
Speciation slows as diversity fills up; the skyline rises and plateaus at the cap of 100.
birth · TotalDiversity
Each lineage starts at its parent's rate and is nudged from it, so close relatives run at similar speeds and one clade radiates while its sister stays sparse.
birth · Drift
The same distribution without the inheritance: every lineage draws for itself, so a fast lineage tells you nothing about its neighbours.
birth · LogNormal
A pulse at t = 3 culls 75% of lineages — the skyline drops sharply at the dashed line, then recovers.
mass_extinctions
The two ways a dataset is less than the tree that grew. Solid reaches the present and is in the data; dashed is never seen; a ring is alive today but not sampled; a diamond is a fossil, which can fall on any branch.
sampling · fossils
Two thousand trees under each of two processes. zombi2 tools tree --gamma separates them almost perfectly.

One family's history on the species tree: duplications (squares), losses (crosses), transfers (arrows, donor→recipient).
duplication · transfer · loss
A family × genome copy-number heatmap, its rows locked to the tips. beside(tree, heatmap(profiles)).

Transfers steered to run between two clades, by topology rather than by a trait. The barplot counts them by clade pair, so A↔B towers over within-clade.
transfer_to · Clades
Two runs at the same mean rates. Every family alike gives no core at all; letting families differ gives 28 core families and a U-shaped spectrum.
varying_among('families')
The shaded clade loses genes twenty times faster after the dashed line, and nothing about it differs before. It ends with about 140 genes against 270 outside — one factor, scoped to a group and to a time.
Clade · changing_at
A genome as a ring — genes evenly spaced by rank, coloured by family, arrows by strand. plot(g, layout="circular") + genes().

Two genomes, one per row; ribbons link same-family genes and cross where the order was rearranged. stack([a,b]) + synteny().

Every tip's gene order beside the tree. Genes are coloured by their ancestral position, so each rearrangement is a break in the gradient.
inversion
One inversion on a circular genome: the segment is reversed and its strands flip. The band marks it in both rings.
inversion · inversion_extent
Seven lineages from the three circular chromosomes drawn above the stem. Fission and fusion are marked on the branch where they happened, so the karyotypes are what those marks did.
fission · fusion
A real bacterium: Mycoplasma genitalium, 546 genes at their true base positions. The forward/reverse switch marks the replication origin.
nucleotide · --gff
A single-copy family across 20 species, residues coloured (with a nucleotide key), each row locked to its tip. beside(tree, alignment(aln)).

A rate says how fast a lineage evolves; the model says what the change looks like. One clade is pulled toward A and T, and its tips arrive at 0.20 GC against 0.50 outside — each on its own model's equilibrium.
model · Clade
The same twenty species evolving amino acids under lg, coloured by chemical class rather than one hue per residue, so a conserved column reads as conserved chemistry. Also poisson, jtt, dayhoff, wag.

Sites gained and lost as well as changed, so the alignment has gaps. A gap is a shared derived character like any other, which is why the columns of gaps line up with clades of the tree rather than scattering down it. insertion and deletion are given relative to substitution.

Every lineage draws its own rate, with no memory of its parent, so the colour is salt-and-pepper. substitution = PerSite().varying_among('lineages', LogNormal(0.0, 0.55)).

The same independent draw with a gamma instead of a lognormal. varying_among('lineages', Gamma(shape=3.31, scale=0.302)).

A daughter starts at its parent's rate and is nudged, so the colour moves in clades rather than branch to branch. substitution = PerSite().varying_among('lineages', Drift(LogNormal(0.0, 0.4))).

The same inherited drift in steps: the rate takes one of a few values and a daughter moves to a neighbouring one. varying_among('lineages', Drift(LogNormal(0.0, 0.45), bins=6)).

A small tree with its internal nodes numbered, and beside it the sequence at each. The rows are one per node, not aligned to the tips.
substitution · .ancestral
A real gene down twenty species. The pale band at 158–170 is one insertion, carried by exactly one of the two clades at the root — an indel is a shared derived character.
insertion · deletion
Free diffusion — sister lineages drift apart with time.
rate
Pulled to an optimum: a high start (yellow) converges to blue.
rate · pull · reverts_to
The diffusion rate falls a hundred-fold at t = 4.5, so the deep clades separate and then every one of them freezes. rate = PerLineage(2.0).changing_at({0: 1.0, 4.5: 0.01}).

The diffusion rate is inherited at each split and nudged, so whole clades wander while their sisters barely move — the trait-level reading of the same Drift a species tree can put on its birth rate.

The diffusion rate is divided by how full the tree is. The curve below shares the time axis: where the lineages level off, the colour stops changing.
rate · TotalDiversity
A discrete trait paints the tree and each painted clade reverts to its own optimum: upland to −4, lowland to 4. regimes= with a reverts_to per state.

All the change happens at the splits, none along the branches. A painted tree cannot show that, so the panel runs the test: sister tips that split long ago are no more different than sister tips that split recently, which is what diffusion cannot do.
at_speciation
A two-state trait hops between habitats; each branch is painted by its state history.
switch
One direction commoner than the other, written as a matrix of directed rates: the structure is gained ten times more readily than it is lost, so it spreads and only rarely goes back.
switch · directed
The state is which side of a threshold a continuous liability sits on, so relatives flip back and forth near the boundary. The crossings carry no times — the liability is what evolves — so a branch takes the state its far end is in.
liability · threshold
Two traits evolve together (r = 0.9) — two trees, coloured by each trait, and the tip scatter.
rate · correlated
The correlation carries the reversion as well: both traits drift together, and each is pulled to its own optimum at its own strength — x hard to 4, y gently to −4.
correlation · reverts_to
Two binary characters where one's flip rate depends on the other's state. X in green, Y in purple, so you can see Y is present where X is; the 2×2 chain is the model.
switch · dependent
A lifestyle trait drives gene loss. Endosymbionts shed genes faster and gain fewer, so their genomes end up a fifth the size.
trait → loss
A selection trait drives duplication. Under relaxed selection duplicates accumulate and the genomes grow fivefold.
trait → duplication
A competence trait drives who receives a transfer rather than a rate. Competent lineages take up DNA more often.
trait → transfer uptake
A diffusing trait drives gene gain. A Curve turns each value into a factor, so genome size follows the trait.

The same run with a different Curve. Gene gain rises with the trait and then levels off, so the factor is bounded.

The response is any function you write. Here it peaks at an intermediate value, which no table of per-state multipliers can express.
continuous trait → origination
Hot lineages invert fifteen times as often, and each inversion takes a longer run of genes. Rate and extent are separate targets and multiply.
trait → inversion
The same target reached from the other end of the map. A driver is read wherever it changes, so a lineage that switches halfway down accumulates at two rates.
trait → substitution
A habitat trait sets how fast body size diffuses: twenty times faster where the habitat fluctuates. rate = PerLineage(0.25).scaled_by(habitat, {…}).

A temperature trait is grown first; body size then diffuses at a rate that reads it. The scale is centred on where it started, so white means it has not moved.
trait → trait
One level conditioning itself. Carrying the element makes a lineage donate twenty-five times more often — a driven transfer is about giving, not receiving.
gene → transfer
A gene family drives the substitution rate. The same tree in time, then in substitutions: branches that have lost the mismatch-repair family run four times longer.
gene → substitution
Carrying a toxin family makes a lineage become pathogenic forty times faster. 74% of the tips with the gene end up pathogenic, against none of those without.
gene → trait
The driver is a fraction, not a yes/no. At the ordered resolution a loss takes a run of neighbours, so an operon goes in blocks and completion drops in steps.
module → substitution
How much of a four-gene module a lineage keeps decides its metabolism, through a step: lambda f: 20.0 if f > 0.5 else 1.0.

The repair operon again, now setting how fast a trait diffuses instead of how fast sequences evolve. One driver reaches every level on the tree.
module → trait rate
A sequence drives a sequence. It cannot drive the gene it grows inside — that would read a run’s own output — but it can drive a different one, run after it.
GC → substitution
The only pair whose driver is grown at the sequence level and whose target is not. gc() is a number, so the connection carries a Curve.

Gene content drives speciation, so the tree is an output. Loss runs at twice origination, yet the family reaches 83% of tips — its carriers split five times as often.
gene content → speciation
A two-state trait drives speciation — the fast state's clades take over; the inset is the state Markov chain.
trait → speciation
One state dies far faster; the doomed lineages (dashed) drop out.
trait → extinction
Three graded speciation rates with constant death — the fastest state fills the tree, extinct lineages dashed.
trait → speciation
The state drives how fast a lineage splits, and the split is where the state changes — the squares. Along the branches almost nothing happens (switch=0.08, at_speciation=0.15).