Files
plotter/tests/test_transform.cpp
T
Martino Ferrari ed3a8ce645 licence: MIT LICENSE and SPDX identifiers in every first-party file
The repository had no licence at all, so every source was all-rights-reserved
by default. Add the verbatim MIT text at LICENSE (Copyright (c) 2026 Martino
Ferrari) and a two-line SPDX header to each of the 113 first-party sources --
48 include/plot, 26 src, 10 adapters, 5 examples, 21 tests, 3 tools -- so the
grant travels with each file rather than relying on the root document alone.

Scripts keep their shebang on line 1; the SPDX lines follow it. Genuinely
vendored code is untouched: third_party/stb_truetype.h carries no header of
ours. src/render/embedded_mono_font.cpp is generated, so tools/fonts/gen_atlas.py
emit() now writes the two lines into its header comment and the file was
regenerated -- gen_atlas.py --check passes and the regeneration adds only those
comment lines to the previously committed file.

README's provenance section now states MIT and links LICENSE.

No code, test or public API change.
2026-09-28 22:13:40 +02:00

97 lines
3.9 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#include "harness.hpp"
#include <plot/math/transform.hpp>
using plot::Transform;
PLOT_TEST(linear_round_trip) {
Transform t{2.0, 10.0, 3.0, -1.0, false, false};
PLOT_CHECK_NEAR(t.to_screen_x(0.0), 10.0, 1e-12);
PLOT_CHECK_NEAR(t.to_screen_x(5.0), 20.0, 1e-12);
PLOT_CHECK_NEAR(t.to_data_x(20.0), 5.0, 1e-12);
PLOT_CHECK_NEAR(t.to_data_y(t.to_screen_y(7.5)), 7.5, 1e-12);
}
PLOT_TEST(log_x_round_trip) {
Transform t{100.0, 0.0, 1.0, 0.0, true, false};
PLOT_CHECK_NEAR(t.to_screen_x(1.0), 0.0, 1e-12);
PLOT_CHECK_NEAR(t.to_screen_x(10.0), 100.0, 1e-12);
PLOT_CHECK_NEAR(t.to_data_x(100.0), 10.0, 1e-9);
}
PLOT_TEST(zoom_anchor_is_invariant) {
// Anchoring a zoom at a data coordinate must leave that coordinate's screen
// position unchanged.
Transform t{1.0, 0.0, 1.0, 0.0, false, false};
const double anchor_data = 3.25;
const double before = t.to_screen_x(anchor_data);
t.sx *= 4.0;
t.tx = before - anchor_data * t.sx;
PLOT_CHECK_NEAR(t.to_screen_x(anchor_data), before, 1e-12);
}
PLOT_TEST(from_range_linear) {
const Transform t =
Transform::from_range({0.0, 10.0}, {-1.0, 1.0}, 0.0, 100.0, 0.0, 200.0, false, false);
PLOT_CHECK_NEAR(t.to_screen_x(0.0), 0.0, 1e-12);
PLOT_CHECK_NEAR(t.to_screen_x(10.0), 100.0, 1e-12);
PLOT_CHECK_NEAR(t.to_screen_y(-1.0), 0.0, 1e-12);
PLOT_CHECK_NEAR(t.to_screen_y(1.0), 200.0, 1e-12);
PLOT_CHECK_NEAR(t.to_data_x(t.to_screen_x(3.5)), 3.5, 1e-12);
PLOT_CHECK_NEAR(t.to_data_y(t.to_screen_y(0.25)), 0.25, 1e-12);
}
PLOT_TEST(from_range_flipped_pixel_axis) {
// px0 > px1 reverses the direction of the axis; the round trip must survive.
const Transform t =
Transform::from_range({0.0, 10.0}, {0.0, 10.0}, 100.0, 0.0, 200.0, 0.0, false, false);
PLOT_CHECK_NEAR(t.to_screen_x(0.0), 100.0, 1e-12);
PLOT_CHECK_NEAR(t.to_screen_x(10.0), 0.0, 1e-12);
PLOT_CHECK_NEAR(t.to_screen_y(0.0), 200.0, 1e-12);
PLOT_CHECK_NEAR(t.to_screen_y(10.0), 0.0, 1e-12);
PLOT_CHECK_NEAR(t.to_data_x(t.to_screen_x(2.5)), 2.5, 1e-12);
PLOT_CHECK_NEAR(t.to_data_y(t.to_screen_y(7.5)), 7.5, 1e-12);
}
PLOT_TEST(from_range_log_x) {
const Transform t =
Transform::from_range({1.0, 1000.0}, {0.0, 1.0}, 0.0, 300.0, 0.0, 100.0, true, false);
PLOT_CHECK(t.log_x);
PLOT_CHECK(!t.log_y);
PLOT_CHECK_NEAR(t.to_screen_x(1.0), 0.0, 1e-12);
PLOT_CHECK_NEAR(t.to_screen_x(1000.0), 300.0, 1e-12);
// log10(31.6227766) == 1.5 lands exactly halfway along the axis.
PLOT_CHECK_NEAR(t.to_screen_x(31.6227766), 150.0, 1e-6);
PLOT_CHECK_NEAR(t.to_data_x(t.to_screen_x(31.6227766)), 31.6227766, 1e-9);
}
PLOT_TEST(from_range_degenerate_data_range) {
// min == max: the range is widened, so the mapping stays finite and invertible.
const Transform t =
Transform::from_range({5.0, 5.0}, {2.0, 2.0}, 0.0, 100.0, 0.0, 200.0, false, false);
PLOT_CHECK(std::isfinite(t.sx) && t.sx != 0.0);
PLOT_CHECK(std::isfinite(t.sy) && t.sy != 0.0);
PLOT_CHECK_NEAR(t.to_data_x(t.to_screen_x(5.0)), 5.0, 1e-9);
PLOT_CHECK_NEAR(t.to_data_y(t.to_screen_y(2.0)), 2.0, 1e-9);
}
PLOT_TEST(from_range_collapsed_pixel_span) {
// px0 == px1 previously gave sx == 0, so to_data_x returned NaN/inf.
const Transform t =
Transform::from_range({0.0, 10.0}, {0.0, 10.0}, 50.0, 50.0, 25.0, 25.0, false, false);
PLOT_CHECK(std::isfinite(t.sx) && t.sx != 0.0);
PLOT_CHECK(std::isfinite(t.sy) && t.sy != 0.0);
PLOT_CHECK_NEAR(t.to_data_x(t.to_screen_x(4.0)), 4.0, 1e-9);
PLOT_CHECK_NEAR(t.to_data_y(t.to_screen_y(7.0)), 7.0, 1e-9);
PLOT_CHECK(std::isfinite(t.to_data_x(t.to_screen_x(0.0))));
PLOT_CHECK(std::isfinite(t.to_data_y(t.to_screen_y(10.0))));
}
PLOT_TEST(log10_positive_clamps_non_positive) {
PLOT_CHECK(std::isfinite(Transform::log10_positive(0.0)));
PLOT_CHECK(std::isfinite(Transform::log10_positive(-5.0)));
PLOT_CHECK_NEAR(Transform::log10_positive(1.0), 0.0, 1e-12);
}