tree-annotated-plot 0.0.1__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.
- tree_annotated_plot-0.0.1/.github/workflows/ci.yml +58 -0
- tree_annotated_plot-0.0.1/.github/workflows/docs.yml +66 -0
- tree_annotated_plot-0.0.1/.github/workflows/release.yml +82 -0
- tree_annotated_plot-0.0.1/.gitignore +10 -0
- tree_annotated_plot-0.0.1/CLAUDE.md +116 -0
- tree_annotated_plot-0.0.1/LICENSE +19 -0
- tree_annotated_plot-0.0.1/NEXT_STEPS.md +94 -0
- tree_annotated_plot-0.0.1/PKG-INFO +225 -0
- tree_annotated_plot-0.0.1/README.md +174 -0
- tree_annotated_plot-0.0.1/docs/cli.md +30 -0
- tree_annotated_plot-0.0.1/docs/examples.md +234 -0
- tree_annotated_plot-0.0.1/docs/index.md +68 -0
- tree_annotated_plot-0.0.1/docs/python-api.md +36 -0
- tree_annotated_plot-0.0.1/examples/fetch_auspice_data.py +57 -0
- tree_annotated_plot-0.0.1/examples/flu-seqneut-2025to2026_titer_charts.py +456 -0
- tree_annotated_plot-0.0.1/examples/synthetic_example.py +185 -0
- tree_annotated_plot-0.0.1/mkdocs.yml +63 -0
- tree_annotated_plot-0.0.1/pyproject.toml +58 -0
- tree_annotated_plot-0.0.1/scripts/build_docs.sh +29 -0
- tree_annotated_plot-0.0.1/scripts/check.sh +24 -0
- tree_annotated_plot-0.0.1/scripts/generate_docs_assets.py +137 -0
- tree_annotated_plot-0.0.1/src/tree_annotated_plot/__init__.py +11 -0
- tree_annotated_plot-0.0.1/src/tree_annotated_plot/_config.py +215 -0
- tree_annotated_plot-0.0.1/src/tree_annotated_plot/_plot.py +1337 -0
- tree_annotated_plot-0.0.1/src/tree_annotated_plot/_tree.py +307 -0
- tree_annotated_plot-0.0.1/src/tree_annotated_plot/cli.py +165 -0
- tree_annotated_plot-0.0.1/tests/test_branch_length.py +144 -0
- tree_annotated_plot-0.0.1/tests/test_chart_loader.py +271 -0
- tree_annotated_plot-0.0.1/tests/test_cli.py +204 -0
- tree_annotated_plot-0.0.1/tests/test_examples_run.py +30 -0
- tree_annotated_plot-0.0.1/tests/test_introspection.py +141 -0
- tree_annotated_plot-0.0.1/tests/test_plot.py +153 -0
- tree_annotated_plot-0.0.1/tests/test_plot_config.py +92 -0
- tree_annotated_plot-0.0.1/tests/test_real_data.py +378 -0
- tree_annotated_plot-0.0.1/tests/test_reconciliation.py +268 -0
- tree_annotated_plot-0.0.1/tests/test_scale_bar.py +264 -0
- tree_annotated_plot-0.0.1/tests/test_style.py +245 -0
- tree_annotated_plot-0.0.1/tests/test_tree_location.py +178 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Run lint, format-check, and tests on every push to main and on every PR.
|
|
2
|
+
#
|
|
3
|
+
# Local equivalent: `scripts/check.sh` (which expects a local .venv;
|
|
4
|
+
# this workflow uses the runner's python directly so it doesn't depend
|
|
5
|
+
# on that layout).
|
|
6
|
+
|
|
7
|
+
name: ci
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches: [main]
|
|
12
|
+
pull_request:
|
|
13
|
+
branches: [main]
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
# Cancel in-flight runs on the same ref when a new commit lands; PRs
|
|
20
|
+
# rebased many times in quick succession don't queue up wasted minutes.
|
|
21
|
+
concurrency:
|
|
22
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
23
|
+
cancel-in-progress: true
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
test:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v6
|
|
30
|
+
|
|
31
|
+
- uses: actions/setup-python@v6
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.13"
|
|
34
|
+
|
|
35
|
+
- name: Install package + dev extras
|
|
36
|
+
run: |
|
|
37
|
+
python -m pip install --upgrade pip
|
|
38
|
+
pip install -e ".[dev]"
|
|
39
|
+
|
|
40
|
+
- name: Fetch upstream Kikawa Auspice JSONs
|
|
41
|
+
# Real-data tests in tests/test_real_data.py and tests/test_cli.py
|
|
42
|
+
# skip cleanly when these files are absent. We want them to
|
|
43
|
+
# actually run in CI, so fetch the upstream JSONs first.
|
|
44
|
+
run: python examples/fetch_auspice_data.py
|
|
45
|
+
|
|
46
|
+
- name: Build Kikawa titer chart specs
|
|
47
|
+
# tests/test_real_data.py also reads the saved Vega-Lite specs
|
|
48
|
+
# produced by this script. Ditto for tests/test_cli.py end-to-end.
|
|
49
|
+
run: python examples/flu-seqneut-2025to2026_titer_charts.py
|
|
50
|
+
|
|
51
|
+
- name: Lint (ruff)
|
|
52
|
+
run: ruff check .
|
|
53
|
+
|
|
54
|
+
- name: Format check (black)
|
|
55
|
+
run: black --check .
|
|
56
|
+
|
|
57
|
+
- name: Tests (pytest)
|
|
58
|
+
run: pytest tests/
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Build the docs on every push to `main` (then deploy to GitHub Pages)
|
|
2
|
+
# and on every pull request targeting `main` (build only, no deploy).
|
|
3
|
+
# The PR build catches broken mkdocstrings refs, missing images, and
|
|
4
|
+
# unresolved cross-links *before* they land on `main` and break the
|
|
5
|
+
# next deploy.
|
|
6
|
+
#
|
|
7
|
+
# Local equivalent: `scripts/build_docs.sh` (mkdocs build --strict).
|
|
8
|
+
# This workflow file is dormant until the repo is published with Pages
|
|
9
|
+
# enabled — keep it under version control so the deployment pipeline is
|
|
10
|
+
# reproducible from a clean checkout.
|
|
11
|
+
|
|
12
|
+
name: docs
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
branches: [main]
|
|
17
|
+
pull_request:
|
|
18
|
+
branches: [main]
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
pages: write
|
|
24
|
+
id-token: write
|
|
25
|
+
|
|
26
|
+
concurrency:
|
|
27
|
+
group: pages
|
|
28
|
+
cancel-in-progress: false
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
build:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v6
|
|
35
|
+
- uses: actions/setup-python@v6
|
|
36
|
+
with:
|
|
37
|
+
python-version: "3.13"
|
|
38
|
+
- name: Install package + docs extras
|
|
39
|
+
run: |
|
|
40
|
+
python -m pip install --upgrade pip
|
|
41
|
+
pip install -e ".[docs]"
|
|
42
|
+
- name: Generate chart assets
|
|
43
|
+
# Renders docs/images/*.png and docs/charts/*.html from the
|
|
44
|
+
# example modules. Both directories are gitignored — the docs
|
|
45
|
+
# site references them, so they must exist before mkdocs runs.
|
|
46
|
+
run: python scripts/generate_docs_assets.py
|
|
47
|
+
- name: Build site
|
|
48
|
+
run: mkdocs build --strict
|
|
49
|
+
- uses: actions/upload-pages-artifact@v5
|
|
50
|
+
with:
|
|
51
|
+
path: ./site
|
|
52
|
+
|
|
53
|
+
deploy:
|
|
54
|
+
# Only deploy on push-to-main. PRs (especially from forks) lack
|
|
55
|
+
# `pages: write` permissions and would publish PR-only content to
|
|
56
|
+
# the live site if they did. The build job above still runs on PRs
|
|
57
|
+
# — that's where docs regressions get caught pre-merge.
|
|
58
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
59
|
+
needs: build
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
environment:
|
|
62
|
+
name: github-pages
|
|
63
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
64
|
+
steps:
|
|
65
|
+
- id: deployment
|
|
66
|
+
uses: actions/deploy-pages@v5
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Build wheel + sdist and publish to PyPI when a `v*` tag is pushed.
|
|
2
|
+
#
|
|
3
|
+
# Uses PyPI trusted publishing (OIDC) — no API token / password is
|
|
4
|
+
# stored in the repo. The trusted-publisher relationship must be
|
|
5
|
+
# configured once on PyPI: project Settings → Publishing → Add trusted
|
|
6
|
+
# publisher, pointing at this repo's owner + name + the workflow
|
|
7
|
+
# filename `release.yml` + environment name `pypi` below. With that
|
|
8
|
+
# config, GitHub mints a short-lived OIDC token at workflow runtime
|
|
9
|
+
# and PyPI exchanges it for an upload token.
|
|
10
|
+
#
|
|
11
|
+
# Maintainer recipe (full version):
|
|
12
|
+
# 1. Bump the `version` field in pyproject.toml on `main`.
|
|
13
|
+
# 2. Commit, then tag: `git tag v0.1.0`.
|
|
14
|
+
# 3. Push: `git push && git push --tags`.
|
|
15
|
+
# The build job verifies the tag matches `pyproject.toml`'s version, so
|
|
16
|
+
# a mismatched tag fails fast before anything reaches PyPI.
|
|
17
|
+
|
|
18
|
+
name: release
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
push:
|
|
22
|
+
tags: ["v*"]
|
|
23
|
+
workflow_dispatch:
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
build:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v6
|
|
33
|
+
|
|
34
|
+
- uses: actions/setup-python@v6
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.13"
|
|
37
|
+
|
|
38
|
+
- name: Install build tooling
|
|
39
|
+
run: |
|
|
40
|
+
python -m pip install --upgrade pip
|
|
41
|
+
pip install build
|
|
42
|
+
|
|
43
|
+
- name: Verify tag matches pyproject.toml version
|
|
44
|
+
# `python -m build` doesn't check this; without the gate, a
|
|
45
|
+
# mistyped tag would happily publish a release whose
|
|
46
|
+
# __version__ disagrees with the tag name.
|
|
47
|
+
run: |
|
|
48
|
+
tag="${GITHUB_REF#refs/tags/}"
|
|
49
|
+
version="v$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
|
|
50
|
+
if [ "$tag" != "$version" ]; then
|
|
51
|
+
echo "::error::tag '$tag' does not match pyproject.toml version '$version'"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
echo "tag and version match: $tag"
|
|
55
|
+
|
|
56
|
+
- name: Build sdist + wheel
|
|
57
|
+
run: python -m build
|
|
58
|
+
|
|
59
|
+
- uses: actions/upload-artifact@v7
|
|
60
|
+
with:
|
|
61
|
+
name: dist
|
|
62
|
+
path: dist/
|
|
63
|
+
|
|
64
|
+
publish:
|
|
65
|
+
# The `pypi` environment lets you optionally require manual approval
|
|
66
|
+
# in repo Settings → Environments → pypi → "Required reviewers"
|
|
67
|
+
# before any release lands on PyPI. PyPI's trusted-publisher config
|
|
68
|
+
# also pins this environment name.
|
|
69
|
+
environment: pypi
|
|
70
|
+
needs: build
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
permissions:
|
|
73
|
+
# Required by PyPI trusted publishing — GitHub mints an OIDC
|
|
74
|
+
# token that PyPI exchanges for an upload token.
|
|
75
|
+
id-token: write
|
|
76
|
+
steps:
|
|
77
|
+
- uses: actions/download-artifact@v8
|
|
78
|
+
with:
|
|
79
|
+
name: dist
|
|
80
|
+
path: dist/
|
|
81
|
+
|
|
82
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# `tree-annotated-plot` – Claude Code Notes
|
|
2
|
+
|
|
3
|
+
- **Keep CLAUDE.md, README.md, and the docs site updated** when changing the
|
|
4
|
+
code. `CLAUDE.md` describes programming conventions; `README.md` describes
|
|
5
|
+
basic use; `docs/` is the user-facing reference. Don't let them drift.
|
|
6
|
+
- **Single source of truth — `pyproject.toml`**: canonical for dependencies,
|
|
7
|
+
supported Python version, build config, tool settings. Don't restate any of
|
|
8
|
+
these in prose; refer to `pyproject.toml`.
|
|
9
|
+
- **Single source of truth — `PlotConfig` and `_config.py`**: every plot-parameter
|
|
10
|
+
description lives in `_config.py`. For styling/behavior knobs, the source is
|
|
11
|
+
the field's `Annotated[T, "<description>"]` metadata on `PlotConfig`; the
|
|
12
|
+
click `--help` text reads it directly, and `tree_annotated_plot.plot.__doc__`
|
|
13
|
+
is assembled at import time by `_config._render_numpy_params`, so the
|
|
14
|
+
REPL / mkdocstrings rendering pulls from the same string. Add Python-only
|
|
15
|
+
prose for a field by setting `_config.PARAM_DOC_EXTRAS[field_name] = "..."`.
|
|
16
|
+
Adding a parameter = a new `PlotConfig` field + a kwarg in
|
|
17
|
+
`tree_annotated_plot.plot`'s signature with the same default. The three
|
|
18
|
+
data-input parameters (`tree`, `chart`, `output`) can't sit on `PlotConfig`
|
|
19
|
+
because their *types* differ between surfaces (the Python API accepts live
|
|
20
|
+
`altair.Chart` / `dict` / `TreeNode`; the CLI only accepts file paths), but
|
|
21
|
+
their descriptions are still single-sourced as
|
|
22
|
+
`_config.{TREE,CHART,OUTPUT}_DESCRIPTION` constants — both the CLI's
|
|
23
|
+
`--help` and `_plot.py`'s `_PLOT_DOC_HEADER` interpolate them.
|
|
24
|
+
- **Docstring style**: NumPy (Parameters / Returns sections with the
|
|
25
|
+
`----------` underline), matching `mkdocs.yml`'s
|
|
26
|
+
`docstring_style: numpy`. Don't mix Google-style `Args:` blocks in;
|
|
27
|
+
mkdocstrings won't render them consistently.
|
|
28
|
+
- **Fail fast**: validate inputs early and raise clear `ValueError` on
|
|
29
|
+
unexpected data. Prefer assertions and explicit error messages over silent
|
|
30
|
+
fallbacks. Tip-set reconciliation errors should include sample values from
|
|
31
|
+
both sides plus candidate-field hints when possible.
|
|
32
|
+
- **Pure-Python package**: managed via `pyproject.toml` and a plain `venv`
|
|
33
|
+
(not conda). Required Python version specified in `pyproject.toml`. Develop
|
|
34
|
+
with:
|
|
35
|
+
```bash
|
|
36
|
+
python3 -m venv .venv
|
|
37
|
+
source .venv/bin/activate
|
|
38
|
+
pip install -e ".[dev,docs]"
|
|
39
|
+
```
|
|
40
|
+
The `src/` layout requires `dev-mode-dirs = ["src"]` under
|
|
41
|
+
`[tool.hatch.build.targets.wheel]` for editable installs to work.
|
|
42
|
+
- **Use default `black` / `ruff` line length.** Do not add `line-length`
|
|
43
|
+
overrides to `pyproject.toml` unless explicitly requested.
|
|
44
|
+
- **API shape**: the user supplies their plot as a Vega-Lite chart (an
|
|
45
|
+
`altair.Chart`-or-subclass object, a JSON path, an HTML path, or a parsed
|
|
46
|
+
spec dict). The package introspects the chart to find the strain encoding
|
|
47
|
+
on `x` or `y`, overrides its sort to match the tree's tip order, and
|
|
48
|
+
hconcat's or vconcat's a tree panel on the matching side. Tree dictates
|
|
49
|
+
tip ordering; plot follows.
|
|
50
|
+
- **Two surfaces, one config**: `tree_annotated_plot.plot()` and the `tree-annotated-plot`
|
|
51
|
+
CLI both accept the same parameters via `PlotConfig`. They converge on
|
|
52
|
+
`_build(tree, chart, config)` so they can never disagree.
|
|
53
|
+
- **Tests must remain green at every commit on `main`.** Single-developer
|
|
54
|
+
workflow with no PRs (yet) — discipline replaces review. Lint + format +
|
|
55
|
+
pytest before committing:
|
|
56
|
+
```bash
|
|
57
|
+
scripts/check.sh
|
|
58
|
+
```
|
|
59
|
+
The same three checks run in CI via
|
|
60
|
+
[`.github/workflows/ci.yml`](.github/workflows/ci.yml) on push to
|
|
61
|
+
`main` and on PRs (CI also fetches the upstream Kikawa Auspice JSONs
|
|
62
|
+
so real-data tests run rather than skip). Local green ≈ CI green.
|
|
63
|
+
- **Build docs alongside checks** when changing public API surface:
|
|
64
|
+
```bash
|
|
65
|
+
scripts/build_docs.sh
|
|
66
|
+
```
|
|
67
|
+
`--strict` mode catches broken mkdocstrings references and missing
|
|
68
|
+
cross-links. The script first calls
|
|
69
|
+
`scripts/generate_docs_assets.py` to render `docs/images/*.svg`
|
|
70
|
+
and `docs/charts/*.html` from the example modules; both
|
|
71
|
+
directories are **gitignored** — never commit anything into them.
|
|
72
|
+
The same asset script runs in CI before `mkdocs build`.
|
|
73
|
+
- **Eyeball examples after any rendering change.** There are no
|
|
74
|
+
image-snapshot regression tests (vl-convert/font/platform variance
|
|
75
|
+
makes them flaky), so visual regressions only get caught by
|
|
76
|
+
looking. After any change to `_plot.py`, `_tree.py`, the example
|
|
77
|
+
scripts, or anything they touch: run `scripts/build_docs.sh`, open
|
|
78
|
+
`site/examples.html`, and click through each example — both the
|
|
79
|
+
embedded SVG and the interactive HTML link. Confirm tip-row
|
|
80
|
+
alignment, tree orientation, scale bar (if applicable),
|
|
81
|
+
tooltips, and any selection/cohort bindings still work. The full
|
|
82
|
+
recipe is in `README.md` under "Visual verification after code
|
|
83
|
+
changes". When telling the user a rendering-affecting task is
|
|
84
|
+
done, also report whether you ran this check (and that you
|
|
85
|
+
cannot, from a non-interactive shell, actually verify the rendered
|
|
86
|
+
output yourself — they need to look).
|
|
87
|
+
- **Docs are auto-deployed**. The
|
|
88
|
+
[`.github/workflows/docs.yml`](.github/workflows/docs.yml)
|
|
89
|
+
workflow runs on every push to `main`, generates assets, builds
|
|
90
|
+
with `mkdocs build --strict`, and deploys to GitHub Pages
|
|
91
|
+
(https://jbloomlab.github.io/tree-annotated-plot/). Pages source
|
|
92
|
+
must be set to "GitHub Actions" in repo Settings; the workflow's
|
|
93
|
+
`permissions:` block already declares `pages: write` and
|
|
94
|
+
`id-token: write` so no further config is needed.
|
|
95
|
+
- **Releases are tag-driven**. Push a `vX.Y.Z` tag (after bumping
|
|
96
|
+
`pyproject.toml`'s `version`) and
|
|
97
|
+
[`.github/workflows/release.yml`](.github/workflows/release.yml)
|
|
98
|
+
builds wheel + sdist and publishes to PyPI via trusted publishing
|
|
99
|
+
(OIDC — no token in the repo). The build job verifies the tag and
|
|
100
|
+
`pyproject.toml` version match before publishing. Per-release recipe
|
|
101
|
+
and one-time PyPI configuration steps are in `README.md` under
|
|
102
|
+
"Releasing a new version".
|
|
103
|
+
- **Adding a new example** (full recipe in `README.md`):
|
|
104
|
+
1. Self-contained module under `examples/` with module-level
|
|
105
|
+
helpers (callable from outside).
|
|
106
|
+
2. New clause in `scripts/generate_docs_assets.py` that produces a
|
|
107
|
+
`.svg` (into `docs/images/`) + `.html` (into `docs/charts/`).
|
|
108
|
+
3. New section in `docs/examples.md` matching the existing
|
|
109
|
+
three-section template (motivation → embedded SVG +
|
|
110
|
+
interactive link → reproduce: CLI first, Python second).
|
|
111
|
+
4. `scripts/build_docs.sh` confirms the page renders.
|
|
112
|
+
- **Iterating on docs**: prefer `mkdocs serve` (live-reload local
|
|
113
|
+
server) for content/layout changes — much faster than
|
|
114
|
+
`build_docs.sh` and shows the actual rendered page in your
|
|
115
|
+
browser. Use `build_docs.sh` for the strict pre-commit
|
|
116
|
+
verification.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Next steps
|
|
2
|
+
|
|
3
|
+
Phase 2 is complete. This file is the to-do list for resuming —
|
|
4
|
+
either by Jesse picking up later or by a future Claude session.
|
|
5
|
+
Lifecycle: shrink as items get done; delete the file when only
|
|
6
|
+
hypothetical Phase 3 work is left.
|
|
7
|
+
|
|
8
|
+
## Immediate actions (not yet done)
|
|
9
|
+
|
|
10
|
+
1. **Push to GitHub.** Local commits haven't been pushed yet.
|
|
11
|
+
```bash
|
|
12
|
+
git push -u origin main
|
|
13
|
+
```
|
|
14
|
+
2. **Enable Pages.** GitHub repo → Settings → Pages → Source:
|
|
15
|
+
"GitHub Actions". This activates the dormant
|
|
16
|
+
`.github/workflows/docs.yml` so the next push to `main` builds
|
|
17
|
+
and deploys the docs site to
|
|
18
|
+
https://jbloomlab.github.io/tree-annotated-plot/.
|
|
19
|
+
3. **Verify the deployed site.** Click through Home / Python API /
|
|
20
|
+
Command line / Examples. On the Examples page, each "Open the
|
|
21
|
+
interactive chart →" link should open a working interactive
|
|
22
|
+
Altair chart with tooltips and selection bindings. If any link
|
|
23
|
+
404s or any image is missing, the `Generate chart assets` step
|
|
24
|
+
in the workflow probably failed — check the Actions tab.
|
|
25
|
+
4. **Bump the version + cut a tag** when you're ready for v0.1.0.
|
|
26
|
+
`pyproject.toml`'s `version = "0.0.1"` is an alpha placeholder.
|
|
27
|
+
```bash
|
|
28
|
+
# edit pyproject.toml: version = "0.1.0"
|
|
29
|
+
git commit -am "release v0.1.0" && git tag v0.1.0
|
|
30
|
+
git push && git push --tags
|
|
31
|
+
```
|
|
32
|
+
5. **Add a GitHub action to run the tests**: this should include running any tests, examples, linting, and code formatting
|
|
33
|
+
6. **Add installation instructions to the docs and README**: assume it can be installed from PyPI but have documentation also link to GitHub source
|
|
34
|
+
|
|
35
|
+
## Phase 3 candidate features (deferred from Phase 2)
|
|
36
|
+
|
|
37
|
+
The Phase 2 plan's "Out of scope" section listed these explicitly
|
|
38
|
+
and the user signed off on deferring them. Pick from this list when
|
|
39
|
+
adding the next round of features:
|
|
40
|
+
|
|
41
|
+
- **Clade coloring / tree decoration.** API shape undecided. Two
|
|
42
|
+
options on the table:
|
|
43
|
+
- Declarative: `clades=[{"node_attr": "subclade", "values_to_colors": {...}}]` and the package paints branches.
|
|
44
|
+
- Lower-level: expose `tree_chart()` returning the tree panel as
|
|
45
|
+
an editable `alt.Chart` so users layer their own decoration.
|
|
46
|
+
- **Image-snapshot regression tests.** Render via
|
|
47
|
+
`vl-convert-python` (already a dep) to PNG and compare with a
|
|
48
|
+
structural diff or perceptual-similarity metric. Tool choice
|
|
49
|
+
undecided (`pytest-mpl` is matplotlib-only; would need to find or
|
|
50
|
+
build something for Vega-Lite output).
|
|
51
|
+
- **Newick / Nexus tree input.** Auspice JSON v2 is the only tree
|
|
52
|
+
format today.
|
|
53
|
+
- **Static (matplotlib) backend.** Only altair-via-Vega-Lite today.
|
|
54
|
+
- **HTML chart-spec extraction beyond the default altair template.**
|
|
55
|
+
Today we parse `var spec = {...}` out of altair's standard
|
|
56
|
+
`to_html()` output via stdlib `html.parser` +
|
|
57
|
+
`json.JSONDecoder.raw_decode()`. Custom `chart.save(template=...)`
|
|
58
|
+
outputs would need a real JS-AST parser (e.g. `esprima`).
|
|
59
|
+
- **Per-clade collapsing of subtrees** (different from the
|
|
60
|
+
topology-pruning we already do for `prune_tree_to_chart`).
|
|
61
|
+
- **Vega-Lite v5 → v6 auto-upgrade** of stale specs. We currently
|
|
62
|
+
detect-and-report; we don't translate.
|
|
63
|
+
|
|
64
|
+
## Smaller polish items
|
|
65
|
+
|
|
66
|
+
- **Add `CHANGELOG.md`.** None today; `git log --oneline` is the
|
|
67
|
+
de-facto record. Worth one if multiple releases pile up.
|
|
68
|
+
- **Add a PyPI publish workflow.**
|
|
69
|
+
`.github/workflows/release.yml` triggered on tag, building wheel +
|
|
70
|
+
sdist, uploading via trusted publishing. None today; would
|
|
71
|
+
complement the existing docs-deploy workflow.
|
|
72
|
+
- **Compress the H1N1 docs PNG** (currently ~1 MB). Options:
|
|
73
|
+
- Pass `optimize=True` to the PIL save (might need post-processing
|
|
74
|
+
after `chart.save()`).
|
|
75
|
+
- Lower PPI in `scripts/generate_docs_assets.py` from 96 to 72.
|
|
76
|
+
- Crop or resize after rendering.
|
|
77
|
+
- **Investigate the "Automatically deduplicated selection parameter"
|
|
78
|
+
altair warning** that appears when the Kikawa chart-builder runs.
|
|
79
|
+
Pre-existing and cosmetic, but it'd be cleaner to silence
|
|
80
|
+
(probably needs explicit `name=` on the selection params).
|
|
81
|
+
- **Add badges to README** Add badges for GitHub Actions test and PyPI version to the README.
|
|
82
|
+
Is there also a badge I can add for the docs, or other badges to consider?
|
|
83
|
+
|
|
84
|
+
## State at session close
|
|
85
|
+
|
|
86
|
+
- 21 commits on `main`, none pushed.
|
|
87
|
+
- All 102 tests pass via `scripts/check.sh`.
|
|
88
|
+
- `scripts/build_docs.sh` produces a viewable `site/` from a clean
|
|
89
|
+
checkout.
|
|
90
|
+
- `plan.md` is gone (disassembled per its own Lifecycle section in
|
|
91
|
+
commit `b9beb66`).
|
|
92
|
+
- `docs_plan.md` is gone (transient design note for the docs
|
|
93
|
+
deployment + asset strategy; written, reviewed, and removed in
|
|
94
|
+
commit `f1f551d`).
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tree-annotated-plot
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Annotate the axis of an Altair / Vega-Lite plot with a phylogenetic tree.
|
|
5
|
+
Project-URL: Documentation, https://jbloomlab.github.io/tree-annotated-plot/
|
|
6
|
+
Project-URL: Homepage, https://github.com/jbloomlab/tree-annotated-plot
|
|
7
|
+
Project-URL: Repository, https://github.com/jbloomlab/tree-annotated-plot
|
|
8
|
+
Project-URL: Issues, https://github.com/jbloomlab/tree-annotated-plot/issues
|
|
9
|
+
Author-email: Jesse Bloom <jbloom@fredhutch.org>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Operating System :: OS Independent
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
34
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
35
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
36
|
+
Requires-Python: >=3.13
|
|
37
|
+
Requires-Dist: altair<7,>=6.0
|
|
38
|
+
Requires-Dist: click>=8.0
|
|
39
|
+
Requires-Dist: pandas>=2.0
|
|
40
|
+
Requires-Dist: vl-convert-python>=1.9
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: black>=24; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
44
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
45
|
+
Provides-Extra: docs
|
|
46
|
+
Requires-Dist: mkdocs-click>=0.8; extra == 'docs'
|
|
47
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
48
|
+
Requires-Dist: mkdocs>=1.6; extra == 'docs'
|
|
49
|
+
Requires-Dist: mkdocstrings[python]>=0.27; extra == 'docs'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
# `tree-annotated-plot`: annotate the axis of an Altair / Vega-Lite plot with a phylogenetic tree
|
|
53
|
+
|
|
54
|
+
[](https://opensource.org/licenses/MIT)
|
|
55
|
+
[](https://github.com/psf/black)
|
|
56
|
+
[](https://github.com/astral-sh/ruff)
|
|
57
|
+
|
|
58
|
+
This is a Python package from the [Bloom lab](https://jbloomlab.org/) that allows you to combine an Altair / Vega-Lite plot with a Nextstrain JSON of a phylogenetic tree so that the tree is aligned to annotate strains on the axis of the plot.
|
|
59
|
+
See [https://jbloomlab.github.io/tree-annotated-plot/](https://jbloomlab.github.io/tree-annotated-plot/) for detailed documentation.
|
|
60
|
+
|
|
61
|
+
> **Note:** charts must be saved from altair 6+ (Vega-Lite v6). Older specs raise by default; pass `--no-strict-version` (CLI) or `strict_version=False` (Python) to override at your own risk. See the [docs](https://jbloomlab.github.io/tree-annotated-plot/) for details.
|
|
62
|
+
|
|
63
|
+
## Notes for developing the package
|
|
64
|
+
|
|
65
|
+
### Installation (development)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python3 -m venv .venv
|
|
69
|
+
source .venv/bin/activate
|
|
70
|
+
pip install -e ".[dev,docs]"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
[pyproject.toml](pyproject.toml) is the canonical source for the supported Python
|
|
74
|
+
version and runtime dependencies.
|
|
75
|
+
|
|
76
|
+
### Visual verification after code changes
|
|
77
|
+
|
|
78
|
+
This package has automated tests for chart **structure** (encodings,
|
|
79
|
+
sort order, tip reconciliation, scale-bar arithmetic, etc.), but
|
|
80
|
+
**not** for chart **appearance** — there are no image-snapshot
|
|
81
|
+
regression tests, because rendering is sensitive to fonts,
|
|
82
|
+
`vl-convert-python` versions, and platform differences in ways that
|
|
83
|
+
make pixel-level comparison flaky in practice.
|
|
84
|
+
|
|
85
|
+
So the discipline is manual: any time you change code that affects
|
|
86
|
+
rendering (`src/tree_annotated_plot/_plot.py`,
|
|
87
|
+
`src/tree_annotated_plot/_tree.py`, the example scripts under
|
|
88
|
+
`examples/`, or anything they touch), run the full check below and
|
|
89
|
+
**eyeball every example** before considering the change done.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
bash scripts/check.sh # lint + format + pytest (all 100+ tests)
|
|
93
|
+
bash scripts/build_docs.sh # regenerates docs/images + docs/charts and runs mkdocs --strict
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then open `site/examples.html` in a browser and, for each example:
|
|
97
|
+
|
|
98
|
+
1. Look at the embedded SVG screenshot. Does it match what the
|
|
99
|
+
feature you changed should produce? Are tip rows still aligned with
|
|
100
|
+
the chart rows? Is the tree's tip-end facing the chart? Is the
|
|
101
|
+
scale bar (if enabled) centered and labeled correctly?
|
|
102
|
+
2. Click "Open the interactive chart →". Hover tips to confirm
|
|
103
|
+
tooltips fire; if there's a cohort selector (Kikawa examples),
|
|
104
|
+
toggle it; pan/zoom if relevant.
|
|
105
|
+
3. Compare against the
|
|
106
|
+
[deployed site](https://jbloomlab.github.io/tree-annotated-plot/)
|
|
107
|
+
(which still reflects `main` before your change) for what "good"
|
|
108
|
+
looks like.
|
|
109
|
+
|
|
110
|
+
If something visibly regresses, fix it before pushing — there's no
|
|
111
|
+
CI gate that will catch a visual regression for you.
|
|
112
|
+
|
|
113
|
+
### Continuous integration
|
|
114
|
+
|
|
115
|
+
[`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs on every
|
|
116
|
+
push to `main` and every pull request: it installs the package, fetches
|
|
117
|
+
the upstream Kikawa Auspice JSONs (so the real-data tests actually
|
|
118
|
+
exercise rather than skip), builds the Kikawa titer chart specs, and
|
|
119
|
+
runs `ruff check`, `black --check`, and `pytest tests/`. This mirrors
|
|
120
|
+
[`scripts/check.sh`](scripts/check.sh) — green locally should mean
|
|
121
|
+
green in CI.
|
|
122
|
+
|
|
123
|
+
A separate workflow,
|
|
124
|
+
[`.github/workflows/docs.yml`](.github/workflows/docs.yml), builds the
|
|
125
|
+
docs site on every push to `main` and every PR (catching broken
|
|
126
|
+
mkdocstrings refs / missing images / unresolved cross-links before
|
|
127
|
+
merge), and additionally deploys `site/` to GitHub Pages on push to
|
|
128
|
+
`main`.
|
|
129
|
+
|
|
130
|
+
A third workflow,
|
|
131
|
+
[`.github/workflows/release.yml`](.github/workflows/release.yml),
|
|
132
|
+
builds the sdist + wheel and publishes them to PyPI when a `v*` tag
|
|
133
|
+
is pushed (see "Releasing a new version" below).
|
|
134
|
+
|
|
135
|
+
### Releasing a new version
|
|
136
|
+
|
|
137
|
+
Releases are fully automated by tag push. Trusted publishing (OIDC)
|
|
138
|
+
means **no API token or password is stored in the repo** — PyPI
|
|
139
|
+
authenticates the workflow via its repo + workflow filename +
|
|
140
|
+
environment.
|
|
141
|
+
|
|
142
|
+
One-time setup (only needed before the first release):
|
|
143
|
+
|
|
144
|
+
1. Reserve `tree-annotated-plot` on [PyPI](https://pypi.org/) (first
|
|
145
|
+
successful publish creates it; or claim it manually via
|
|
146
|
+
`pip install build && python -m build && twine upload dist/*` from a
|
|
147
|
+
maintainer machine).
|
|
148
|
+
2. On PyPI, go to the project → "Publishing" tab → "Add a new pending
|
|
149
|
+
publisher". Fill in: owner `jbloomlab`, repository
|
|
150
|
+
`tree-annotated-plot`, workflow filename `release.yml`, environment
|
|
151
|
+
`pypi`.
|
|
152
|
+
3. (Optional but recommended) On GitHub, repo Settings → Environments
|
|
153
|
+
→ New environment → name `pypi`. Add yourself as a required
|
|
154
|
+
reviewer if you want a manual approval gate before each release
|
|
155
|
+
lands on PyPI.
|
|
156
|
+
|
|
157
|
+
Per-release recipe (every time):
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# 1. Edit pyproject.toml and bump `version = "X.Y.Z"`.
|
|
161
|
+
# 2. Commit and tag (the workflow verifies they match before publishing).
|
|
162
|
+
git commit -am "release vX.Y.Z"
|
|
163
|
+
git tag vX.Y.Z
|
|
164
|
+
git push && git push --tags
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The `release` workflow then builds, verifies tag ⇋ version match, and
|
|
168
|
+
publishes to PyPI.
|
|
169
|
+
|
|
170
|
+
### Documentation
|
|
171
|
+
The docs are at
|
|
172
|
+
[https://jbloomlab.github.io/tree-annotated-plot/](https://jbloomlab.github.io/tree-annotated-plot/).
|
|
173
|
+
They are auto-deployed by GitHub Actions on every push to `main` —
|
|
174
|
+
[`.github/workflows/docs.yml`](.github/workflows/docs.yml) installs
|
|
175
|
+
the package + docs extras, runs
|
|
176
|
+
[`scripts/generate_docs_assets.py`](scripts/generate_docs_assets.py)
|
|
177
|
+
to render images and standalone interactive HTML for each
|
|
178
|
+
example, then `mkdocs build --strict`, and uploads `site/` as a
|
|
179
|
+
GitHub Pages artifact.
|
|
180
|
+
|
|
181
|
+
### Building documentation locally
|
|
182
|
+
|
|
183
|
+
For day-to-day editing of the docs, use MkDocs's live-reload server
|
|
184
|
+
(saves go straight to your browser):
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
mkdocs serve # http://localhost:8000
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
To do this on the Fred Hutch remote server, do:
|
|
191
|
+
```bash
|
|
192
|
+
mkdocs serve -a $(hostname -i | awk '{print $1}'):$(fhfreeport)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
For the strict build that matches CI (catches broken
|
|
196
|
+
[`mkdocstrings`](https://mkdocstrings.github.io/) refs, missing
|
|
197
|
+
images, unresolved cross-links):
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
bash scripts/build_docs.sh
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This first runs `scripts/generate_docs_assets.py` (so the example
|
|
204
|
+
SVGs and interactive HTMLs exist before MkDocs reads `docs/`), then
|
|
205
|
+
`mkdocs build --strict`. Output lands in `site/`.
|
|
206
|
+
|
|
207
|
+
### Adding a new example
|
|
208
|
+
|
|
209
|
+
The docs follow a single template per example: a short motivation,
|
|
210
|
+
an embedded SVG screenshot, a link to the fully-interactive
|
|
211
|
+
standalone HTML, and the commands to reproduce (CLI form first,
|
|
212
|
+
Python API form second). [`docs/examples.md`](docs/examples.md)
|
|
213
|
+
shows the existing shape. To add another:
|
|
214
|
+
|
|
215
|
+
1. Drop a runnable script into `examples/`. Keep helpers at module
|
|
216
|
+
level (callable from outside) so the asset script can import them.
|
|
217
|
+
2. Add a clause to `scripts/generate_docs_assets.py` that imports
|
|
218
|
+
the script and calls `tree_annotated_plot.plot(...)` followed by saving both
|
|
219
|
+
`<name>.svg` (into `docs/images/`) and `<name>.html` (into
|
|
220
|
+
`docs/charts/`). Both directories are gitignored — only the
|
|
221
|
+
asset script writes there.
|
|
222
|
+
3. Add a section to `docs/examples.md` matching the template.
|
|
223
|
+
4. Run `bash scripts/build_docs.sh` and open `site/examples.html`
|
|
224
|
+
to verify. Push when happy; the deployed site updates on the
|
|
225
|
+
next CI run.
|