Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- `connect_leader_to_label` (default `False`): extends each strain's
dashed leader line all the way to its text label. When on, the
chart's strain-axis labels are suppressed and replacement labels
are rendered alongside the tree on its chart-facing edge.
- `strain_label_font_size` (default `10`), `strain_label_font_weight`
(default `"normal"`), and `shift_tree_loc` (default `0`) for tuning
the size, weight, and placement of the connected labels.

## [0.1.0] - 2026-05-04

Initial release.
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
- **Keep CLAUDE.md, README.md, and the docs site updated** when changing the
code. `CLAUDE.md` describes programming conventions; `README.md` describes
basic use; `docs/` is the user-facing reference. Don't let them drift.
- **Record user-facing changes in `CHANGELOG.md`** under the `## [Unreleased]`
section as you make them — new parameters, behavior changes, removed
features, bug fixes — in [Keep a Changelog](https://keepachangelog.com/)
format. At release time, `## [Unreleased]` is renamed to
`## [X.Y.Z] - YYYY-MM-DD` and a fresh `## [Unreleased]` is created.
- **Single source of truth — `pyproject.toml`**: canonical for dependencies,
supported Python version, build config, tool settings. Don't restate any of
these in prose; refer to `pyproject.toml`.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ pip install git+https://github.com/jbloomlab/tree-annotated-plot.git

For a development checkout, see [Installation (development)](#installation-development) below.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for the version history.

## Notes for developing the package

### Installation (development)
Expand Down
35 changes: 33 additions & 2 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,39 @@ With the tree panel added by `tree-annotated-plot`:
The strain axis is on `y`, so `tree_annotated_plot.plot` auto-picks
the **vertical layout** (`tree_location` defaults to `"left"` on a
y-encoded strain): result is an `HConcatChart` with the tree on the
left, tips flush against the chart's strain labels, and a centered
scale bar at the bottom of the tree panel.
left and a centered scale bar at the bottom of the tree panel. The
chart's natural strain-axis labels are kept exactly as the
chart-builder wrote them (fonts, ticks, axis title, and all), and the
tree's dashed leader lines stop at the tree panel's chart-facing
edge.

### Optional: connect leaders all the way to the labels

If you'd prefer the dashed leaders to run flush into the strain
labels themselves (with no break between tip and label), set
`connect_leader_to_label=True`. This involves moving the labels off
the chart's natural axis and into the tree panel, with a few
trade-offs to be aware of:

- The chart's strain-axis is replaced: any labels, ticks, title,
or custom `axis=...` you set on that encoding are dropped, and
replacement labels are rendered alongside the tree.
- Label widths are estimated, not measured exactly, so layout may
need tuning. The two main knobs are `strain_label_font_size`
(default 10) and `shift_tree_loc` (a manual pixel offset that
moves the tree closer to the labels).

The example below turns on label connection, shrinks the labels to
9 pt, and uses `shift_tree_loc=60` to bring the tree flush against
them:

![H3N2 with label connection at 9pt font](images/h3n2_combined_label_connect.svg)

[Open the interactive chart in a new tab →](charts/h3n2_combined_label_connect.html){target="_blank"}

CLI flags: `--connect-leader-to-label --strain-label-font-size 9
--shift-tree-loc 60`. In Python:
`connect_leader_to_label=True, strain_label_font_size=9, shift_tree_loc=60`.

### Reproduce — command line

Expand Down
27 changes: 27 additions & 0 deletions scripts/generate_docs_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,33 @@ def _render_kikawa() -> None:
)
_save_pair(out, f"{basename}_combined")

# H3N2 again, with `connect_leader_to_label=True` and a 9-point label
# font: opt-in label connection where leaders run flush into the
# labels rendered alongside the tree (the default keeps the chart's
# natural strain-axis labels untouched).
h3n2_chart = builder.make_chart(
subtype="H3N2",
chart_type="iqr",
titers=titers,
viruses=viruses,
metadata=metadata,
all_cohorts=all_cohorts,
)
out = tree_annotated_plot.plot(
DATA_DIR / "flu-seqneut-2025to2026_H3N2.json",
h3n2_chart,
chart_strain_field="axis_label",
tree_strain_field="derived_haplotype",
branch_length="div",
tree_size=140,
scale_bar=True,
branch_length_units="substitutions",
connect_leader_to_label=True,
strain_label_font_size=9,
shift_tree_loc=60,
)
_save_pair(out, "h3n2_combined_label_connect")


def main() -> None:
"""Render every example to SVG + interactive HTML under `docs/`."""
Expand Down
41 changes: 40 additions & 1 deletion src/tree_annotated_plot/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,39 @@ class PlotConfig:
"those become warnings and parsing proceeds.",
] = True

connect_leader_to_label: Annotated[
bool,
"Off (default): the chart's strain-axis labels are kept as the "
"user wrote them and dashed leader lines stop at the tree "
"panel's chart-facing edge. On: leaders extend all the way to "
"the labels — which requires moving the labels off the chart's "
"strain axis and into the tree panel, so the chart's "
"strain-axis labels, ticks, axis line, and title are SUPPRESSED "
"(any user-supplied `axis=...` is overridden) and replacement "
"labels are rendered alongside the tree. Label widths are "
"estimated; for crowded charts tune `strain_label_font_size` or "
"`shift_tree_loc`.",
] = False

strain_label_font_size: Annotated[
float,
"Font size (px) for the strain text labels rendered in the tree "
"panel when `connect_leader_to_label` is on.",
] = 10.0

strain_label_font_weight: Annotated[
Literal["normal", "bold"],
"Font weight for the strain text labels rendered in the tree panel "
"when `connect_leader_to_label` is on.",
] = "normal"

shift_tree_loc: Annotated[
int,
"Pixels by which to shift the tree toward (positive) or away from "
"(negative) the chart. Default 0. Has no effect when "
"connect_leader_to_label is off.",
] = 0


# Sidecar for Python-docstring-only prose, keyed by PlotConfig field name.
# Empty by default — add an entry when a field's docstring entry needs more
Expand Down Expand Up @@ -170,7 +203,11 @@ def _render_data_param(name: str, description: str, width: int = 75) -> str:
`_render_numpy_params`.
"""
body = textwrap.fill(
description, width=width, initial_indent=" ", subsequent_indent=" "
description,
width=width,
initial_indent=" ",
subsequent_indent=" ",
break_on_hyphens=False,
)
return f"{name}\n{body}"

Expand Down Expand Up @@ -200,6 +237,7 @@ def _render_numpy_params(
width=width,
initial_indent=" ",
subsequent_indent=" ",
break_on_hyphens=False,
)
)
if name in extras:
Expand All @@ -210,6 +248,7 @@ def _render_numpy_params(
width=width,
initial_indent=" ",
subsequent_indent=" ",
break_on_hyphens=False,
)
)
return "\n".join(chunks)
Loading