genoplot 0.1.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- genoplot-0.1.0a1/.gitattributes +2 -0
- genoplot-0.1.0a1/.gitignore +11 -0
- genoplot-0.1.0a1/LICENSE +21 -0
- genoplot-0.1.0a1/PKG-INFO +141 -0
- genoplot-0.1.0a1/PROGRESS.md +90 -0
- genoplot-0.1.0a1/README.md +128 -0
- genoplot-0.1.0a1/examples.md +214 -0
- genoplot-0.1.0a1/pixi.lock +989 -0
- genoplot-0.1.0a1/pyproject.toml +43 -0
- genoplot-0.1.0a1/scripts/demo.py +48 -0
- genoplot-0.1.0a1/scripts/examples.py +107 -0
- genoplot-0.1.0a1/src/genoplot/__init__.py +83 -0
- genoplot-0.1.0a1/src/genoplot/anchors.py +64 -0
- genoplot-0.1.0a1/src/genoplot/colors.py +37 -0
- genoplot-0.1.0a1/src/genoplot/figure.py +175 -0
- genoplot-0.1.0a1/src/genoplot/mock.py +122 -0
- genoplot-0.1.0a1/src/genoplot/render.py +176 -0
- genoplot-0.1.0a1/src/genoplot/schema.py +69 -0
- genoplot-0.1.0a1/src/genoplot/window.py +146 -0
- genoplot-0.1.0a1/tests/conftest.py +15 -0
- genoplot-0.1.0a1/tests/test_anchors.py +66 -0
- genoplot-0.1.0a1/tests/test_colors.py +29 -0
- genoplot-0.1.0a1/tests/test_figure.py +85 -0
- genoplot-0.1.0a1/tests/test_window.py +125 -0
genoplot-0.1.0a1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Luan Leal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: genoplot
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Modular genome neighborhood plots with colormaps
|
|
5
|
+
Author-email: Luan Leal <luanleal@usp.br>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: matplotlib>=3.8
|
|
10
|
+
Requires-Dist: numpy>=1.26
|
|
11
|
+
Requires-Dist: polars>=1.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# genoplot
|
|
15
|
+
|
|
16
|
+
Modular genome neighborhood plots with colormaps.
|
|
17
|
+
|
|
18
|
+
`genoplot` plots the genes around an anchor gene on each plasmid: a backbone
|
|
19
|
+
line with direction-aware gene arrows, colored by a numeric association value
|
|
20
|
+
(e.g. NPMI). It replaces the original monolithic `plot_npmi_neighborhood`
|
|
21
|
+
function with pure coordinate math separated from matplotlib rendering.
|
|
22
|
+
|
|
23
|
+
- **Polars in, figure out** — pass a `pl.DataFrame` of coordinates, get a
|
|
24
|
+
`matplotlib` figure.
|
|
25
|
+
- **Circular genomes handled for free** — windows may cross the plasmid
|
|
26
|
+
boundary; genes spanning the replication origin stay contiguous.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import genoplot
|
|
32
|
+
|
|
33
|
+
coords = genoplot.make_mock_coords()
|
|
34
|
+
fig, axes = genoplot.plot_neighborhoods(
|
|
35
|
+
coords,
|
|
36
|
+
["plasmid_1", "plasmid_4"],
|
|
37
|
+
neighbor_range=10_000,
|
|
38
|
+
)
|
|
39
|
+
fig.savefig("neighborhoods.png", dpi=200)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
The project uses [pixi](https://pixi.sh):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pixi install
|
|
48
|
+
pixi run test # run tests
|
|
49
|
+
pixi run lint # ruff check src tests scripts
|
|
50
|
+
pixi run demo # regenerate demo_output/*.png
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For a plain pip install: `pip install -e .` (requires python >=3.11, needs
|
|
54
|
+
`polars`, `matplotlib`, `numpy`).
|
|
55
|
+
|
|
56
|
+
## Coordinate table schema
|
|
57
|
+
|
|
58
|
+
One row per gene. Required columns: `plasmid`, `gene_id`, `start`, `end`,
|
|
59
|
+
`strand` (the entry `start`/`end` may be any integer range; `strand` is
|
|
60
|
+
`1` or `-1`). Optional but useful: label columns (`accession`,
|
|
61
|
+
`short_name`) and a numeric value column such as `NPMI_h1`.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import polars as pl
|
|
65
|
+
import genoplot
|
|
66
|
+
|
|
67
|
+
df = pl.DataFrame({
|
|
68
|
+
"plasmid": ["p1"] * 3,
|
|
69
|
+
"gene_id": ["g1", "g2", "g3"],
|
|
70
|
+
"accession": ["WP_00000001.1", "WP_00000002.1", None],
|
|
71
|
+
"short_name": ["RepB", None, "parA"],
|
|
72
|
+
"start": [0, 3400, 5100],
|
|
73
|
+
"end": [1200, 3900, 5800],
|
|
74
|
+
"strand": [1, -1, 1],
|
|
75
|
+
"NPMI_h1": [0.91, None, -0.2],
|
|
76
|
+
})
|
|
77
|
+
genoplot.plot_neighborhoods(df, ["p1"])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
### High level
|
|
83
|
+
|
|
84
|
+
- `genoplot.plot_neighborhoods(coords, plasmids, *, neighbor_range=10_000,
|
|
85
|
+
value_col="NPMI_h1", color="managua", vmin=-1.0, vmax=1.0, ...)` —
|
|
86
|
+
one track per plasmid. Returns `(fig, axes)`; a shared colorbar is
|
|
87
|
+
attached on the right. Plasmids without an anchor gene get a placeholder
|
|
88
|
+
row. `value_col` selects the numeric column used for anchor ranking,
|
|
89
|
+
gene coloring, the colorbar, and the title. `predicate=...` (a
|
|
90
|
+
DataFrame->DataFrame filter) narrows the anchor candidates before
|
|
91
|
+
ranking and takes precedence over `label_contains` — e.g. pass
|
|
92
|
+
`predicate=lambda df: df.filter(pl.col("NPMI_h1") > 0.7)` for a minimum
|
|
93
|
+
anchor threshold.
|
|
94
|
+
|
|
95
|
+
### Lower level
|
|
96
|
+
|
|
97
|
+
- `genoplot.schema.validate_coords(df)` — check required columns, cast
|
|
98
|
+
`start`/`end` to `Int64`; `check_value_column(df, col)`.
|
|
99
|
+
- `genoplot.window.Window`, `make_window`, `resolve_window` — pure
|
|
100
|
+
coordinate math mapping genes into a linear frame where the visible
|
|
101
|
+
window is `[0, width]`. Genes are sorted by `_plot_start`; origin-spanning
|
|
102
|
+
genes stay contiguous.
|
|
103
|
+
- `genoplot.anchors.select_anchor(genes, value_col,
|
|
104
|
+
label_contains="Rep")` — pick the highest-value candidate (default: a
|
|
105
|
+
`short_name` containing `"Rep"`). Pass `predicate=...` for a custom
|
|
106
|
+
filter, or `label_contains=None` to disable the label rule.
|
|
107
|
+
- `genoplot.colors.build_cmap / build_norm / value_to_color / add_colorbar`
|
|
108
|
+
— colormap machinery; missing values render as `lightgray`.
|
|
109
|
+
- `genoplot.render.draw_backbone / draw_gene_arrow / draw_gene_label /
|
|
110
|
+
draw_track` — drawing primitives onto a caller-provided `Axes`.
|
|
111
|
+
|
|
112
|
+
### Mock data
|
|
113
|
+
|
|
114
|
+
- `genoplot.make_mock_coords(n_plasmids=4, seed=2026, value_col="NPMI_h1")`
|
|
115
|
+
— deterministic synthetic table for demos/tests. The first plasmid's
|
|
116
|
+
anchor sits near the origin and the last near the genome end to exercise
|
|
117
|
+
boundary-crossing windows.
|
|
118
|
+
|
|
119
|
+
## Render a demo
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pixi run demo
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
writes PNGs into `demo_output/` (gitignored).
|
|
126
|
+
|
|
127
|
+
## Layout
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
src/genoplot/
|
|
131
|
+
schema.py column conventions + validation
|
|
132
|
+
window.py circular-window coordinate math
|
|
133
|
+
anchors.py anchor gene selection
|
|
134
|
+
colors.py colormap / norm helpers
|
|
135
|
+
render.py drawing primitives (backbone, arrows, labels)
|
|
136
|
+
figure.py high-level plot_neighborhoods API
|
|
137
|
+
mock.py seeded synthetic data generator
|
|
138
|
+
tests/ pytest suite (window, anchors, colors, figure)
|
|
139
|
+
scripts/
|
|
140
|
+
demo.py demo PNG renderer
|
|
141
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# genoplot — progress notes
|
|
2
|
+
|
|
3
|
+
State as of 2026-08-27: **complete**. All tasks green.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
Library `genoplot` (src layout, pixi + hatchling) replacing the original
|
|
8
|
+
monolithic `plot_npmi_neighborhood` function. Modular: pure coordinate
|
|
9
|
+
math separated from matplotlib rendering; Polars in, figure out.
|
|
10
|
+
|
|
11
|
+
## Done
|
|
12
|
+
|
|
13
|
+
- [x] `pyproject.toml` — genoplot 0.1.0, deps (polars, matplotlib, numpy),
|
|
14
|
+
pixi workspace (conda-forge/linux-64), pypi deps (pytest, ruff),
|
|
15
|
+
tasks: `demo`, `lint`, `test`. Editable install of the package.
|
|
16
|
+
- [x] `src/genoplot/schema.py` — column constants, `validate_coords`,
|
|
17
|
+
`check_value_column`, `SchemaError`.
|
|
18
|
+
- [x] `src/genoplot/window.py` — `Window` dataclass (xmin/xmax/width,
|
|
19
|
+
anchor ± flank), `make_window`, `resolve_window`.
|
|
20
|
+
- [x] `src/genoplot/anchors.py` — `select_anchor` (max value among
|
|
21
|
+
candidates; `label_contains="Rep"` default, or custom predicate;
|
|
22
|
+
returns None if no candidates).
|
|
23
|
+
- [x] `src/genoplot/colors.py` — `build_cmap/build_norm/value_to_color/
|
|
24
|
+
add_colorbar`, MISSING_COLOR="lightgray".
|
|
25
|
+
- [x] `src/genoplot/render.py` — primitives: `draw_backbone`,
|
|
26
|
+
`draw_gene_arrow` (FancyArrow, head_ratio=0.2, head_max=300),
|
|
27
|
+
`draw_gene_label`, `set_track_limits`, `draw_track` (anchor gets
|
|
28
|
+
black outline). YLIM computed from arrow geometry: matplotlib 3.11
|
|
29
|
+
FancyArrow builds a plain polygon with width/head_width in DATA
|
|
30
|
+
units, so arrows span ±max(width, head_width)/2 ≈ ±1 around
|
|
31
|
+
Y_CENTER; YLIM = (-1.3, 2.3) (previously -0.7..2.3 clipped the
|
|
32
|
+
arrow bottoms).
|
|
33
|
+
- [x] `src/genoplot/figure.py` — `plot_neighborhoods`: GridSpec rows +
|
|
34
|
+
shared colorbar axis, per-plasmid filtering, placeholder row when
|
|
35
|
+
no anchor ("No annotated genes with NPMI"), title template
|
|
36
|
+
`{plasmid} | {value_col}={value:.3g}`, defaults color="managua",
|
|
37
|
+
vmin/vmax=-1/1, neighbor_range=10_000, value_col="NPMI_h1".
|
|
38
|
+
- [x] `src/genoplot/mock.py` — `make_mock_coords(n_plasmids, seed,
|
|
39
|
+
value_col)`: seeded numpy RNG; genes 300-1500 bp with gaps;
|
|
40
|
+
~18% named genes; each plasmid has an intended Rep anchor with
|
|
41
|
+
NPMI 0.75-0.95 plus 1-2 decoy Reps (-0.5..0.5); plasmid_1's anchor
|
|
42
|
+
near origin, last plasmid's anchor near genome end (exercises
|
|
43
|
+
wrap-around windows).
|
|
44
|
+
- [x] `src/genoplot/__init__.py` — public API re-exports + `__all__`.
|
|
45
|
+
- [x] Env built: `pixi install` (Python 3.14.7 via conda-forge).
|
|
46
|
+
- [x] Tests (25 passing): `tests/conftest.py` (MPLBACKEND=Agg before
|
|
47
|
+
pyplot import, autoclose figures), `tests/test_window.py` (interior
|
|
48
|
+
identity, xmin<0 / xmax>L wrap cases sorted by `_plot_start`,
|
|
49
|
+
origin-spanning gene contiguous, edge-partial overlap included,
|
|
50
|
+
outside excluded), `tests/test_anchors.py` (max-value Rep, None when
|
|
51
|
+
no candidates/values, predicate precedence, label filter off),
|
|
52
|
+
`tests/test_colors.py`, `tests/test_figure.py` (axes/cbar counts,
|
|
53
|
+
PNG save, placeholder rows, SchemaError paths).
|
|
54
|
+
- [x] `scripts/demo.py` — mock data -> PNGs into `demo_output/` (default
|
|
55
|
+
flank, big-flank boundary crossing, all four plasmids).
|
|
56
|
+
- [x] Verification: `pixi run test` (25 passed), `pixi run lint`
|
|
57
|
+
(all checks passed), `pixi run demo` (3 PNGs regenerated).
|
|
58
|
+
NOTE: model could not render PNGs (no image input), so the final
|
|
59
|
+
visual eyeball step was skipped — outputs should be checked by eye.
|
|
60
|
+
- [x] `README.md` — quick start, schema, API summary, pixi usage, layout.
|
|
61
|
+
- [x] `examples.md` + `scripts/examples.py` — six simple runnable
|
|
62
|
+
examples rendered into `examples_output/` (all gitignored); pixi
|
|
63
|
+
task `examples` added. Covers hand-built tables, mock defaults,
|
|
64
|
+
styling, boundary-crossing flanks, anchor customization with a
|
|
65
|
+
predicate, and CSV input.
|
|
66
|
+
- [x] `plot_neighborhoods(..., predicate=...)` — anchor-candidate filter
|
|
67
|
+
threaded into `select_anchor` (takes precedence over
|
|
68
|
+
`label_contains`); used for minimum anchor thresholds. Two new
|
|
69
|
+
tests (27 passing); README + examples.md updated.
|
|
70
|
+
|
|
71
|
+
## Design decision to remember
|
|
72
|
+
|
|
73
|
+
Window math is fully modulo-based (`rel = (coord - xmin) mod L`, window =
|
|
74
|
+
[0, width] in shifted frame). Consequence: a gene spanning the replication
|
|
75
|
+
origin stays **contiguous** (single arrow) because the backbone linearizes
|
|
76
|
+
at the *window edges*, not at the origin — so no gene-splitting branch is
|
|
77
|
+
needed anywhere.
|
|
78
|
+
|
|
79
|
+
## Optional polish (not done, not needed)
|
|
80
|
+
|
|
81
|
+
- Ruff config tweaks, docstring pass, `pixi run fmt`/`ruff format` if
|
|
82
|
+
desired.
|
|
83
|
+
- `mock._next_accession` uses a module-level counter — not seed-safe for
|
|
84
|
+
exact reproducibility of accession strings across calls (positions/NPMIs
|
|
85
|
+
are seeded). Fine for tests.
|
|
86
|
+
|
|
87
|
+
## Gotchas
|
|
88
|
+
|
|
89
|
+
- Python here is system 3.14 without pip; conda-forge solve should pick
|
|
90
|
+
a compatible python (>=3.11 pinned).
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# genoplot
|
|
2
|
+
|
|
3
|
+
Modular genome neighborhood plots with colormaps.
|
|
4
|
+
|
|
5
|
+
`genoplot` plots the genes around an anchor gene on each plasmid: a backbone
|
|
6
|
+
line with direction-aware gene arrows, colored by a numeric association value
|
|
7
|
+
(e.g. NPMI). It replaces the original monolithic `plot_npmi_neighborhood`
|
|
8
|
+
function with pure coordinate math separated from matplotlib rendering.
|
|
9
|
+
|
|
10
|
+
- **Polars in, figure out** — pass a `pl.DataFrame` of coordinates, get a
|
|
11
|
+
`matplotlib` figure.
|
|
12
|
+
- **Circular genomes handled for free** — windows may cross the plasmid
|
|
13
|
+
boundary; genes spanning the replication origin stay contiguous.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import genoplot
|
|
19
|
+
|
|
20
|
+
coords = genoplot.make_mock_coords()
|
|
21
|
+
fig, axes = genoplot.plot_neighborhoods(
|
|
22
|
+
coords,
|
|
23
|
+
["plasmid_1", "plasmid_4"],
|
|
24
|
+
neighbor_range=10_000,
|
|
25
|
+
)
|
|
26
|
+
fig.savefig("neighborhoods.png", dpi=200)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
The project uses [pixi](https://pixi.sh):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pixi install
|
|
35
|
+
pixi run test # run tests
|
|
36
|
+
pixi run lint # ruff check src tests scripts
|
|
37
|
+
pixi run demo # regenerate demo_output/*.png
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For a plain pip install: `pip install -e .` (requires python >=3.11, needs
|
|
41
|
+
`polars`, `matplotlib`, `numpy`).
|
|
42
|
+
|
|
43
|
+
## Coordinate table schema
|
|
44
|
+
|
|
45
|
+
One row per gene. Required columns: `plasmid`, `gene_id`, `start`, `end`,
|
|
46
|
+
`strand` (the entry `start`/`end` may be any integer range; `strand` is
|
|
47
|
+
`1` or `-1`). Optional but useful: label columns (`accession`,
|
|
48
|
+
`short_name`) and a numeric value column such as `NPMI_h1`.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import polars as pl
|
|
52
|
+
import genoplot
|
|
53
|
+
|
|
54
|
+
df = pl.DataFrame({
|
|
55
|
+
"plasmid": ["p1"] * 3,
|
|
56
|
+
"gene_id": ["g1", "g2", "g3"],
|
|
57
|
+
"accession": ["WP_00000001.1", "WP_00000002.1", None],
|
|
58
|
+
"short_name": ["RepB", None, "parA"],
|
|
59
|
+
"start": [0, 3400, 5100],
|
|
60
|
+
"end": [1200, 3900, 5800],
|
|
61
|
+
"strand": [1, -1, 1],
|
|
62
|
+
"NPMI_h1": [0.91, None, -0.2],
|
|
63
|
+
})
|
|
64
|
+
genoplot.plot_neighborhoods(df, ["p1"])
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
### High level
|
|
70
|
+
|
|
71
|
+
- `genoplot.plot_neighborhoods(coords, plasmids, *, neighbor_range=10_000,
|
|
72
|
+
value_col="NPMI_h1", color="managua", vmin=-1.0, vmax=1.0, ...)` —
|
|
73
|
+
one track per plasmid. Returns `(fig, axes)`; a shared colorbar is
|
|
74
|
+
attached on the right. Plasmids without an anchor gene get a placeholder
|
|
75
|
+
row. `value_col` selects the numeric column used for anchor ranking,
|
|
76
|
+
gene coloring, the colorbar, and the title. `predicate=...` (a
|
|
77
|
+
DataFrame->DataFrame filter) narrows the anchor candidates before
|
|
78
|
+
ranking and takes precedence over `label_contains` — e.g. pass
|
|
79
|
+
`predicate=lambda df: df.filter(pl.col("NPMI_h1") > 0.7)` for a minimum
|
|
80
|
+
anchor threshold.
|
|
81
|
+
|
|
82
|
+
### Lower level
|
|
83
|
+
|
|
84
|
+
- `genoplot.schema.validate_coords(df)` — check required columns, cast
|
|
85
|
+
`start`/`end` to `Int64`; `check_value_column(df, col)`.
|
|
86
|
+
- `genoplot.window.Window`, `make_window`, `resolve_window` — pure
|
|
87
|
+
coordinate math mapping genes into a linear frame where the visible
|
|
88
|
+
window is `[0, width]`. Genes are sorted by `_plot_start`; origin-spanning
|
|
89
|
+
genes stay contiguous.
|
|
90
|
+
- `genoplot.anchors.select_anchor(genes, value_col,
|
|
91
|
+
label_contains="Rep")` — pick the highest-value candidate (default: a
|
|
92
|
+
`short_name` containing `"Rep"`). Pass `predicate=...` for a custom
|
|
93
|
+
filter, or `label_contains=None` to disable the label rule.
|
|
94
|
+
- `genoplot.colors.build_cmap / build_norm / value_to_color / add_colorbar`
|
|
95
|
+
— colormap machinery; missing values render as `lightgray`.
|
|
96
|
+
- `genoplot.render.draw_backbone / draw_gene_arrow / draw_gene_label /
|
|
97
|
+
draw_track` — drawing primitives onto a caller-provided `Axes`.
|
|
98
|
+
|
|
99
|
+
### Mock data
|
|
100
|
+
|
|
101
|
+
- `genoplot.make_mock_coords(n_plasmids=4, seed=2026, value_col="NPMI_h1")`
|
|
102
|
+
— deterministic synthetic table for demos/tests. The first plasmid's
|
|
103
|
+
anchor sits near the origin and the last near the genome end to exercise
|
|
104
|
+
boundary-crossing windows.
|
|
105
|
+
|
|
106
|
+
## Render a demo
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pixi run demo
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
writes PNGs into `demo_output/` (gitignored).
|
|
113
|
+
|
|
114
|
+
## Layout
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
src/genoplot/
|
|
118
|
+
schema.py column conventions + validation
|
|
119
|
+
window.py circular-window coordinate math
|
|
120
|
+
anchors.py anchor gene selection
|
|
121
|
+
colors.py colormap / norm helpers
|
|
122
|
+
render.py drawing primitives (backbone, arrows, labels)
|
|
123
|
+
figure.py high-level plot_neighborhoods API
|
|
124
|
+
mock.py seeded synthetic data generator
|
|
125
|
+
tests/ pytest suite (window, anchors, colors, figure)
|
|
126
|
+
scripts/
|
|
127
|
+
demo.py demo PNG renderer
|
|
128
|
+
```
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# genoplot examples
|
|
2
|
+
|
|
3
|
+
Six small, self-contained examples. They build on each other, so read them
|
|
4
|
+
in order. Run everything with:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pixi run examples
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
which regenerates the images below into `examples_output/`.
|
|
11
|
+
|
|
12
|
+
The examples use mock coordinates (`genoplot.make_mock_coords`) so they are
|
|
13
|
+
deterministic and need no data files. For your own data, replace the mock
|
|
14
|
+
frame with any `pl.DataFrame` following the
|
|
15
|
+
[schema](README.md#coordinate-table-schema).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Plot a table you build by hand
|
|
20
|
+
|
|
21
|
+
The smallest possible call: define a few genes, pass them to
|
|
22
|
+
`plot_neighborhoods`, get a figure back.
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import polars as pl
|
|
26
|
+
import genoplot
|
|
27
|
+
|
|
28
|
+
genes = pl.DataFrame({
|
|
29
|
+
"plasmid": ["p1"] * 3,
|
|
30
|
+
"gene_id": ["g1", "g2", "g3"],
|
|
31
|
+
"accession": ["WP_00000001.1", "WP_00000002.1", None],
|
|
32
|
+
"short_name": ["RepB", None, "parA"],
|
|
33
|
+
"start": [0, 3400, 5100],
|
|
34
|
+
"end": [1200, 3900, 5800],
|
|
35
|
+
"strand": [1, -1, 1],
|
|
36
|
+
"NPMI_h1": [0.91, None, -0.2],
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
fig, _ = genoplot.plot_neighborhoods(genes, ["p1"])
|
|
40
|
+
fig.savefig("my_plot.png", dpi=200)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
`RepB` (value 0.91) is picked as the anchor because it is the `Rep`-labeled
|
|
46
|
+
gene with the highest `NPMI_h1`. It gets a black outline; the gene without a
|
|
47
|
+
value renders gray.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 2. The defaults, on realistic mock data
|
|
52
|
+
|
|
53
|
+
Generated data mimics real plasmid tables: ~25-45 genes per plasmid,
|
|
54
|
+
sparse labels, sparse values, and one `Rep` anchor per plasmid.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import genoplot
|
|
58
|
+
|
|
59
|
+
coords = genoplot.make_mock_coords(n_plasmids=4)
|
|
60
|
+
fig, _ = genoplot.plot_neighborhoods(
|
|
61
|
+
coords,
|
|
62
|
+
["plasmid_1", "plasmid_4"],
|
|
63
|
+
neighbor_range=10_000, # show anchor ± 10 kb
|
|
64
|
+
)
|
|
65
|
+
fig.savefig("example2.png", dpi=200)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+

|
|
69
|
+
|
|
70
|
+
Every gene is drawn as a direction-aware arrow (strand `+1` points right,
|
|
71
|
+
`-1` points left), colored by its `NPMI_h1` value through the shared
|
|
72
|
+
colorbar.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 3. Tune colors, bounds, and titles
|
|
77
|
+
|
|
78
|
+
All the knobs are keyword arguments on `plot_neighborhoods`:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import genoplot
|
|
82
|
+
|
|
83
|
+
coords = genoplot.make_mock_coords(n_plasmids=2)
|
|
84
|
+
fig, _ = genoplot.plot_neighborhoods(
|
|
85
|
+
coords,
|
|
86
|
+
["plasmid_1", "plasmid_2"],
|
|
87
|
+
color="viridis", # any matplotlib colormap name
|
|
88
|
+
vmin=-0.5, # colorbar normalization (default -1..1)
|
|
89
|
+
vmax=1.0,
|
|
90
|
+
colorbar_label="NPMI (normalized)", # shared colorbar text
|
|
91
|
+
title_template="{plasmid} — best {value_col}={value:.2f}",
|
|
92
|
+
)
|
|
93
|
+
fig.savefig("example3.png", dpi=200)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+

|
|
97
|
+
|
|
98
|
+
The title template receives `plasmid`, `value_col`, and `value` (the
|
|
99
|
+
anchor's score).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 4. Wide windows that cross the plasmid boundary
|
|
104
|
+
|
|
105
|
+
Genomes here are circular. When the flank is large, the window can extend
|
|
106
|
+
past both ends of the sequence; genes on the far side are then pulled in
|
|
107
|
+
from the "other direction". The first plasmid's anchor sits near the start
|
|
108
|
+
and the last near the end, which is exactly where this happens.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import genoplot
|
|
112
|
+
|
|
113
|
+
coords = genoplot.make_mock_coords(n_plasmids=4)
|
|
114
|
+
fig, _ = genoplot.plot_neighborhoods(
|
|
115
|
+
coords,
|
|
116
|
+
["plasmid_1", "plasmid_4"],
|
|
117
|
+
neighbor_range=25_000,
|
|
118
|
+
figsize=(16, 5),
|
|
119
|
+
)
|
|
120
|
+
fig.savefig("example4.png", dpi=200)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+

|
|
124
|
+
|
|
125
|
+
A gene spanning the replication origin is drawn as a single contiguous
|
|
126
|
+
arrow, never split in two.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 5. Pick the anchor yourself
|
|
131
|
+
|
|
132
|
+
By default a candidate is a gene whose label contains `"Rep"`, and the
|
|
133
|
+
highest-scoring candidate wins. Set `label_contains=None` to rank every
|
|
134
|
+
labeled gene by score instead:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import genoplot
|
|
138
|
+
|
|
139
|
+
coords = genoplot.make_mock_coords(n_plasmids=2)
|
|
140
|
+
fig, _ = genoplot.plot_neighborhoods(
|
|
141
|
+
coords,
|
|
142
|
+
["plasmid_1", "plasmid_2"],
|
|
143
|
+
label_contains=None,
|
|
144
|
+
)
|
|
145
|
+
fig.savefig("example5.png", dpi=200)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+

|
|
149
|
+
|
|
150
|
+
For a fully custom rule, pass a `predicate` (a `DataFrame -> DataFrame`
|
|
151
|
+
filter) to `plot_neighborhoods`. It is applied to the anchor candidates
|
|
152
|
+
before ranking and replaces the `"Rep"` rule. A minimum anchor threshold is
|
|
153
|
+
just a value filter — here we only accept anchors scoring above 0.7:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import polars as pl
|
|
157
|
+
import genoplot
|
|
158
|
+
|
|
159
|
+
coords = genoplot.make_mock_coords(n_plasmids=2)
|
|
160
|
+
|
|
161
|
+
def above_07(df):
|
|
162
|
+
return df.filter(pl.col("NPMI_h1") > 0.7)
|
|
163
|
+
|
|
164
|
+
fig, _ = genoplot.plot_neighborhoods(
|
|
165
|
+
coords,
|
|
166
|
+
["plasmid_1"],
|
|
167
|
+
predicate=above_07, # overrides label_contains
|
|
168
|
+
)
|
|
169
|
+
fig.savefig("threshold.png", dpi=200)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If no candidate survives the predicate, the row shows the "No annotated
|
|
173
|
+
genes with NPMI" placeholder instead of a plot. The same filter is exposed
|
|
174
|
+
on the lower-level `genoplot.select_anchor`, which returns the anchor row
|
|
175
|
+
as a dict if you need it directly:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
anchor = genoplot.select_anchor(
|
|
179
|
+
coords.filter(pl.col("plasmid") == "plasmid_1"),
|
|
180
|
+
"NPMI_h1",
|
|
181
|
+
predicate=above_07,
|
|
182
|
+
)
|
|
183
|
+
print(anchor["gene_id"], anchor["short_name"]) # best-scoring gene > 0.7
|
|
184
|
+
```
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 6. Read your table from a file
|
|
190
|
+
|
|
191
|
+
A file on disk works exactly like the frames above — just load it and
|
|
192
|
+
plot. (The mock generator can emit one for you.)
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
import polars as pl
|
|
196
|
+
import genoplot
|
|
197
|
+
|
|
198
|
+
genoplot.make_mock_coords(n_plasmids=3).write_csv("genes.csv")
|
|
199
|
+
df = pl.read_csv("genes.csv")
|
|
200
|
+
|
|
201
|
+
fig, _ = genoplot.plot_neighborhoods(df, ["plasmid_2"])
|
|
202
|
+
fig.savefig("example6.png", dpi=200)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+

|
|
206
|
+
|
|
207
|
+
Columns stay the same: `plasmid`, `gene_id`, `start`, `end`, `strand`,
|
|
208
|
+
optional labels (`accession`, `short_name`), and your value column.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
That's the whole API for everyday use. For the lower-level drawing
|
|
213
|
+
primitives (backbone, arrows, labels) see
|
|
214
|
+
[README.md](README.md#api).
|