fix(plot): single-rounding clock labels, log-axis fits that refuse non-positive data

This commit is contained in:
Martino Ferrari committed 2026-09-27 22:28:56 +02:00
1 parent 93fc06f050
commit 606e40a9f1
7 files changed
+258 -73

No files matched your search

+57
View File
@@ -1,11 +1,42 @@
#include "harness.hpp"
#include <plot/plot/ticks.hpp>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <string>
#include <vector>
namespace {
/// Fails if any colon-separated field of a clock label reaches its radix: minutes
/// and seconds must stay below 60 and hours below 24 (a value that rounds up to
/// 24 h takes the day shape instead), so "1:60" or "24:00:00" cannot slip through.
void check_no_field_reaches_its_radix(const std::string& label) {
const std::size_t colons = static_cast<std::size_t>(std::count(label.begin(), label.end(), ':'));
if (colons == 0) return; // decimal shapes have no fields
// Two colons mean h:mm:ss, one means m:ss: only the former starts with hours.
const int first_radix = colons >= 2 ? 24 : 60;
std::string clock = label;
if (!clock.empty() && (clock[0] == '-' || clock[0] == '+')) clock.erase(0, 1);
std::size_t start = 0;
std::size_t field = 0;
while (start <= clock.size()) {
const std::size_t colon = clock.find(':', start);
const std::string part =
clock.substr(start, colon == std::string::npos ? std::string::npos : colon - start);
if (!part.empty()) {
const int value = std::atoi(part.c_str());
PLOT_CHECK(value >= 0 && value < (field == 0 ? first_radix : 60));
}
if (colon == std::string::npos) break;
start = colon + 1;
++field;
}
}
} // namespace
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);
@@ -107,3 +138,29 @@ PLOT_TEST(format_time_shapes) {
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");
}
PLOT_TEST(format_time_never_lets_a_field_reach_its_radix) {
char b[32];
// Rounding the seconds leftover on its own used to print 119.5 s as "1:60";
// every field now comes from one rounded whole-second count.
plot::format_time(119.5, b, sizeof b); PLOT_CHECK(std::string(b) == "2:00");
plot::format_time(3599.5, b, sizeof b); PLOT_CHECK(std::string(b) == "1:00:00");
plot::format_time(59.4999, b, sizeof b); PLOT_CHECK(std::string(b) == "59.5s");
plot::format_time(59.5, b, sizeof b); PLOT_CHECK(std::string(b) == "1:00");
plot::format_time(60.0, b, sizeof b); PLOT_CHECK(std::string(b) == "1:00");
// At 24 h the day field appears, so the hour field never reaches 24.
plot::format_time(86399.4, b, sizeof b); PLOT_CHECK(std::string(b) == "23:59:59");
plot::format_time(86399.5, b, sizeof b); PLOT_CHECK(std::string(b) == "1d");
plot::format_time(86400.0, b, sizeof b); PLOT_CHECK(std::string(b) == "1d");
// Sweep the boundary neighbourhoods and check every emitted field.
const double values[] = {0.4, 0.9995, 1.0, 59.4, 59.4999, 59.5, 59.9, 60.0, 90.0, 119.5, 3599.4,
3599.5, 3599.9, 86399.4, 86399.5, 86399.9, 86400.0, 172800.0, -119.5,
-3599.5, -86399.5};
for (const double v : values) {
plot::format_time(v, b, sizeof b);
const std::string label(b);
PLOT_CHECK(!label.empty());
check_no_field_reaches_its_radix(label);
}
}