feat(plot): per-trace legend with measured layout and swatches
This commit is contained in:
1 parent
9dc3b1e5e6
commit
2d51b978d7
9 files changed
+850
-2
No files matched your search
@@ -8,6 +8,7 @@
|
||||
#include <plot/plot/axis.hpp>
|
||||
#include <plot/plot/cursor.hpp>
|
||||
#include <plot/plot/grid.hpp>
|
||||
#include <plot/plot/legend.hpp>
|
||||
#include <plot/plot/measure.hpp>
|
||||
#include <plot/plot/trace.hpp>
|
||||
#include <plot/render/style.hpp>
|
||||
@@ -50,6 +51,12 @@ struct PlotLayout {
|
||||
/// no text backend there is no title to lay out at all and both stay 0.0f.
|
||||
f32 y_title_width = 0.0f;
|
||||
f32 x_title_height = 0.0f;
|
||||
/// The measured legend: inside `axes`, never reserving any part of it, and empty
|
||||
/// when there is nothing to draw. It is measured with the same text backend the
|
||||
/// strips above are, and against the same `axes` rect the caller reads back, so
|
||||
/// the panel a test asserts and the panel the renderer paints are one set of
|
||||
/// numbers rather than two derivations of one rule (see LegendLayout).
|
||||
LegendLayout legend;
|
||||
};
|
||||
|
||||
/// The public façade: axes, traces, cursors, annotations and their layout.
|
||||
@@ -221,6 +228,17 @@ class Plot {
|
||||
/// Grid segments for the primary axis pair (x axis 0, y axis 0), in data space.
|
||||
const Grid& grid() const noexcept { return grid_; }
|
||||
|
||||
// --- legend -----------------------------------------------------------
|
||||
|
||||
/// The legend's configuration. A legend is on unless a caller turns it off and
|
||||
/// costs nothing until a trace is named: with no named visible trace -- or no text
|
||||
/// backend to measure a name with -- `layout()` produces an empty layout and
|
||||
/// nothing is drawn.
|
||||
const Legend& legend() const noexcept { return legend_; }
|
||||
/// Replaces the configuration. The next `layout()` measures the panel with it, so
|
||||
/// a caller can move the legend -- or switch it off -- between frames.
|
||||
void set_legend(const Legend& legend) noexcept { legend_ = legend; }
|
||||
|
||||
private:
|
||||
/// One measure plus its cache. The dirty flag is what keeps measurement() from
|
||||
/// re-evaluating every frame; `last_series`, `last_size` and `last_x0` catch a
|
||||
@@ -263,10 +281,17 @@ class Plot {
|
||||
/// sides: the column the rotated y title is drawn in, one column per titled
|
||||
/// axis. Zero with no backend or no titled y axis.
|
||||
f32 y_title_strip_width() const;
|
||||
/// Measures the legend into `layout_.legend`: which traces have an entry, how wide
|
||||
/// the panel has to be, where it is anchored, and one row per entry it can fit.
|
||||
/// Zeroes the layout and returns when there is nothing to measure with or nothing
|
||||
/// to list -- an empty legend is what keeps the feature free for a caller who
|
||||
/// never names a trace.
|
||||
void build_legend();
|
||||
|
||||
Rect viewport_{};
|
||||
render::Style style_{};
|
||||
const render::TextBackend* text_ = nullptr;
|
||||
Legend legend_{};
|
||||
|
||||
std::vector<Axis> x_axes_;
|
||||
std::vector<Axis> y_axes_;
|
||||
@@ -274,6 +299,12 @@ class Plot {
|
||||
std::vector<Cursor> cursors_;
|
||||
std::vector<MeasureSlot> measures_;
|
||||
std::vector<Annotation> annotations_;
|
||||
/// Scratch for build_legend(): the name width of every trace that has an entry, in
|
||||
/// trace order and prefix-maxed in place, so the fit loop reads the width a given
|
||||
/// number of kept entries needs in O(1) instead of re-measuring the names it is
|
||||
/// about to drop. A member rather than a local, so a steady-state frame's legend
|
||||
/// build allocates nothing.
|
||||
std::vector<f32> legend_name_px_;
|
||||
|
||||
PlotLayout layout_{};
|
||||
Grid grid_;
|
||||
|
||||
Reference in new issue
Block a user