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
{{ message }}
Repository navigation
aprender-viz: GGPlot::title/xlab/ylab are accepted and silently discarded — never drawn, and #[allow(dead_code)] hides it #3590
GGPlot's builder accepts a title and both axis labels and draws none of them. The values are taken, stored, and discarded.
Measured on the published aprender-viz 0.68.2:
GGPlot::title() (src/grammar/ggplot.rs:170) stores into GGPlot.title.
GGPlot::xlab() (:177) and ylab() (:184) store into GGPlot.xlab / GGPlot.ylab.
build() (:194) carries onlytitle into BuiltGGPlot (:209) — and the field is declared
#[allow(dead_code)]
title:Option<String>,
xlab and ylab are not carried into BuiltGGPlot at all.
BuiltGGPlot::to_framebuffer → render_panel draws background, scales, grid, layers, axes and border. grep -n 'self\.title\|self\.xlab\|self\.ylab' over ggplot.rs returns only the three setters and the one build() copy — no draw site.
So three builder methods are silent no-ops. Nothing warns, nothing errors, and the compiler is explicitly told not to notice.
Receipt
A ~15-line program against the published crate:
let built = GGPlot::new().data_xy(&xs,&ys).aes(Aes::new().x("x").y("y")).geom(Geom::point()).dimensions(1920,1080).title("grammar path probe").xlab("x").ylab("sin(x)*40+50").build()?;PngEncoder::write_to_file(&built.to_framebuffer()?,"grammar.png")?;
The PNG is a correct figure — panel, grid, axes, 200 scaled points — with no title and no axis labels anywhere on it. build() returned Ok. Nothing in the API suggested the three calls had no effect.
The positive control is already in the source
#[allow(dead_code)] on BuiltGGPlot.title is the confession, and it is also the test: remove the allow and the compiler names the bug. That makes a cheap, non-vacuous regression guard — if the field is ever read by a draw path, the allow is unnecessary; while it is necessary, the feature is absent.
For xlab/ylab the equivalent control is that they do not reach BuiltGGPlot at all, so the guard is a test asserting the rendered output contains the label text (or, once outlines land, the corresponding glyph paths).
Fix, either way — but not silently
Draw them, or refuse them. A builder method that discards its argument is worse than one that does not exist, because the caller has no way to find out. If titles are deliberately out of scope for the grammar path, title/xlab/ylab should be removed or return an error rather than accept and drop.
Consumer context
Found by apex (paiml/apex, APEX-001), a first-party consumer, while probing what the published crate can emit. apex binds every rendered string to a claim id and will not ship a figure whose text silently vanishes — so this is a correctness issue for it, not a cosmetic one. Sibling of the SVG-path gap filed alongside this.
Receipt: .title/.xlab/.ylab are now drawn or refused, never dropped. Branch fix/3590-ggplot-labels @ e5bf28342, off main. No PR: aprender is at its open-PR cap, so this is a fold candidate for stage/0.70.1.
Behaviour. A set label is resolved at build():
Without the text-path feature: there is no way to draw text, so build() returns an Err that names the builder method, e.g. "GGPlot::xlab cannot be drawn…".
With text-path:.font(bytes) pins the font, and there is deliberately no default font.
A missing font is an Err.
A font that draws no glyph for the label is an Err too, since an empty outline would be a silent drop by another route.
A resolved label is filled with the nonzero winding rule and one sample per pixel centre, in theme.text_color:
the title in the top margin;
xlab in the bottom margin;
ylab rotated a quarter turn in the left margin.
An empty label string needs no font.
Tests.
grammar lib tests pass: 135 on default features, 139 with --features text-path.
clippy --features text-path --all-targets -D warnings is clean, and cargo fmt is clean.
The host-bound test tests/ggplot_labels_3590.rs (--ignored, DejaVuSans) counts ink in each margin: labelled must exceed unlabelled by more than 20 px.
Mutants. Each one is planted, turns its guard RED, and the file is then restored and checked with cmp.
merged into #5312 (master ticket for epic #3994; triage rule 7b, 2026-10-14). It is a checklist row there; reopen this issue when work on it starts or its PR opens.
Moved: this sub-ticket is now a row in #5334 (aprender-viz: draw titles and axis labels, ship runnable Facet/Coord examples), split out of #5312 by theme.
Activity
noahgift commentedon Sep 23, 2026
milestone → 0.73.0 per docs/roadmaps/milestone-triage-2026-09-23.md (#4024), operator approval 2026-09-23 (relayed by aprender-cf). Reason: debt ratchet pillar D (#3997: backlog, docs, packaging), slice 4
noahgift commentedon Sep 26, 2026
Receipt:
.title/.xlab/.ylabare now drawn or refused, never dropped. Branchfix/3590-ggplot-labels@e5bf28342, off main. No PR: aprender is at its open-PR cap, so this is a fold candidate for stage/0.70.1.Behaviour. A set label is resolved at
build():text-pathfeature: there is no way to draw text, sobuild()returns anErrthat names the builder method, e.g. "GGPlot::xlab cannot be drawn…".text-path:.font(bytes)pins the font, and there is deliberately no default font.Err.Errtoo, since an empty outline would be a silent drop by another route.theme.text_color:xlabin the bottom margin;ylabrotated a quarter turn in the left margin.Tests.
grammarlib tests pass: 135 on default features, 139 with--features text-path.clippy --features text-path --all-targets -D warningsis clean, andcargo fmtis clean.tests/ggplot_labels_3590.rs(--ignored, DejaVuSans) counts ink in each margin: labelled must exceed unlabelled by more than 20 px.Mutants. Each one is planted, turns its guard RED, and the file is then restored and checked with
cmp.test_ggplot_label_without_a_font_is_refusedtest_ggplot_font_that_draws_no_glyph_is_refusedeach_label_is_drawn_in_its_own_margin(host)fill_path_nonzero_keeps_the_hole_of_a_reversed_inner_contournoahgift commentedon Oct 14, 2026
merged into #5312 (master ticket for epic #3994; triage rule 7b, 2026-10-14). It is a checklist row there; reopen this issue when work on it starts or its PR opens.
noahgift commentedon Oct 14, 2026
Moved: this sub-ticket is now a row in #5334 (aprender-viz: draw titles and axis labels, ship runnable Facet/Coord examples), split out of #5312 by theme.
feat(viz): GGPlot::to_svg draws title, xlab and ylab (Refs paiml#5334,…