Skip to content

feat: rotate and wrap long axis tick labels - #141

Merged
schochastics merged 1 commit into
mainfrom
feat/wi4-axis-label-typography
Aug 1, 2026
Merged

feat: rotate and wrap long axis tick labels#141
schochastics merged 1 commit into
mainfrom
feat/wi4-axis-label-typography

Conversation

@schochastics

Copy link
Copy Markdown
Collaborator

Closes the last WI-4 residual: wrapping/auto-fit for long axis tick labels (title/subtitle/caption already wrap). Two mechanisms, both driven from the theme.

Rotation

axis.text.x / axis.text.y now honour an angle — previously a silent no-op. .draw_axis reads the element's rotation, passes rot= to the label grob, and anchors it: an explicit hjust/vjust wins, otherwise the end of the run is pinned at the tick so the slant clears the panel. The gutter/row is sized from the rotated extent (the existing .track_w/.track_h rot path).

vplot(gdp) |> mark_bar(x = country, y = score) |>
  theme(axis.text.x = element_text(angle = 45))

Wrapping

Left horizontal, a long discrete label that would overrun its tick spacing wraps onto multiple lines and the label row grows to fit. At layout time — when the page width is known and the panel is a plain null track — each tick's mm budget (panel width ÷ break count) is computed via vellum::vl_convert() and handed to the drawer as an absolute wrap width; the row is measured through the same grob (.axis_text_grob) so the reserved height matches the drawn text.

Byte-identical guarantee: wrapping only engages when the widest label actually overruns its budget, so every plot whose labels already fit is unchanged (all existing snapshots pass untouched). Rotated, aspect-locked (coord_fixed/coord_sf/polar), faceted-free-x, marginal, and aligned-composition paths fall back to single line.

Also: .el_rot() is now blank-safe (element_blank() carries no angle property).

Verification (WI-0)

Rendered and looked at 45°, 90°, and wrapped bar charts (labels rotate/wrap, clear the panel, row height reserved, no overlap); confirmed a rotated axis renders through vellumwidget::as_widget(); inst/examples/32-axis-labels.R runs end to end.

Tests: test-axis-text.R (13). Full suite [ FAIL 0 | WARN 1 pre-existing | SKIP 0 | PASS 2873 ].

🤖 Generated with Claude Code

Axis tick labels were always drawn single-line and unrotated: an angle set
on axis.text.x/y was silently ignored, and a long discrete label just
overran its neighbour.

- Honour el@angle on axis.text.x/y. `.draw_axis` reads the element's
  rotation, passes rot= to the label grob, and anchors it: an explicit
  hjust/vjust wins, otherwise the end of the run is pinned at the tick so
  the slant clears the panel. The gutter/row is measured from the rotated
  extent (the .track_w/.track_h rot path).
- Wrap long x tick labels to their per-tick width. At layout time, when the
  page width is known and the panel is a plain null track, compute each
  tick's mm budget (panel width / break count) and pass it to the drawer as
  an absolute wrap width; the label row is measured through the same grob so
  the reserved height matches. Wrapping only engages when the widest label
  actually overruns its budget, so every plot whose labels already fit stays
  byte-identical. Rotated, aspect-locked, faceted-free-x, and composition
  paths fall back to single line.
- .el_rot is now blank-safe (element_blank() carries no angle).

Docs: NEWS, an effects-and-themes section, inst/examples/32-axis-labels.R,
and tests in test-axis-text.R.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@schochastics
schochastics merged commit 6a9a4f3 into main Aug 1, 2026
2 checks passed
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