changed the title
[WIP] Issue #303 margin adjustment for multi-line labels and titles
Issue #303 margin adjustment for multi-line labels and titles
Instead of starting with a pre-inflated mar and subtracting when titles are absent, dynamic themes now start at c(0.1, 0.1, 0.1, 0.1) and build up per-side margins additively for the elements that are actually present (tick row, axis labels, main, sub). Key changes: - New helper dynmar_side() in R/utils.R is the single source of truth for 'how much margin does this side need'. Both the pre-title path in tinyplot.default() and draw_facet_window() call into it. - theme_dynamic$mar reduced to the minimal pad baseline. All dynamic derivatives (clean, clean2, bw, classic, minimal, ipsum, dark, ridge, ridge2) inherit via modifyList. - draw_facet_window() simplified: no more 'if missing, subtract 1' per side; the new formula takes max(tick_extent, label_extent) because tick labels and axis titles share vertical space. - Pre-sizing margins and calling plot.window() before draw_title() ensures title/mtext alignment measures against the plot region (not the default [0,1,0,1] figure region). - draw_title() refactored: removed post-hoc ylab/main compensators that were workarounds for the old undersized-margin regime. Added explicit line = mgp[1] + (N-1)*cex for multi-line xlab so line 1 lands where a single-line xlab would sit (rather than being pushed up into the tick-label zone by base R's default multi-line layout). - line_sub default tightened from 1.7 to 0.7 (side.sub=3) to reduce the gap between sub and the plot box top. - xaxt / yaxt = 'n' is now honored: no tick row reserved when axes are off under a dynamic theme. Non-dynmar themes (default, basic, tufte, void) are unchanged. Snapshots will need regeneration on Linux (devcontainer) since the pixel output has shifted for every dynamic theme.
Previously, main was drawn at base R's default line when sub was absent, and at a tinyplot-specified line when sub was present. The two differed slightly, causing main to shift when sub was toggled on/off. Now we explicitly set line_main = mgp[3] + 0.6 for dynmar themes regardless of sub presence. The sub branch still bumps main up by 1.2 lines when needed. Non-dynmar themes retain base R's default.
!is.null(NA) is TRUE, so the 'push main up to make room for sub' branch was firing for sub = NA even though no sub space was reserved. Use text_line_count(sub) > 0 so NA/empty sub is treated the same as NULL.
title() stacks multi-line main above the passed line (line N sits at line, earlier lines extend upward). Dropping the old -(N-1)/2 adjustment, which was centering the block and causing line 1 to drift with N. The top margin reservation already accounts for (N-1)*cex_main extra lines.
The sub-row offset applied to line_main was a hardcoded +1.2 regardless of sub line count, so main overlapped multi-line sub. Now scales as 1.2 + (N-1)*cex.sub, mirroring the margin reservation in dynmar_side().
The main-push-up and margin-reservation formulas for sub both used a hardcoded +1.2 for the first sub row, which happens to equal the default cex.sub but doesn't scale if the user customises it. Express the first-row bump as cex_sub + 0.2 (0.2-line breathing room) so it tracks user-supplied cex.sub. Also fix the get_tpar default for cex.sub in dynmar_side() to match the 1.2 used in draw_title(). No visual change for default cex.sub = 1.
Previously dynmar_side() smuggled in a pad = 0.3 constant as baseline breathing room, which conflicted with the theme's own mar values. Now the theme's mar acts as the baseline (via pmax), and dynmar_side() returns exactly what's needed for the elements actually present. Also: - Main contribution now scales the top-line ascender with cex_main (0.6 * cex_main) so custom cex.main values (e.g. 2) don't clip. - Sub contribution adds its own ascender when drawn alone (no main), derived from 0.6 * cex_sub. When main is present, main's ascender already covers the top so sub just contributes its row heights. - Comments updated; call sites in tinyplot.R and facet.R use pmax(theme_mar, computed) as documented.
Without this, main/sub titles drawn above the top facet strip would overlap the strip contents. fmar[3] already encodes facet_newlines and the facet_grid special-case; we add back the 0.5 line that's stripped when frame.plot is FALSE (that reduction is for inter-panel gaps, not the top strip itself).
The right side never gets tick/label content (except spineplot), so theme mar is the sole source of padding there. Bumping from 0.1 to 0.6 restores the breathing room the pre-refactor layout had. Other sides stay at 0.1 (they get ample space from tick rows and labels when present).
…sides - Move full dynmar margin computation (theme_mar + dynmar_side + whtsbp) into tinyplot.default before legend drawing, pass dynmar_computed through to draw_facet_window instead of recomputing in two places. - On outer-legend sides, zero only the theme baseline padding (.theme_mar) so the plot meets the legend's oma flush, while preserving axis-driven bumps (.dyn) for left!/bottom! legends. - Detect legend position via tryCatch(eval(legend)) since sanitize_legend runs later inside draw_legend; default NULL legend treated as 'right!'. - Hoist .whtsbp default so post-block references are always defined. - draw_title gains ylab_line_offset for whtsbp-aware ylab placement. - theme_dynamic baseline mar changed to c(0.1, 0.1, 0.6, 0.6).
grantmcdermott
changed the title
Issue #303 margin adjustment for multi-line labels and titles
Rework dynamic theme logic
Closed
Closed
This was referenced
May 15, 2026Closed
Closed
Closed