feat(plot): nice-number ticks, axes, grid, traces, cursors, measurement queries
This commit is contained in:
1 parent
b4e88fe0c1
commit
93fc06f050
18 files changed
+1582
No files matched your search
@@ -0,0 +1,109 @@
|
||||
#include "harness.hpp"
|
||||
#include <plot/plot/ticks.hpp>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
PLOT_TEST(nice_ticks_are_powers_of_ten_times_125) {
|
||||
std::vector<plot::Tick> t;
|
||||
plot::generate_ticks({0.0, 1.0}, /*px=*/500.0f, /*min_px=*/60.0f, t);
|
||||
PLOT_CHECK(!t.empty());
|
||||
for (auto& tick : t) PLOT_CHECK(std::fabs(tick.value * 5.0 - std::round(tick.value * 5.0)) < 1e-9);
|
||||
// spacing of major ticks >= min_px
|
||||
PLOT_CHECK(static_cast<double>(t.size()) <= 500.0 / 60.0 + 2.0);
|
||||
}
|
||||
PLOT_TEST(ticks_are_monotonic_and_cover_range) {
|
||||
std::vector<plot::Tick> t;
|
||||
plot::generate_ticks({-3.7, 91.2}, 800.0f, 60.0f, t);
|
||||
for (size_t i = 1; i < t.size(); ++i) PLOT_CHECK(t[i].value > t[i-1].value);
|
||||
PLOT_CHECK(t.front().value <= 0.0);
|
||||
PLOT_CHECK(t.back().value >= 90.0);
|
||||
}
|
||||
PLOT_TEST(si_formatting) {
|
||||
char b[32];
|
||||
plot::format_si(0.0000015, b, sizeof b); PLOT_CHECK(std::string(b) == "1.5\xC2\xB5s" || std::string(b).find("1.5") == 0);
|
||||
plot::format_si(2.5e9, b, sizeof b); PLOT_CHECK(std::string(b).find("2.5") == 0);
|
||||
plot::format_si(1234.0, b, sizeof b); PLOT_CHECK(std::string(b).find("1.23") == 0);
|
||||
plot::format_si(0.0, b, sizeof b); PLOT_CHECK(std::string(b) == "0");
|
||||
}
|
||||
PLOT_TEST(degenerate_ranges_do_not_hang) {
|
||||
std::vector<plot::Tick> t;
|
||||
plot::generate_ticks({5.0, 5.0}, 500.0f, 60.0f, t); // must not loop forever
|
||||
PLOT_CHECK(t.size() <= 64);
|
||||
plot::generate_ticks({0.0, 0.0}, 0.0f, 60.0f, t);
|
||||
plot::generate_ticks({1e300, 1e300}, 500.0f, 60.0f, t);
|
||||
}
|
||||
|
||||
PLOT_TEST(hostile_tick_inputs_clear_the_output) {
|
||||
std::vector<plot::Tick> t{{1.0, true, "stale"}};
|
||||
plot::generate_ticks({10.0, -10.0}, 500.0f, 60.0f, t); // inverted
|
||||
PLOT_CHECK(t.empty());
|
||||
plot::generate_ticks({0.0, std::nan("")}, 500.0f, 60.0f, t); // non-finite max
|
||||
PLOT_CHECK(t.empty());
|
||||
plot::generate_ticks({std::nan(""), 1.0}, 500.0f, 60.0f, t);
|
||||
PLOT_CHECK(t.empty());
|
||||
plot::generate_ticks({0.0, 1.0}, 500.0f, 0.0f, t); // min_px <= 0
|
||||
PLOT_CHECK(t.empty());
|
||||
// Span overflows to infinity: the step would be inf, so nothing is emitted
|
||||
// and, crucially, the loop never runs.
|
||||
plot::generate_ticks({-1e308, 1e308}, 500.0f, 60.0f, t);
|
||||
PLOT_CHECK(t.empty());
|
||||
}
|
||||
|
||||
PLOT_TEST(minor_ticks_subdivide_and_the_total_is_capped) {
|
||||
std::vector<plot::Tick> t;
|
||||
plot::generate_ticks({0.0, 1.0}, 500.0f, 60.0f, t, /*include_minors=*/true);
|
||||
PLOT_CHECK(t.size() > 10); // minors really added
|
||||
for (size_t i = 1; i < t.size(); ++i) PLOT_CHECK(t[i].value > t[i-1].value);
|
||||
// The major step is 0.2 (leading digit 2), so minors are quartered at 0.05.
|
||||
for (const plot::Tick& tick : t) {
|
||||
const double scale = tick.major ? 5.0 : 20.0;
|
||||
PLOT_CHECK(std::fabs(tick.value * scale - std::round(tick.value * scale)) < 1e-9);
|
||||
PLOT_CHECK(tick.major ? !tick.label.empty() : tick.label.empty());
|
||||
}
|
||||
// A range dense enough to want millions of minors stops at the hard cap.
|
||||
std::vector<plot::Tick> capped;
|
||||
plot::generate_ticks({0.0, 1e9}, 1e6f, 1.0f, capped, /*include_minors=*/true);
|
||||
PLOT_CHECK(capped.size() == plot::kMaxTicks);
|
||||
}
|
||||
|
||||
PLOT_TEST(si_labels_are_utf8_and_never_overrun) {
|
||||
char b[32];
|
||||
plot::format_si(0.0000015, b, sizeof b);
|
||||
PLOT_CHECK(std::string(b) == "1.5\xC2\xB5"); // U+00B5, not the Greek mu
|
||||
plot::format_si(1234.0, b, sizeof b);
|
||||
PLOT_CHECK(std::string(b) == "1.23k");
|
||||
plot::format_si(-1234.0, b, sizeof b);
|
||||
PLOT_CHECK(std::string(b) == "-1.23k");
|
||||
plot::format_si(2.5e9, b, sizeof b);
|
||||
PLOT_CHECK(std::string(b) == "2.5G");
|
||||
plot::format_si(1.5e-6, b, sizeof b, "s");
|
||||
PLOT_CHECK(std::string(b) == "1.5\xC2\xB5s"); // a caller-supplied unit suffix
|
||||
plot::format_si(std::nan(""), b, sizeof b); // never silently dropped
|
||||
PLOT_CHECK(std::string(b) == "nan");
|
||||
plot::format_si(std::numeric_limits<double>::infinity(), b, sizeof b);
|
||||
PLOT_CHECK(std::string(b) == "inf");
|
||||
|
||||
// A short buffer truncates; the string is always terminated inside it and
|
||||
// nothing is ever written at or past `n`.
|
||||
for (plot::usize n = 1; n <= 8; ++n) {
|
||||
char small[8];
|
||||
for (char& c : small) c = '#';
|
||||
plot::format_si(-1234.567, small, n);
|
||||
PLOT_CHECK(std::strlen(small) < n);
|
||||
PLOT_CHECK(small[std::strlen(small)] == '\0');
|
||||
}
|
||||
}
|
||||
|
||||
PLOT_TEST(format_time_shapes) {
|
||||
char b[32];
|
||||
plot::format_time(0.5, b, sizeof b); PLOT_CHECK(std::string(b) == "500ms");
|
||||
plot::format_time(1.5, b, sizeof b); PLOT_CHECK(std::string(b) == "1.5s");
|
||||
plot::format_time(90.0, b, sizeof b); PLOT_CHECK(std::string(b) == "1:30");
|
||||
plot::format_time(3661.0, b, sizeof b); PLOT_CHECK(std::string(b) == "1:01:01");
|
||||
plot::format_time(-90.0, b, sizeof b); PLOT_CHECK(std::string(b) == "-1:30");
|
||||
plot::format_time(86400.0, b, sizeof b); PLOT_CHECK(std::string(b) == "1d");
|
||||
plot::format_time(std::nan(""), b, sizeof b); PLOT_CHECK(std::string(b) == "nan");
|
||||
}
|
||||
Reference in new issue
Block a user