You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Figures with many rows render overlapping text. The clearest offender is CPPPlot.feature_map: its left-hand scale subcategory row labels collide as
soon as the figure has more than a handful of rows. At the full AAontology
breadth (74 subcategories) the label column becomes an unreadable black blur;
the overlap is already measurable at ~20 rows. There is no test that catches
this, so regressions ship silently and existing committed figures likely already
carry mild overlap.
Investigation notes (verified by rendering real figures, 2026-06-14):
The visible row labels are hand-placed ax.text artists, not real y-tick
labels. Path: feature_map → plot_feature_map → PlotElements.add_subcat_bars → ut.plot_add_bars → _add_text_labels (aaanalysis/_utils/utils_plot_elements.py).
They render at the current rcParams font size (~10pt); a vestigial 1pt set_yticklabels copy also exists (ignore it).
feature_map is a 5-axis composite (heatmap + top importance bars +
colorbar + cumulative-importance + legend). A naive "any text overlaps any
text" detector is unusable — it flags legitimate layered text. The test
must scope to the row-label artists.
Reliable bbox measurement requires a forced render first
(fig.savefig(BytesIO()) or fig.draw_without_rendering()); a bare get_window_extent returns degenerate ~1px heights and reports false "0 overlaps".
Separate bug found: the top importance-bar numeric ticks ('40' over '0')
overlap independently of row count — fix in the same PR or split out.
Goal
A test gate that renders dense figures and fails on overlapping row labels (scoped, low-false-positive).
feature_map (and the other dense plots — heatmap, ranking, profile) self-adjust so labels never overlap and never drop below a legibility
floor: shrink the label font to a floor (~5–6pt), then grow the figure
height when even the floor would collide. Per maintainer steer, route the
font adjustment through plot_settings (it scales fonts properly and has
other benefits) rather than a bespoke per-call knob.
Requirements
Add a reusable test helper (e.g. tests/unit/plotting_tests/_text_overlap.py)
that, given a fig and a set of target label strings, force-renders and
returns label-vs-label bbox overlaps above a fraction threshold. Reference
implementation below.
Add a fixture that builds a valid high-row df_feat from CPPPlot()._df_cat (one scale per subcategory → up to 74 rows). Reference below.
Add tests asserting feature_map has 0 row-label overlaps at 20 / 36 /
55 / 74 subcategories.
Implement shrink-to-floor-then-grow, routed through plot_settings/the
agreed sizing trigger. Decide the trigger (figsize default None=auto vs
auto-fit within (7,5) vs a flag) — CONFIRM-FIRST if it changes the
public feature_map signature/default.
Resolve the separate top importance-bar numeric tick overlap.
Re-run + re-commit affected example notebooks + the cheat-sheet figure
(their committed outputs will change — that is intended).
Extend the gate to heatmap, ranking, profile.
KPIs / Acceptance criteria (measurable)
feature_map row-label-vs-row-label overlaps (≥30% of the smaller bbox area)
= 0 at all of {20, 36, 55, 74} subcategories. (Today: 19 / 69 / 159 / 286.)
Rendered row-label font is never < 5pt.
Top importance-bar numeric ticks: 0 mutual overlaps.
Test runs headless (Agg) in < a few seconds; green on Linux + Windows.
No example/cheat-sheet figure regresses to more overlap than before.
Dense offenders first (feature_map tracer → heatmap/ranking/profile);
sparse plots (eval, single feature, logos) later if needed.
Not a blanket "no two text artists overlap anywhere" gate — scoped to label
artists to avoid false positives on legitimately layered composite axes.
Standards checklist
Library plotting code never calls plt.show() / plt.tight_layout();
colors via ut.COLOR_*; plot_settings only from user-facing entry points.
tests headless (Agg, already set in tests/conftest.py); no print();
bare ValueError/RuntimeError.
CONFIRM-FIRST if the feature_map public default/signature changes.
Sub-task B — Global auto_font mechanism: auto-optimize fonts for all plots (+ auto-size for feature_map)
(merged & broadened from #270; Sub-task A above is the reactive overlap guard + test gate for the dense composites. Both touch _cpp_plot.py, _backend/cpp/, _utils/utils_plot_elements.py — develop together / serialized.)
Problem (B)
Font sizes across the package are fixed absolutes set per method (fontsize_titles, fontsize_labels, fontsize_annotations, fontsize_imp_bar, xtick_size, seq_size/fontsize_tmd_jmd, …) plus the rcParams from aa.plot_settings(). They are
hand-tuned for one figure shape, so the same text is drawn whether a panel is small or
large — text looks too big on dense panels and too small on sparse ones. feature_map
is the worst case because its grid also changes shape with the data
(n_subcat rows × n_positions residue columns), which is why tutorial3d_data_representations.ipynb renders with mismatched fonts. There is no single
switch that makes plots size their type to the panel.
Goal (B)
Add a globalauto_font option (aa.options["auto_font"], in config.py) that, when
enabled, automatically optimizes font sizes for every plot in the package from the
rendered axes/figure size. For feature_map (and the dense composites) auto_fontalso
auto-derives the figure size from n_subcat × n_positions. Off by default (or default to
current behavior) → output byte-identical to today; explicit per-call figsize/fontsize_*
always override.
Requirements (B)
Global optionauto_font in aa.options / aaanalysis/config.py (CONFIRM-FIRST: config.py is on the options surface). Thread it through the shared plotting glue so it
applies package-wide, not per method.
General font auto-optimization (all *Plot methods): when auto_font is on and a fontsize_* arg is left at its sentinel (None/auto), derive that font from the rendered
figure/axes size via one shared helper (e.g. in _utils/utils_plot_elements.py), with a legibility floor (reuse the ~5–6 pt floor from Sub-task A) and an upper clamp at the
current fixed default.
feature_map/dense-plot size auto-scaling (the "+ size, if enabled" part): when auto_font is on, also map (n_subcat, n_positions) → recommended figsize via a fixed
per-cell target clamped to min/max bounds, so a cell stays ~constant on-screen as the grid
grows. Fold in Sub-task A's shrink-to-floor-then-grow.
Override precedence: explicit per-call figsize/fontsize_* > auto_font global >
fixed defaults. numpydoc: document auto_font and that None font/size args mean "auto".
With auto_fontoff (default), every plot is byte-identical to the current release
(regression-tested) — no silent change for existing users.
With auto_fonton, across n_subcat ∈ {5, 20, 55, 74} and n_positions ∈ {20, 40, 80}: feature_map on-screen cell size (figure inches / grid count) stays within ±15%.
With auto_font on, every auto-derived font stays in band: never < 5 pt, never larger
than the current fixed default at the canonical shape — verified for ≥3 different *Plot
methods (e.g. feature_map, heatmap, profile, plus one non-CPP plot).
Explicit figsize/fontsize_* passed by the user is honored regardless of auto_font.
tutorial3d + affected examples pass the nbmake gate with regenerated outputs.
Scope / non-goals (B)
General mechanism = font auto-optimization for all plots; size auto-scaling is the feature_map/dense-composite extension only (other plots keep their figsize).
No new dependencies; core only. auto_font is a global toggle, not a per-method knob explosion.
Problem
Figures with many rows render overlapping text. The clearest offender is
CPPPlot.feature_map: its left-hand scale subcategory row labels collide assoon as the figure has more than a handful of rows. At the full AAontology
breadth (74 subcategories) the label column becomes an unreadable black blur;
the overlap is already measurable at ~20 rows. There is no test that catches
this, so regressions ship silently and existing committed figures likely already
carry mild overlap.
Investigation notes (verified by rendering real figures, 2026-06-14):
ax.textartists, not real y-ticklabels. Path:
feature_map → plot_feature_map → PlotElements.add_subcat_bars → ut.plot_add_bars → _add_text_labels(aaanalysis/_utils/utils_plot_elements.py).They render at the current rcParams font size (~10pt); a vestigial 1pt
set_yticklabelscopy also exists (ignore it).feature_mapis a 5-axis composite (heatmap + top importance bars +colorbar + cumulative-importance + legend). A naive "any text overlaps any
text" detector is unusable — it flags legitimate layered text. The test
must scope to the row-label artists.
(
fig.savefig(BytesIO())orfig.draw_without_rendering()); a bareget_window_extentreturns degenerate ~1px heights and reports false "0 overlaps".'40'over'0')overlap independently of row count — fix in the same PR or split out.
Goal
row labels (scoped, low-false-positive).
feature_map(and the other dense plots —heatmap,ranking,profile)self-adjust so labels never overlap and never drop below a legibility
floor: shrink the label font to a floor (~5–6pt), then grow the figure
height when even the floor would collide. Per maintainer steer, route the
font adjustment through
plot_settings(it scales fonts properly and hasother benefits) rather than a bespoke per-call knob.
Requirements
tests/unit/plotting_tests/_text_overlap.py)that, given a
figand a set of target label strings, force-renders andreturns label-vs-label bbox overlaps above a fraction threshold. Reference
implementation below.
df_featfromCPPPlot()._df_cat(one scale per subcategory → up to 74 rows). Reference below.feature_maphas 0 row-label overlaps at 20 / 36 /55 / 74 subcategories.
plot_settings/theagreed sizing trigger. Decide the trigger (figsize default
None=auto vsauto-fit within
(7,5)vs a flag) — CONFIRM-FIRST if it changes thepublic
feature_mapsignature/default.(their committed outputs will change — that is intended).
heatmap,ranking,profile.KPIs / Acceptance criteria (measurable)
feature_maprow-label-vs-row-label overlaps (≥30% of the smaller bbox area)= 0 at all of {20, 36, 55, 74} subcategories. (Today: 19 / 69 / 159 / 286.)
Reference: validated detector
Reference: high-row fixture
Scope / non-goals
feature_maptracer →heatmap/ranking/profile);sparse plots (eval, single feature, logos) later if needed.
artists to avoid false positives on legitimately layered composite axes.
Standards checklist
plt.show()/plt.tight_layout();colors via
ut.COLOR_*;plot_settingsonly from user-facing entry points.tests/conftest.py); noprint();bare
ValueError/RuntimeError.feature_mappublic default/signature changes.Sub-task B — Global
auto_fontmechanism: auto-optimize fonts for all plots (+ auto-size forfeature_map)(merged & broadened from #270; Sub-task A above is the reactive overlap guard + test gate for the dense composites. Both touch
_cpp_plot.py,_backend/cpp/,_utils/utils_plot_elements.py— develop together / serialized.)Problem (B)
Font sizes across the package are fixed absolutes set per method (
fontsize_titles,fontsize_labels,fontsize_annotations,fontsize_imp_bar,xtick_size,seq_size/fontsize_tmd_jmd, …) plus the rcParams fromaa.plot_settings(). They arehand-tuned for one figure shape, so the same text is drawn whether a panel is small or
large — text looks too big on dense panels and too small on sparse ones.
feature_mapis the worst case because its grid also changes shape with the data
(n_subcat rows × n_positions residue columns), which is why
tutorial3d_data_representations.ipynbrenders with mismatched fonts. There is no singleswitch that makes plots size their type to the panel.
Goal (B)
Add a global
auto_fontoption (aa.options["auto_font"], inconfig.py) that, whenenabled, automatically optimizes font sizes for every plot in the package from the
rendered axes/figure size. For
feature_map(and the dense composites)auto_fontalsoauto-derives the figure size from
n_subcat × n_positions. Off by default (or default tocurrent behavior) → output byte-identical to today; explicit per-call
figsize/fontsize_*always override.
Requirements (B)
auto_fontinaa.options/aaanalysis/config.py(CONFIRM-FIRST:config.pyis on the options surface). Thread it through the shared plotting glue so itapplies package-wide, not per method.
*Plotmethods): whenauto_fontis on and afontsize_*arg is left at its sentinel (None/auto), derive that font from the renderedfigure/axes size via one shared helper (e.g. in
_utils/utils_plot_elements.py), with alegibility floor (reuse the ~5–6 pt floor from Sub-task A) and an upper clamp at the
current fixed default.
feature_map/dense-plot size auto-scaling (the "+ size, if enabled" part): whenauto_fontis on, also map(n_subcat, n_positions)→ recommendedfigsizevia a fixedper-cell target clamped to min/max bounds, so a cell stays ~constant on-screen as the grid
grows. Fold in Sub-task A's shrink-to-floor-then-grow.
figsize/fontsize_*>auto_fontglobal >fixed defaults. numpydoc: document
auto_fontand thatNonefont/size args mean "auto".tutorial3d_data_representations.ipynb+ affectedexamples/*notebooks.KPIs / Acceptance criteria (B, measurable)
auto_fontoff (default), every plot is byte-identical to the current release(regression-tested) — no silent change for existing users.
auto_fonton, across n_subcat ∈ {5, 20, 55, 74} and n_positions ∈ {20, 40, 80}:feature_mapon-screen cell size (figure inches / grid count) stays within ±15%.auto_fonton, every auto-derived font stays in band: never < 5 pt, never largerthan the current fixed default at the canonical shape — verified for ≥3 different
*Plotmethods (e.g.
feature_map,heatmap,profile, plus one non-CPP plot).figsize/fontsize_*passed by the user is honored regardless ofauto_font.tutorial3d+ affected examples pass thenbmakegate with regenerated outputs.Scope / non-goals (B)
feature_map/dense-composite extension only (other plots keep their figsize).auto_fontis a global toggle, not a per-method knob explosion.