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.
Files changed (38) hide show
  1. tree_annotated_plot-0.0.1/.github/workflows/ci.yml +58 -0
  2. tree_annotated_plot-0.0.1/.github/workflows/docs.yml +66 -0
  3. tree_annotated_plot-0.0.1/.github/workflows/release.yml +82 -0
  4. tree_annotated_plot-0.0.1/.gitignore +10 -0
  5. tree_annotated_plot-0.0.1/CLAUDE.md +116 -0
  6. tree_annotated_plot-0.0.1/LICENSE +19 -0
  7. tree_annotated_plot-0.0.1/NEXT_STEPS.md +94 -0
  8. tree_annotated_plot-0.0.1/PKG-INFO +225 -0
  9. tree_annotated_plot-0.0.1/README.md +174 -0
  10. tree_annotated_plot-0.0.1/docs/cli.md +30 -0
  11. tree_annotated_plot-0.0.1/docs/examples.md +234 -0
  12. tree_annotated_plot-0.0.1/docs/index.md +68 -0
  13. tree_annotated_plot-0.0.1/docs/python-api.md +36 -0
  14. tree_annotated_plot-0.0.1/examples/fetch_auspice_data.py +57 -0
  15. tree_annotated_plot-0.0.1/examples/flu-seqneut-2025to2026_titer_charts.py +456 -0
  16. tree_annotated_plot-0.0.1/examples/synthetic_example.py +185 -0
  17. tree_annotated_plot-0.0.1/mkdocs.yml +63 -0
  18. tree_annotated_plot-0.0.1/pyproject.toml +58 -0
  19. tree_annotated_plot-0.0.1/scripts/build_docs.sh +29 -0
  20. tree_annotated_plot-0.0.1/scripts/check.sh +24 -0
  21. tree_annotated_plot-0.0.1/scripts/generate_docs_assets.py +137 -0
  22. tree_annotated_plot-0.0.1/src/tree_annotated_plot/__init__.py +11 -0
  23. tree_annotated_plot-0.0.1/src/tree_annotated_plot/_config.py +215 -0
  24. tree_annotated_plot-0.0.1/src/tree_annotated_plot/_plot.py +1337 -0
  25. tree_annotated_plot-0.0.1/src/tree_annotated_plot/_tree.py +307 -0
  26. tree_annotated_plot-0.0.1/src/tree_annotated_plot/cli.py +165 -0
  27. tree_annotated_plot-0.0.1/tests/test_branch_length.py +144 -0
  28. tree_annotated_plot-0.0.1/tests/test_chart_loader.py +271 -0
  29. tree_annotated_plot-0.0.1/tests/test_cli.py +204 -0
  30. tree_annotated_plot-0.0.1/tests/test_examples_run.py +30 -0
  31. tree_annotated_plot-0.0.1/tests/test_introspection.py +141 -0
  32. tree_annotated_plot-0.0.1/tests/test_plot.py +153 -0
  33. tree_annotated_plot-0.0.1/tests/test_plot_config.py +92 -0
  34. tree_annotated_plot-0.0.1/tests/test_real_data.py +378 -0
  35. tree_annotated_plot-0.0.1/tests/test_reconciliation.py +268 -0
  36. tree_annotated_plot-0.0.1/tests/test_scale_bar.py +264 -0
  37. tree_annotated_plot-0.0.1/tests/test_style.py +245 -0
  38. 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,10 @@
1
+ .*
2
+ !.git*
3
+ /_*
4
+ *.out
5
+ examples/data/
6
+ site/
7
+ docs/images/
8
+ docs/charts/
9
+ __pycache__/
10
+ *.pyc
@@ -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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
55
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
56
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](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.