tree-annotated-plot 0.3.0__tar.gz → 0.5.0__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 (45) hide show
  1. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/CHANGELOG.md +63 -1
  2. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/CLAUDE.md +14 -2
  3. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/PKG-INFO +3 -3
  4. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/docs/examples.md +71 -0
  5. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/examples/flu-seqneut-2025to2026_titer_charts.py +11 -0
  6. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/pyproject.toml +9 -2
  7. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/scripts/generate_docs_assets.py +43 -0
  8. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/src/tree_annotated_plot/_config.py +53 -6
  9. tree_annotated_plot-0.5.0/src/tree_annotated_plot/_label_style.py +349 -0
  10. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/src/tree_annotated_plot/_plot.py +117 -77
  11. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/src/tree_annotated_plot/cli.py +62 -12
  12. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_appearance_tuning.py +27 -0
  13. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_chart_loader.py +108 -0
  14. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_cli.py +1 -0
  15. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_label_connection.py +48 -3
  16. tree_annotated_plot-0.5.0/tests/test_strain_label_styles.py +565 -0
  17. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_tree_location.py +52 -0
  18. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/.github/workflows/docs.yml +0 -0
  19. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/.github/workflows/release.yml +0 -0
  20. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/.github/workflows/tests.yml +0 -0
  21. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/.gitignore +0 -0
  22. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/LICENSE +0 -0
  23. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/README.md +0 -0
  24. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/docs/cli.md +0 -0
  25. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/docs/index.md +0 -0
  26. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/docs/python-api.md +0 -0
  27. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/examples/fetch_auspice_data.py +0 -0
  28. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/examples/synthetic_example.py +0 -0
  29. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/mkdocs.yml +0 -0
  30. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/scripts/build_docs.sh +0 -0
  31. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/scripts/check.sh +0 -0
  32. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/src/tree_annotated_plot/__init__.py +0 -0
  33. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/src/tree_annotated_plot/_color.py +0 -0
  34. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/src/tree_annotated_plot/_tree.py +0 -0
  35. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_branch_length.py +0 -0
  36. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_color_tree.py +0 -0
  37. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_examples_run.py +0 -0
  38. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_introspection.py +0 -0
  39. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_line_order.py +0 -0
  40. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_plot.py +0 -0
  41. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_plot_config.py +0 -0
  42. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_real_data.py +0 -0
  43. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_reconciliation.py +0 -0
  44. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_scale_bar.py +0 -0
  45. {tree_annotated_plot-0.3.0 → tree_annotated_plot-0.5.0}/tests/test_style.py +0 -0
@@ -5,7 +5,69 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.5.0] - 2026-10-30
9
+
10
+ ### Added
11
+
12
+ - `strain_label_styles` (default `None`): style a subset of the strain labels
13
+ rendered in the tree panel when `connect_leader_to_label` is on, e.g. to draw
14
+ the vaccine strains' labels in bold red. A list of rules, each naming some
15
+ `strains` and a `style` of Vega-Lite text properties (`fill`, `opacity`,
16
+ `fontWeight`, `fontStyle`). Where rules overlap, later rules win property by
17
+ property. An optional `when` condition applies a rule only while it holds:
18
+ - the name of a parameter declared in the chart, such as a checkbox;
19
+ - any Vega expression, e.g. `"emphasis == 'cell'"` for a radio button;
20
+ - in Python, an `alt.Parameter`, which is declared on the returned chart if
21
+ the chart doesn't already declare it.
22
+
23
+ Each styled property becomes a per-label expression on the existing label
24
+ layers, so no layers are added and the returned chart keeps its shape. A
25
+ strain that isn't a tree tip raises; strains dropped by `prune_tree_to_chart`
26
+ are skipped. CLI form: `--strain-label-styles` with a JSON array, where
27
+ `when` names a parameter saved in the chart JSON.
28
+ ([#12](https://github.com/jbloomlab/tree-annotated-plot/issues/12))
29
+
30
+ ### Fixed
31
+
32
+ - A chart with selection parameters on a compound chart (layer, facet, or
33
+ concat) failed to render when passed to `plot()` as a JSON path, HTML path
34
+ or spec dict, which is every CLI use. Vega-Lite reported
35
+ `Duplicate signal name: "<selection>_tuple"`. This affected the flu examples'
36
+ CLI reproductions. The chart loader removed each selection's `views` before
37
+ loading, and altair then emitted empty `views`, which makes Vega-Lite define
38
+ the selection in every view. The loader now leaves the spec's `views` intact.
39
+ Live altair charts were never affected.
40
+ - The `plot()` docstring's Returns section described the panel order wrongly
41
+ for a strain on `x`. The default `tree_location="bottom"` puts the chart
42
+ first and the tree second. The section now gives the order for each
43
+ `tree_location`.
44
+
45
+ ## [0.4.0] - 2026-08-20
46
+
47
+ ### Added
48
+
49
+ - `spacing` (default `0`): pixels of blank space between the tree panel and
50
+ the chart panel. `0` keeps the two butted together, as before. Distinct from
51
+ `shift_tree_loc`, which moves the tree within its own panel by resizing the
52
+ label strip. CLI form: `--spacing`.
53
+
54
+ ### Changed
55
+
56
+ - Pin `ruff` to `>=0.16,<0.17` and state the lint rules explicitly with
57
+ `[tool.ruff.lint] select`. Ruff's *default* rule set widened in 0.16, which
58
+ turned CI red on unchanged code; the selection is now a project decision
59
+ rather than whatever the installed ruff defaults to. Development-only, no
60
+ effect on the package.
61
+
62
+ ### Fixed
63
+
64
+ - With `connect_leader_to_label` on, strain labels were anchored exactly on the
65
+ tree panel's chart-facing edge, so the text ran up against the chart's frame
66
+ and the frame line became hard to read. The pad that the label strip already
67
+ reserved is now applied between the text and the chart, and the leader lines
68
+ stop where the text begins rather than continuing to the panel edge. The gap
69
+ is `max(3, strain_label_font_size * 0.2)` px, and the tree panel grows by that
70
+ much, so a hand-tuned `shift_tree_loc` may want the same adjustment.
9
71
 
10
72
  ## [0.3.0] - 2026-05-19
11
73
 
@@ -7,7 +7,9 @@
7
7
  section as you make them — new parameters, behavior changes, removed
8
8
  features, bug fixes — in [Keep a Changelog](https://keepachangelog.com/)
9
9
  format. At release time, `## [Unreleased]` is renamed to
10
- `## [X.Y.Z] - YYYY-MM-DD` and a fresh `## [Unreleased]` is created.
10
+ `## [X.Y.Z] - YYYY-MM-DD`, with no empty `## [Unreleased]` left above it.
11
+ When the next change lands and there's no `## [Unreleased]` section, add
12
+ one at the top.
11
13
  - **Single source of truth — `pyproject.toml`**: canonical for dependencies,
12
14
  supported Python version, build config, tool settings. Don't restate any of
13
15
  these in prose; refer to `pyproject.toml`.
@@ -52,6 +54,13 @@
52
54
  on `x` or `y`, overrides its sort to match the tree's tip order, and
53
55
  hconcat's or vconcat's a tree panel on the matching side. Tree dictates
54
56
  tip ordering; plot follows.
57
+ - **Loaded specs go to `from_dict` unmodified.** Don't strip or rewrite
58
+ altair's annotations when loading a dict / JSON / HTML chart. Selection
59
+ params' `views` in particular are valid at the top level; removing them made
60
+ every compound chart with a selection fail to render ("Duplicate signal
61
+ name"). See `_load_chart`. Likewise, declare extra params by appending to
62
+ the combined chart's top-level `params`, never with `add_params`, which
63
+ recurses into sub-charts and raises on spec-loaded ones.
55
64
  - **Two surfaces, one config**: `tree_annotated_plot.plot()` and the `tree-annotated-plot`
56
65
  CLI both accept the same parameters via `PlotConfig`. They converge on
57
66
  `_build(tree, chart, config)` so they can never disagree.
@@ -78,7 +87,10 @@
78
87
  - **Eyeball examples after any rendering change.** There are no
79
88
  image-snapshot regression tests (vl-convert/font/platform variance
80
89
  makes them flaky), so visual regressions only get caught by
81
- looking. After any change to `_plot.py`, `_tree.py`, the example
90
+ looking. Tests may still render with `vl_convert.vegalite_to_svg`
91
+ to check that a spec renders at all, or to assert discrete SVG
92
+ attributes such as a label's `font-weight` / `fill`; those are
93
+ deterministic. After any change to `_plot.py`, `_tree.py`, the example
82
94
  scripts, or anything they touch: run `scripts/build_docs.sh`, open
83
95
  `site/examples.html`, and click through each example — both the
84
96
  embedded SVG and the interactive HTML link. Confirm tip-row
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: tree-annotated-plot
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Annotate the axis of an Altair / Vega-Lite plot with a phylogenetic tree.
5
5
  Project-URL: Documentation, https://jbloomlab.github.io/tree-annotated-plot/
6
6
  Project-URL: Homepage, https://github.com/jbloomlab/tree-annotated-plot
@@ -43,7 +43,7 @@ Requires-Dist: vl-convert-python>=1.9
43
43
  Provides-Extra: dev
44
44
  Requires-Dist: black>=24; extra == 'dev'
45
45
  Requires-Dist: pytest>=8; extra == 'dev'
46
- Requires-Dist: ruff>=0.6; extra == 'dev'
46
+ Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
47
47
  Provides-Extra: docs
48
48
  Requires-Dist: mkdocs-click>=0.8; extra == 'docs'
49
49
  Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
@@ -140,6 +140,77 @@ CLI flags: `--connect-leader-to-label --strain-label-font-size 9
140
140
  --shift-tree-loc 60`. In Python:
141
141
  `connect_leader_to_label=True, strain_label_font_size=9, shift_tree_loc=60`.
142
142
 
143
+ ### Optional: emphasize some strains' labels
144
+
145
+ With label connection on, you can also style a subset of the labels, for
146
+ example so the vaccine strains are easy to pick out. Pass
147
+ `strain_label_styles`, a list of rules. Each rule names some `strains`
148
+ (tree tip identifiers, i.e. `tree_strain_field` values) and a `style` built
149
+ from Vega-Lite text properties: `fill`, `opacity`, `fontWeight` and
150
+ `fontStyle`. A rule can also take a `when` condition, usually the name of a
151
+ checkbox parameter, so the style applies only while the box is ticked.
152
+ Where rules overlap, later rules win.
153
+
154
+ The example below draws the labels of the H3N2 recent vaccine strains in
155
+ bold red, with a checkbox that turns the emphasis off and on:
156
+
157
+ ![H3N2 with vaccine-strain labels in bold red](images/h3n2_combined_label_styles.svg)
158
+
159
+ [Open the interactive chart in a new tab →](charts/h3n2_combined_label_styles.html){target="_blank"}
160
+
161
+ From the CLI, a `when` parameter has to be declared in the saved chart, so
162
+ add it before saving:
163
+
164
+ ```python
165
+ chart = chart.add_params(
166
+ alt.param(
167
+ name="vaccine_strains",
168
+ value=True,
169
+ bind=alt.binding_checkbox(name="highlight vaccine strains "),
170
+ )
171
+ )
172
+ chart.save("titers.json")
173
+ ```
174
+
175
+ then pass the rules as a JSON array, alongside the label-connection flags
176
+ above:
177
+
178
+ ```bash
179
+ --connect-leader-to-label --strain-label-styles \
180
+ '[{"strains": ["J.2.4", "J.2:S145N"],
181
+ "style": {"fill": "#d62728", "fontWeight": "bold"},
182
+ "when": "vaccine_strains"}]'
183
+ ```
184
+
185
+ In Python, pass the parameter object itself. If your chart doesn't already
186
+ declare it, it is declared on the returned chart for you:
187
+
188
+ ```python
189
+ vaccine_strains = alt.param(
190
+ name="vaccine_strains",
191
+ value=True,
192
+ bind=alt.binding_checkbox(name="highlight vaccine strains "),
193
+ )
194
+ tree_annotated_plot.plot(
195
+ ...,
196
+ connect_leader_to_label=True,
197
+ strain_label_styles=[
198
+ {
199
+ "strains": ["J.2.4", "J.2:S145N"],
200
+ "style": {"fill": "#d62728", "fontWeight": "bold"},
201
+ "when": vaccine_strains,
202
+ }
203
+ ],
204
+ )
205
+ ```
206
+
207
+ Leave out `when` for a style that is always on. `when` can be any Vega
208
+ expression, so other inputs work too. For example, with a radio button
209
+ `alt.param(name="emphasis", value="cell", bind=alt.binding_radio(options=["cell", "egg"]))`
210
+ declared on the chart, one rule with `"when": "emphasis == 'cell'"` and
211
+ another with `"when": "emphasis == 'egg'"` switch the emphasis between two
212
+ groups of strains.
213
+
143
214
  ### Color the tree
144
215
 
145
216
  Pass `color_tree_by` to color the tree's branches and tip circles by
@@ -131,6 +131,17 @@ def strain_plot_order(viruses_subset: pd.DataFrame) -> list[str]:
131
131
  return sorted_df["virus"].tolist()
132
132
 
133
133
 
134
+ def vaccine_axis_labels(viruses: pd.DataFrame, subtype: str) -> list[str]:
135
+ """Axis labels of the `RECENT_VACCINE_STRAINS` in `subtype`, in table order.
136
+
137
+ These are the strain-axis values the chart (and so the tree's tips) use for
138
+ the vaccine strains, e.g. for `strain_label_styles`."""
139
+ is_vaccine = (viruses["subtype"] == subtype) & viruses["virus"].isin(
140
+ RECENT_VACCINE_STRAINS
141
+ )
142
+ return viruses.loc[is_vaccine, "axis_label"].tolist()
143
+
144
+
134
145
  def make_chart(
135
146
  subtype: str,
136
147
  chart_type: str,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tree-annotated-plot"
7
- version = "0.3.0"
7
+ version = "0.5.0"
8
8
  description = "Annotate the axis of an Altair / Vega-Lite plot with a phylogenetic tree."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -36,7 +36,7 @@ Issues = "https://github.com/jbloomlab/tree-annotated-plot/issues"
36
36
  [project.optional-dependencies]
37
37
  dev = [
38
38
  "pytest>=8",
39
- "ruff>=0.6",
39
+ "ruff>=0.16,<0.17",
40
40
  "black>=24",
41
41
  ]
42
42
  docs = [
@@ -58,3 +58,10 @@ target-version = ["py311"]
58
58
 
59
59
  [tool.ruff]
60
60
  target-version = "py311"
61
+
62
+ [tool.ruff.lint]
63
+ # Ruff's default rule set is not stable across releases -- 0.16 broadened it,
64
+ # which turned CI red on unchanged code. State the rules we lint against so a
65
+ # ruff upgrade changes them only when we say so. These are ruff's pre-0.16
66
+ # defaults, which is what this project has been checked against all along.
67
+ select = ["E4", "E7", "E9", "F"]
@@ -25,6 +25,8 @@ import sys
25
25
  from pathlib import Path
26
26
  from types import ModuleType
27
27
 
28
+ import altair as alt
29
+
28
30
  import tree_annotated_plot
29
31
 
30
32
  REPO = Path(__file__).resolve().parent.parent
@@ -163,6 +165,47 @@ def _render_kikawa() -> None:
163
165
  )
164
166
  _save_pair(out, "h3n2_combined_label_connect")
165
167
 
168
+ # The same label-connect H3N2 chart, with the recent vaccine strains'
169
+ # labels drawn in bold red while a checkbox is ticked: `strain_label_styles`
170
+ # with a `when` param. The checkbox starts ticked so the static SVG shows
171
+ # the emphasis.
172
+ h3n2_chart_styles = builder.make_chart(
173
+ subtype="H3N2",
174
+ chart_type="iqr",
175
+ titers=titers,
176
+ viruses=viruses,
177
+ metadata=metadata,
178
+ all_cohorts=all_cohorts,
179
+ )
180
+ vaccine_strains = alt.param(
181
+ name="vaccine_strains",
182
+ value=True,
183
+ bind=alt.binding_checkbox(name="highlight vaccine strains "),
184
+ )
185
+ out = tree_annotated_plot.plot(
186
+ DATA_DIR / "flu-seqneut-2025to2026_H3N2.json",
187
+ h3n2_chart_styles,
188
+ chart_strain_field="axis_label",
189
+ tree_strain_field="derived_haplotype",
190
+ branch_length="div",
191
+ tree_size=140,
192
+ scale_bar=True,
193
+ branch_length_units="substitutions",
194
+ connect_leader_to_label=True,
195
+ strain_label_font_size=9,
196
+ shift_tree_loc=60,
197
+ color_tree_by="subclade",
198
+ tree_color_legend_format={"orient": "left"},
199
+ strain_label_styles=[
200
+ {
201
+ "strains": builder.vaccine_axis_labels(viruses, "H3N2"),
202
+ "style": {"fill": "#d62728", "fontWeight": "bold"},
203
+ "when": vaccine_strains,
204
+ }
205
+ ],
206
+ )
207
+ _save_pair(out, "h3n2_combined_label_styles")
208
+
166
209
  # H3N2 once more, colored by genotype at HA1 site 158: same chart and
167
210
  # default layout as `h3n2_combined`, with `color_tree_by` switched to
168
211
  # the genotype form. Site 158 has two mutations (N158K, N158D) in the
@@ -68,6 +68,15 @@ class PlotConfig:
68
68
  "(x-encoded).",
69
69
  ] = None
70
70
 
71
+ spacing: Annotated[
72
+ int,
73
+ "Pixels of blank space between the tree panel and the chart panel. "
74
+ "Default 0, which butts the two together. Distinct from "
75
+ "`shift_tree_loc`, which moves the tree within its own panel by "
76
+ "resizing the label strip; `spacing` sets the gap between the panels "
77
+ "and applies whether or not `connect_leader_to_label` is on.",
78
+ ] = 0
79
+
71
80
  tree_line_width: Annotated[
72
81
  float,
73
82
  "Stroke width (px) for the tree's branch lines. Default 2.",
@@ -152,11 +161,34 @@ class PlotConfig:
152
161
  "when `connect_leader_to_label` is on.",
153
162
  ] = "normal"
154
163
 
164
+ strain_label_styles: Annotated[
165
+ list[dict[str, Any]] | None,
166
+ "Style a subset of the strain labels rendered in the tree panel; "
167
+ "requires `connect_leader_to_label`. A list of rules, each a dict "
168
+ 'with keys "strains" (a list of tip identifiers, i.e. '
169
+ '`tree_strain_field` values), "style" and, optionally, "when". '
170
+ '"style" holds Vega-Lite text-mark properties: "fill" (a color), '
171
+ '"opacity" (0 to 1), "fontWeight" ("normal", "bold", or 100-900) '
172
+ 'and "fontStyle" ("normal" or "italic"). "when" is a Vega '
173
+ "expression that turns the rule on while it is truthy, usually just "
174
+ "the name of a parameter declared in the chart (e.g. one bound to a "
175
+ "checkbox), but any expression works (e.g. \"emphasis == 'cell'\" "
176
+ 'for a radio button). Without "when" the style always applies. '
177
+ "Where rules overlap, later rules win property by property; "
178
+ "properties no rule sets keep `strain_label_font_weight` and the "
179
+ "default black, upright text. A strain that isn't a tree tip is an "
180
+ "error; strains dropped by `prune_tree_to_chart` are skipped. CLI "
181
+ "form: a JSON array (quote the whole argument), e.g. "
182
+ '\'[{"strains": ["K"], "style": {"fill": "#d62728", '
183
+ '"fontWeight": "bold"}, "when": "vaccine_strains"}]\'.',
184
+ ] = None
185
+
155
186
  shift_tree_loc: Annotated[
156
187
  int,
157
188
  "Pixels by which to shift the tree toward (positive) or away from "
158
- "(negative) the chart. Default 0. Has no effect when "
159
- "connect_leader_to_label is off.",
189
+ "(negative) the chart, by resizing the label strip inside the tree "
190
+ "panel. Default 0. Has no effect when connect_leader_to_label is off; "
191
+ "to set the gap between the tree and chart panels use `spacing`.",
160
192
  ] = 0
161
193
 
162
194
  color_tree_by: Annotated[
@@ -215,10 +247,25 @@ class PlotConfig:
215
247
 
216
248
 
217
249
  # Sidecar for Python-docstring-only prose, keyed by PlotConfig field name.
218
- # Empty by default — add an entry when a field's docstring entry needs more
219
- # than the CLI `--help` text covers. The extras are appended to the field's
220
- # description in `tree_annotated_plot.plot.__doc__`, never in CLI --help.
221
- PARAM_DOC_EXTRAS: dict[str, str] = {}
250
+ # Add an entry when a field's docstring entry needs more than the CLI
251
+ # `--help` text covers. The extras are appended to the field's description in
252
+ # `tree_annotated_plot.plot.__doc__`, never in CLI --help.
253
+ PARAM_DOC_EXTRAS: dict[str, str] = {
254
+ "strain_label_styles": (
255
+ 'In Python, "when" may also be an `altair.Parameter`, e.g. '
256
+ '`alt.param(name="vaccine_strains", value=True, '
257
+ "bind=alt.binding_checkbox())`, which is declared on the returned "
258
+ "chart if your chart doesn't already declare it; or an altair "
259
+ 'parameter expression such as `emphasis == "cell"`. Only a bare '
260
+ "`altair.Parameter` is declared for you: parameters that an "
261
+ "expression refers to must be declared on the chart, and an "
262
+ "expression other than a bare name isn't checked until the chart "
263
+ "renders. A bare name or parameter must be a variable parameter: "
264
+ "selection parameters are rejected, since the label data doesn't "
265
+ "carry the fields a selection tests. Any iterable of strings (e.g. "
266
+ 'a pandas Series) works for "strains".'
267
+ ),
268
+ }
222
269
 
223
270
 
224
271
  # Descriptions for the three data-input parameters. These can't live on