#pragma once #include "../axis/Axis.h" #include #include #include 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(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(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(column_count - 1); }; const double first_position = position(clipped_low); const double last_position = position(clipped_high); const double position_low = std::min(first_position, last_position); const double position_high = std::max(first_position, last_position); int first = std::clamp(static_cast(std::floor(position_low)), 0, column_count - 1); int last = std::clamp(static_cast(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(index) / static_cast(column_count - 1); }; return Frequency_Columns{first, last, {coordinate(first), coordinate(last)}}; } }