Files
Renderive/render_2D/plottable/Heatmap_Utils.h
T
2026-08-12 09:01:25 +08:00

114 lines
5.1 KiB
C++

#pragma once
#include "../axis/Axis.h"
#include <algorithm>
#include <cmath>
#include <optional>
namespace renderive::detail {
inline bool axes_are_orthogonal(const Axis_Transform& first,
const Axis_Transform& second) noexcept {
return first.orientation != second.orientation;
}
inline PointF mapped_point(const Axis_Transform& first,
double first_coordinate,
const Axis_Transform& second,
double second_coordinate) noexcept {
if (!axes_are_orthogonal(first, second))
return {};
const double first_pixel = first.coord_to_pixel(first_coordinate);
const double second_pixel = second.coord_to_pixel(second_coordinate);
return first.orientation == Orientation::Horizontal
? PointF{first_pixel, second_pixel}
: PointF{second_pixel, first_pixel};
}
inline RectF mapped_rect(const Axis_Transform& first,
const Axis_Transform& second,
Range first_range,
Range second_range) noexcept {
if (!axes_are_orthogonal(first, second))
return {};
const PointF origin = mapped_point(first, first_range.origin, second, second_range.origin);
const PointF target = mapped_point(first, first_range.target, second, second_range.target);
return {std::min(origin.x, target.x), std::min(origin.y, target.y),
std::abs(target.x - origin.x), std::abs(target.y - origin.y)};
}
struct Axis_Raster_Layout {
int width{};
int height{};
RectF target;
bool first_reversed{};
bool second_reversed{};
bool first_is_horizontal{};
[[nodiscard]] bool valid() const noexcept {
return width > 0 && height > 0 && !target.empty();
}
[[nodiscard]] std::size_t index(int first, int second,
int first_count, int second_count) const noexcept {
if (first_reversed)
first = first_count - 1 - first;
if (second_reversed)
second = second_count - 1 - second;
const int x = first_is_horizontal ? first : second;
const int y = first_is_horizontal ? second : first;
return static_cast<std::size_t>(y) * width + x;
}
};
inline Axis_Raster_Layout axis_raster_layout(const Axis_Transform& first,
const Axis_Transform& second,
Range first_range,
Range second_range,
int first_count,
int second_count) noexcept {
if (!axes_are_orthogonal(first, second) || first_count <= 0 || second_count <= 0)
return {};
const bool first_horizontal = first.orientation == Orientation::Horizontal;
return {
first_horizontal ? first_count : second_count,
first_horizontal ? second_count : first_count,
mapped_rect(first, second, first_range, second_range),
first.coord_to_pixel(first_range.origin) > first.coord_to_pixel(first_range.target),
second.coord_to_pixel(second_range.origin) > second.coord_to_pixel(second_range.target),
first_horizontal
};
}
inline double normalized_value(double value, Range range) {
if(range.length() == 0.0)
return 0.0;
return std::clamp((value - range.origin) / range.length(), 0.0, 1.0);
}
struct Frequency_Columns {
int first{};
int last{};
Range range;
};
inline std::optional<Frequency_Columns> frequency_columns(Range data_range, Range visible_range, int column_count, bool visible_only) {
if(column_count <= 0)
return std::nullopt;
if(!visible_only || column_count == 1 || data_range.length() == 0.0)
return Frequency_Columns{0, column_count - 1, data_range};
const auto [data_low, data_high] = std::minmax(data_range.origin, data_range.target);
const auto [visible_low, visible_high] = std::minmax(visible_range.origin, visible_range.target);
const double clipped_low = std::max(data_low, visible_low);
const double clipped_high = std::min(data_high, visible_high);
if(clipped_low > clipped_high)
return std::nullopt;
const auto position = [data_range, column_count](double coordinate) { return (coordinate - data_range.origin) / data_range.length() * static_cast<double>(column_count - 1); };
const auto [position_low, position_high] = std::minmax(position(clipped_low), position(clipped_high));
int first = std::clamp(static_cast<int>(std::floor(position_low)), 0, column_count - 1);
int last = std::clamp(static_cast<int>(std::ceil(position_high)), first, column_count - 1);
if(first == last) {
if(last + 1 < column_count)
++last;
else if(first > 0)
--first;
}
const auto coordinate = [data_range, column_count](int index) { return data_range.origin + data_range.length() * static_cast<double>(index) / static_cast<double>(column_count - 1); };
return Frequency_Columns{first, last, {coordinate(first), coordinate(last)}};
}
}