// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2026 Martino Ferrari #include "harness.hpp" #include 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); }