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.
@@ -0,0 +1,2 @@
1
+ # SCM syntax highlighting & preventing 3-way merges
2
+ pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
@@ -0,0 +1,11 @@
1
+ # pixi environments
2
+ .pixi/*
3
+ !.pixi/config.toml
4
+
5
+ # demo outputs
6
+ demo_output/
7
+ examples_output/
8
+
9
+ # build artifacts
10
+ dist/
11
+ *.egg-info/
@@ -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
+ ![Example 1](examples_output/ex1.png)
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
+ ![Example 2](examples_output/ex2.png)
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
+ ![Example 3](examples_output/ex3.png)
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
+ ![Example 4](examples_output/ex4.png)
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
+ ![Example 5](examples_output/ex5.png)
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
+ ![Example 6](examples_output/ex6.png)
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).