feat(render): independent major and minor grid classes tied to the tick set

This commit is contained in:
Martino Ferrari committed 2026-09-28 12:42:22 +02:00
1 parent 69495a3799
commit eaae07d8e0
7 files changed
+635 -16

No files matched your search

+6 -2
View File
@@ -26,16 +26,20 @@ void Grid::build(const std::vector<Tick>& x_ticks, Range x_range,
// Segments stay in data space: the renderer applies the transform, so a pan or
// zoom needs no grid rebuild other than for a changed tick set.
//
// Both classes are built: the tick set is the only source of positions, and
// `Style::grid_*_visible` decides what the renderer stages from here, so a class
// is never filtered out of the tick set (that would be a second, geometry-side
// switch for the same behaviour, and the grid would then disagree with the tick
// list a caller can read).
if (vertical) {
for (const Tick& tick : x_ticks) {
if (!tick.major && !show_minor) continue;
if (!std::isfinite(tick.value) || !inside(tick.value, x_range)) continue;
lines_.push_back(GridLine{tick.value, y_range.min, tick.value, y_range.max, tick.major});
}
}
if (horizontal) {
for (const Tick& tick : y_ticks) {
if (!tick.major && !show_minor) continue;
if (!std::isfinite(tick.value) || !inside(tick.value, y_range)) continue;
lines_.push_back(GridLine{x_range.min, tick.value, x_range.max, tick.value, tick.major});
}
+20 -11
View File
@@ -543,21 +543,30 @@ void Renderer::draw_grid(const Plot& plot) {
// transforms even when a trace uses another pair.
const bool log_x = plot.x_axis(0).log;
const bool log_y = plot.y_axis(0).log;
// A grid line is a size a user perceives, so it is one logical pixel wide at
// every DPI rather than a hairline on a 1x panel and a slab on a 2x one. (Spec §11
// gives the two classes a width each; until then both are this one logical pixel.)
const f32 grid_width = style_.device(1.0f);
// Two colour groups, one pass over the segments each: `LineBatch::clear()`
// keeps its capacity, so the second group re-uses the first one's storage.
for (u32 pass = 0; pass < 2u; ++pass) {
const bool majors = pass == 0u;
// Two classes, one pass over the segment list each: the tick set decides *where*
// the lines are and this decides only how one is drawn -- its class' colour and
// its class' logical width, converted to device pixels here, the one conversion
// point (spec §11: a grid line is a size a user perceives, the same hairline on a
// 1x panel and a 2x one). A class the style switched off stages nothing at all:
// the pass is skipped before the first segment, so a disabled class costs nothing
// to draw rather than being drawn transparent. Majors first, minors over them, as
// the single group order always did. `LineBatch::clear()` keeps its capacity, so
// the second pass re-uses the first one's storage.
const auto draw_class = [&](bool major, bool visible, Color color, f32 width) {
if (!visible) {
return;
}
for (const GridLine& line : grid.lines()) {
if (line.major != majors) continue; // grid.build() already dropped non-finite ones
if (line.major != major) continue; // grid.build() already dropped non-finite ones
stage_segment(line.x0, line.y0, line.x1, line.y1, log_x, log_y);
}
flush_lines(map, majors ? style_.grid_major : style_.grid_minor, grid_width);
}
flush_lines(map, color, width);
};
draw_class(true, style_.grid_major_visible, style_.grid_major,
style_.device(style_.grid_major_width));
draw_class(false, style_.grid_minor_visible, style_.grid_minor,
style_.device(style_.grid_minor_width));
}
// ------------------------------------------------------------- axis chrome