Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
0ef5989
Add rendering and template management for genomic figures
alsmith151 Mar 23, 2026
c2f4cc5
feat(cli): add init, plot, and validate commands for template generat…
alsmith151 Mar 23, 2026
e338230
feat(enums): expand TrackType to cover all track aliases and former T…
alsmith151 Mar 25, 2026
4ebd05b
feat(aesthetics): add BaseAesthetics and BaseMultiColorAesthetics bas…
alsmith151 Mar 25, 2026
190172d
feat(registry): add TrackRegistry with register decorator and singleton
alsmith151 Mar 25, 2026
e294c5e
feat(tracks): register signal tracks; migrate aesthetics to BaseAesth…
alsmith151 Mar 25, 2026
4e0cee3
feat(tracks): register interval and guide tracks; migrate aesthetics …
alsmith151 Mar 25, 2026
22948b5
feat(tracks): register Hi-C and QuantNado tracks; fix test failures f…
alsmith151 Mar 25, 2026
7f158ed
feat(tracks): expose registry from tracks/__init__.py; ensure all dec…
alsmith151 Mar 25, 2026
f4dd7b4
feat(theme): add Theme.apply() single-pass method replacing three-pas…
alsmith151 Mar 25, 2026
ac7ab46
refactor(figure): replace _alias_map with registry; use Theme.apply()…
alsmith151 Mar 25, 2026
ad1df70
refactor: replace TemplateTrackType with unified TrackType throughout…
alsmith151 Mar 25, 2026
77453fa
Enhance documentation and refactor code for clarity and usability
alsmith151 Mar 25, 2026
82bf005
refactor(figure): improve type hints and null checks; streamline trac…
alsmith151 Mar 25, 2026
b3aaf5f
refactor(bigwig): remove unused aesthetics attributes from BigwigAest…
alsmith151 Mar 25, 2026
38f371b
Refactor Plotnado codebase for improved maintainability and extensibi…
alsmith151 Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# AGENTS.md — AI Coding Guidelines for plotnado

## What is plotnado?

plotnado is a lightweight Python package for creating genome browser-style plots from genomic data files (BigWig, BED, NarrowPeak, etc.). It provides two complementary interfaces:

1. **Python API** — fluent builder for programmatic use
2. **CLI + YAML templates** — file-driven workflow for non-programmers

## Architecture

```
plotnado/
├── figure.py # GenomicFigure — the core plotting API
├── template.py # Pydantic models for YAML templates (Template, TrackSpec, ...)
├── render.py # TemplateCompiler → RenderPlan (template-to-figure bridge)
├── theme.py # Theme / BuiltinTheme
├── tracks/ # Track implementations (BigWigTrack, BedTrack, ...)
│ ├── enums.py # Internal enums incl. TrackType (figure-layer)
│ └── ...
└── cli/
├── cli.py # Typer app entry point
├── init.py # `plotnado init` — infer template from files
├── plot.py # `plotnado plot` — render template for regions
├── validate.py # `plotnado validate` — check template validity
├── inference.py # Heuristics: infer track type/title from filename
└── grouping.py # Grouping strategies for init command
```

## Key Design Decisions

### Python API (GenomicFigure)

The fluent builder pattern allows chaining:

```python
fig = (
GenomicFigure()
.bigwig("signal.bw", title="H3K27ac")
.narrowpeak("peaks.narrowpeak")
.genes("hg38")
.axis()
.scalebar()
)
fig.save("out.png", region="chr1:1000000-2000000")
```

- Each method (`.bigwig()`, `.bed()`, etc.) appends a track and returns `self`
- `from_template(path)` builds a figure from a YAML template

### Template / CLI layer

YAML templates are human-readable, version-controllable, and editable:

```yaml
genome: hg38
tracks:
- path: signal.bw
type: bigwig
title: H3K27ac
group: sample1
guides:
genes: true
axis: true
scalebar: true
```

`TemplateCompiler.compile(template)` → `RenderPlan` → `GenomicFigure` calls.

## TrackType vs TemplateTrackType

There are two separate enums. Do not confuse them:

| Enum | Location | Purpose |
|---|---|---|
| `TrackType` | `plotnado/tracks/enums.py` | Internal figure enum; values match `GenomicFigure` method names |
| `TemplateTrackType` | `plotnado/template.py` | User-facing YAML vocabulary; values appear in template files |

`TemplateTrackType` has more values (for example `annotation` and `unknown`) that map to existing figure methods. The mapping is defined in `RenderPlan.get_track_by_method()` in `render.py`.

`TrackType = TemplateTrackType` alias exists in `template.py` for backward compatibility.

## Adding a New Track Type

1. Create `plotnado/tracks/mytrack.py` with a class extending `Track`
2. Add aesthetics class if needed and register field names
3. Add a method to `GenomicFigure` in `figure.py` (for example `.mytrack(data, **kwargs)`)
4. Add the alias in `GenomicFigure._alias_map()`
5. Add a `TemplateTrackType.MYTRACK = "mytrack"` value in `template.py`
6. Add the mapping in `RenderPlan.get_track_by_method()` in `render.py`
7. Export from `plotnado/tracks/__init__.py` and `plotnado/__init__.py`
8. Write tests

## Method Map

`RenderPlan.get_track_by_method()` maps `TemplateTrackType` values to `GenomicFigure` method names:

| TemplateTrackType | GenomicFigure method | Notes |
|---|---|---|
| `bigwig` | `bigwig` | |
| `bedgraph` | `bigwig` | BigWigTrack handles bedgraph natively |
| `bed` | `bed` | |
| `narrowpeak` | `narrowpeak` | |
| `gene` | `genes` | |
| `links` | `links` | |
| `annotation` | `bed` | BED interval track with annotation semantics |
| `overlay` | `overlay` | |
| `unknown` | `bed` | Fallback |

## Template Compilation Rules

- `TemplateCompiler.compile()` must never mutate the `Template` argument
- Resolved group indices go into `RenderPlan.resolved_group_indices`, not back into the template
- Group references are resolved case-insensitively against track `name` or `title` fields

## Common Pitfalls

- Width override: always use `width if width is not None else plan.width`, never `width or plan.width`
- Bedgraph method: there is no `GenomicFigure.bedgraph()`; bedgraph files use `.bigwig()`
- `TemplateTrackType` vs `TrackType`: import the right enum for the layer you are working in
- CLI shim: `plotnado/cli/render.py` re-exports from `plotnado.render`; import from `plotnado.render` in new code

## Testing

Run: `uv run pytest tests/`

| Test file | Coverage |
|---|---|
| `test_template.py` | Template round-trip, YAML serialization |
| `test_render.py` | TemplateCompiler, no-mutation guarantee, autocolor |
| `test_inference.py` | Track type/title inference heuristics |
| `test_grouping.py` | Grouping strategies |
| `test_cli.py` | CLI integration via `typer.testing.CliRunner` |

Guidelines:
- Use `tmp_path` pytest fixture for file-based tests
- Do not mock `GenomicFigure.plot()` or `.save()` in unit tests
- The no-mutation guarantee on `TemplateCompiler.compile()` must always have a regression test

## Dev Setup

```bash
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
uv run pytest tests/
```

Entry point: `plotnado = "plotnado.cli.cli:main"` (defined in `pyproject.toml`)
141 changes: 141 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# CLAUDE.md — AI Coding Guidelines for plotnado

## What is plotnado?

plotnado is a lightweight Python package for creating genome browser-style plots from genomic data files (BigWig, BED, NarrowPeak, etc.). It provides two complementary interfaces:

1. **Python API** — fluent builder for programmatic use
2. **CLI + YAML templates** — file-driven workflow for non-programmers

## Architecture

```
plotnado/
├── figure.py # GenomicFigure — the core plotting API
├── template.py # Pydantic models for YAML templates (Template, TrackSpec, ...)
├── render.py # TemplateCompiler → RenderPlan (template-to-figure bridge)
├── theme.py # Theme / BuiltinTheme
├── tracks/ # Track implementations (BigWigTrack, BedTrack, ...)
│ ├── enums.py # Internal enums incl. TrackType (figure-layer)
│ └── ...
└── cli/
├── cli.py # Typer app entry point
├── init.py # `plotnado init` — infer template from files
├── plot.py # `plotnado plot` — render template for regions
├── validate.py # `plotnado validate` — check template validity
├── inference.py # Heuristics: infer track type/title from filename
└── grouping.py # Grouping strategies for init command
```

## Key Design Decisions

### Python API (GenomicFigure)

The fluent builder pattern allows chaining:

```python
fig = (
GenomicFigure()
.bigwig("signal.bw", title="H3K27ac")
.narrowpeak("peaks.narrowpeak")
.genes("hg38")
.axis()
.scalebar()
)
fig.save("out.png", region="chr1:1000000-2000000")
```

- Each method (`.bigwig()`, `.bed()`, etc.) appends a track and returns `self`
- `from_template(path)` builds a figure from a YAML template

### Template / CLI layer

YAML templates are human-readable, version-controllable, and editable:

```yaml
genome: hg38
tracks:
- path: signal.bw
type: bigwig
title: H3K27ac
group: sample1
guides:
genes: true
axis: true
scalebar: true
```

`TemplateCompiler.compile(template)` → `RenderPlan` → `GenomicFigure` calls.

## Adding a New Track Type

1. Create `plotnado/tracks/mytrack.py` with a class extending `Track`
2. If it has a single primary color: `class MyAesthetics(BaseAesthetics)` (from `tracks/aesthetics.py`)
If it renders multiple series: `class MyAesthetics(BaseMultiColorAesthetics)`
3. Register the track using the decorator:
```python
from .registry import registry
from .enums import TrackType

@registry.register(TrackType.MYTRACK, aliases=["my_alias"])
class MyTrack(Track): ...
```
4. Add `MYTRACK = "mytrack"` to `TrackType` in `tracks/enums.py`
5. Add a builder method to `GenomicFigure` in `figure_methods.py`:
```python
def mytrack(self, data: Any, /, **kwargs) -> Self:
return self.add_track(TrackType.MYTRACK, data=data, **kwargs)
```
6. Add a `MytrackKwargs` TypedDict entry and regenerate `_kwargs.py`:
```bash
python scripts/generate_kwargs.py
```
7. Export from `plotnado/tracks/__init__.py` and `plotnado/__init__.py`
8. Write tests

## Method Map (render.py)

`TemplateCompiler` and `GenomicFigure` no longer maintain a separate method map.
Track lookup is centralized in `plotnado/tracks/registry.py`, and aliases such as
`bedgraph`, `annotation`, `unknown`, `bigwig_overlay`, and `scale` resolve there.

## Template Compilation Rules

- `TemplateCompiler.compile()` must **never mutate** the `Template` argument
- Resolved group indices go into `RenderPlan.resolved_group_indices`, not back into the template
- Group references are resolved case-insensitively against track `name` or `title` fields

## Common Pitfalls

- **Width override**: always use `width if width is not None else plan.width`, never `width or plan.width` (breaks when width=0.0)
- **TrackType source of truth**: use `plotnado.tracks.enums.TrackType` for both Python and template codepaths
- **Registry lookup**: if you need alias → class resolution, use `plotnado.tracks.registry.registry`, not a hard-coded method map
- **CLI shim**: `plotnado/cli/render.py` re-exports from `plotnado.render` — always import from `plotnado.render` in new code

## Testing

Run: `uv run pytest tests/`

| Test file | Coverage |
|---|---|
| `test_template.py` | Template round-trip, YAML serialization |
| `test_render.py` | TemplateCompiler, no-mutation guarantee, autocolor |
| `test_inference.py` | Track type/title inference heuristics |
| `test_grouping.py` | Grouping strategies |
| `test_cli.py` | CLI integration via `typer.testing.CliRunner` |

Guidelines:
- Use `tmp_path` pytest fixture for file-based tests
- Do not mock `GenomicFigure.plot()` or `.save()` in unit tests
- The no-mutation guarantee on `TemplateCompiler.compile()` must always have a regression test

## Dev Setup

```bash
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
uv run pytest tests/
```

Entry point: `plotnado = "plotnado.cli.cli:main"` (defined in pyproject.toml)
Loading
Loading