feat(plot): an x axis caption drawn as chrome, not as an annotation
A host that re-anchors its x labels on a grid has to name the anchor somewhere, and an `Annotation` cannot serve: its anchor is a data coordinate, so the caption crawls across the plot under a pan and drifts under a zoom. `Plot::set_x_note()` takes the host's own string -- the host is the only one that knows how much precision the sum of a label and its caption needs -- and `Renderer::draw_x_note()` places it in the axes' bottom-left corner in pixels, on the legend's own backplate. It is measured in the renderer rather than in `Plot::layout()` because it reserves no space and nothing can be picked on it, and a caption explaining the x labels must not be able to move the layout those labels were measured into. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
03c41adbec
commit
59c2e45af7
5 files changed
+150
No files matched your search
@@ -533,6 +533,7 @@ void Renderer::draw_plot(Plot& plot, const PlotLayout& layout) {
|
||||
draw_measures(plot, layout);
|
||||
draw_annotations(plot);
|
||||
draw_legend(plot, layout);
|
||||
draw_x_note(plot, layout);
|
||||
// The under text batch goes out here, so the readout's panel below it can
|
||||
// occlude what it covers: legend names, cursor labels, measure labels and
|
||||
// annotation captions are all chrome the panel is allowed to sit on.
|
||||
@@ -1403,6 +1404,28 @@ void Renderer::draw_legend(const Plot& plot, const PlotLayout& layout) {
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::draw_x_note(const Plot& plot, const PlotLayout& layout) {
|
||||
const std::string& text = plot.x_note();
|
||||
if (text.empty()) return;
|
||||
const Rect& axes = layout.axes;
|
||||
const f32 line_h = font_backend_.height();
|
||||
const f32 width = font_backend_.measure(text.c_str());
|
||||
if (!(width > 0.0f) || !(line_h > 0.0f)) return;
|
||||
// The pad is the legend's, so the two panels read as the same kind of chrome.
|
||||
const f32 pad = style_.device(4.0f);
|
||||
const Rect panel{axes.x + pad, axes.bottom() - pad - line_h - 2.0f * pad, width + 2.0f * pad,
|
||||
line_h + 2.0f * pad};
|
||||
// A sliver of axes gets no caption rather than one hanging over the frame: the panel is chrome
|
||||
// *inside* the axes, and a backplate crossing the axis line would read as a second plot.
|
||||
if (panel.w > axes.w || panel.h > axes.h) return;
|
||||
draw_readout_panel(panel, style_.axis_line);
|
||||
// Bottom-left, beside the labels it qualifies -- the reader adds the caption to a tick, so it
|
||||
// belongs at the end of the tick row and not in a corner of its own. `glyphs_under_` because
|
||||
// the panel is flushed with the frame's line geometry and the under-text batch goes out after
|
||||
// it: text staged into the over batch would be drawn before its own backplate.
|
||||
stage_text(glyphs_under_, {panel.x + pad, panel.y + pad}, text.c_str(), style_.axis_text);
|
||||
}
|
||||
|
||||
void Renderer::stage_marker(plot::Marker marker, Vec2 centre_px, f32 size_px) {
|
||||
// Marker shapes as line segments, in viewport pixels: the trace's flush maps
|
||||
// them with `pixel_mapping()`, so the size is device pixels whatever the data
|
||||
|
||||
Reference in new issue
Block a user