Skip to content

inset strain labels from the chart edge and add a spacing parameter - #9

Merged
jbloom merged 3 commits into
mainfrom
label-inset-and-spacing
Aug 20, 2026
Merged

inset strain labels from the chart edge and add a spacing parameter#9
jbloom merged 3 commits into
mainfrom
label-inset-and-spacing

Conversation

@jbloom

@jbloom jbloom commented Aug 20, 2026

Copy link
Copy Markdown
Member

The bug

With connect_leader_to_label on, the strain labels are anchored exactly at
chart_edge_branch — the tree panel's chart-facing edge — and right-aligned, so the text
runs up against the chart's frame and the frame line becomes hard to read.

A pad was already being counted into the label strip's width:

label_pixel_width = label_pad_px + max_name_len * char_px + halo_px / 2
...
tips_df = tips_df.assign(x_label=chart_edge_branch)

but because the anchor sits on the edge, that pad lands on the tree side of the text and
none of it between the text and the chart. The strip reserved room for a gap that was never
drawn.

Spotted on the flu-seqneut-2026 titer charts, where the long haplotype labels make it
obvious.

The fix

The gap that clears the chart's frame is a different job from the pad that clears the
deepest tip, so it gets its own constant rather than reusing label_pad_px:

_LABEL_CHART_GAP_PX_MIN = 3
_LABEL_CHART_GAP_RATIO = 0.2   # max(3, font_size * 0.2)

The strip now reserves both, and the labels anchor chart_gap_px inside the edge:

label_pixel_width = label_pad_px + chart_gap_px + max_name_len * char_px + halo_px / 2
...
label_anchor_branch = chart_edge_branch - chart_gap_px * per_pixel

The leader lines stop at the same place, so they meet the text rather than passing under it
to the panel edge. per_pixel is the right converter: the panel spans
tree_size + strip_pixel_width pixels over a proportionally extended branch domain, so
branch-units-per-pixel stays branch_span / tree_size throughout.

Because the anchor is a data value on the branch scale and each layout already orders its
own branch_domain, this works unchanged for all four tree_location values.

The 3px floor was chosen by rendering the affected charts at 2, 3 and 4px and comparing;
2px still read as crowded against the frame, 4px as loose.

Behavior change: the tree panel grows by max(3, strain_label_font_size * 0.2) px.
Since strip_pixel_width = label_pixel_width - shift_tree_loc, anyone who hand-tuned
shift_tree_loc may want the same adjustment. Noted in the CHANGELOG.

The new parameter

_concat_for_location hardcoded spacing=0 at all four branches, leaving callers no way
to open a gap between the panels — Vega-Lite's own concat default is 20, so 0 was a
deliberate "tree attached to chart" choice, just not an overridable one.

spacing now exposes it, defaulting to 0 so existing charts are unchanged. The inset
above is what fixes the flush-label problem; spacing is a general layout knob, not a
workaround for it. Negative values are accepted and overlap the panels.

It is easy to confuse with shift_tree_loc, which is described as moving the tree
"toward/away from the chart" but acts inside the tree panel by resizing the label strip.
The two descriptions now cross-reference each other.

Per the single-source-of-truth design, cli.py and the docs needed no edits — --spacing
and the rendered API page generate from the PlotConfig field. Verified both.

Tests

231 pass, up from 218.

  • test_tree_location.pyspacing defaults to 0, propagates, and holds across all four
    tree_location values; panel dimensions are unaffected.
  • test_label_connection.py — labels sit strictly inside chart_edge_branch in both
    orientations, and leader x2 equals the label anchor.
  • test_appearance_tuning.py — a --spacing CLI round-trip.
  • test_cli.py--spacing listed in --help.
  • test_plot_config.py needed no change; it already enforces the
    signature/default/docstring contract.

Verification

scripts/check.sh (pytest, ruff, black) and scripts/build_docs.sh (mkdocs build --strict) both pass.

Generated the docs assets on main and on this branch and compared byte-for-byte: of the
eight images, only h3n2_combined_label_connect.svg differs — the one example that uses
connect_leader_to_label=True. The other seven are identical, as expected.

Re-rendered the chart that surfaced this, with spacing at its default: the labels now sit
clearly inside the frame line, confirming the inset alone resolves it without needing
spacing at all.

Per CLAUDE.md, this is a rendering-affecting change and there are deliberately no
image-snapshot tests — a human should still eyeball site/examples.html.

🤖 Generated with Claude Code

With `connect_leader_to_label` on, the strain labels were anchored exactly at
`chart_edge_branch` — the tree panel's chart-facing edge — and right-aligned, so
the text ran up against the chart's frame and the frame line stopped reading as
a line. `label_pad_px` was already counted into the label strip's width, but
because the anchor sat on the edge that pad landed on the tree side of the text
rather than between the text and the chart.

Reserve a pad at each end of the strip and anchor the labels one pad inside the
chart-facing edge, with the leader lines stopping at the same place so they meet
the text instead of passing under it to the panel edge. The tree panel grows by
one pad, so a hand-tuned `shift_tree_loc` may want the same adjustment.

Separately, `_concat_for_location` hardcoded `spacing=0` at all four
`tree_location` branches, leaving callers no way to open a gap between the
panels. Expose it as `spacing`, defaulting to 0 so existing charts are
unchanged. It is easy to confuse with `shift_tree_loc`, which moves the tree
within its own panel by resizing the label strip, so the two descriptions now
cross-reference each other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jbloom
jbloom force-pushed the label-inset-and-spacing branch from b32302e to 3a0d348 Compare August 20, 2026 20:28
jbloom and others added 2 commits August 20, 2026 14:10
The inset reused `label_pad_px`, which clears the deepest tip, for the gap that
clears the chart's frame. Those are different jobs and want different sizes: at
the font sizes in practice the pad is 4px, which reads as more separation than
the frame needs.

Add `_LABEL_CHART_GAP_PX_MIN` / `_LABEL_CHART_GAP_RATIO`, giving
`max(2, font_size * 0.2)` -- half the tree-facing pad at every font size, so 2px
at fonts 9 and 10. The tree panel now grows by that instead of by a full pad,
halving the layout shift the inset introduces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2px left the labels reading as slightly too close to the chart's frame. Raise the
floor to 3px; the ratio is unchanged, so it only differs below font size 15.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jbloom
jbloom merged commit e8040ee into main Aug 20, 2026
3 checks passed
@jbloom
jbloom deleted the label-inset-and-spacing branch August 20, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant