Files
Renderive/render_2D/axis/Abs_Axis.cpp
T
2026-08-11 23:17:03 +08:00

156 lines
6.3 KiB
C++

#include "Abs_Axis.h"
#include "Axis_Format.h"
#include "../render/Blend2D_Cache.h"
#include <algorithm>
#include <cmath>
namespace renderive {
Abs_Axis::Abs_Axis(Plot_Core& plot, Orientation orientation)
: Abs_Axis(plot, Properties{.orientation = orientation}) {}
Abs_Axis::Abs_Axis(Plot_Core& plot, const Properties& properties)
: Base(properties, plot, true) {}
Abs_Axis::~Abs_Axis() = default;
#define RENDERIVE_AXIS_PROPERTY(Type, Name) \
Type Abs_Axis::Name() const { return Base::template get<&Properties::Name>(); } \
void Abs_Axis::set_##Name(Type value) { set_axis_property<&Properties::Name>(std::move(value)); }
RENDERIVE_AXIS_PROPERTY(int, x)
RENDERIVE_AXIS_PROPERTY(int, y)
RENDERIVE_AXIS_PROPERTY(Orientation, orientation)
RENDERIVE_AXIS_PROPERTY(std::size_t, pixel_length)
RENDERIVE_AXIS_PROPERTY(int, tick_length)
RENDERIVE_AXIS_PROPERTY(int, sub_tick_length)
RENDERIVE_AXIS_PROPERTY(Color, color)
RENDERIVE_AXIS_PROPERTY(Number_Locale, locale)
RENDERIVE_AXIS_PROPERTY(std::string, unit_text)
RENDERIVE_AXIS_PROPERTY(Font, unit_text_font)
RENDERIVE_AXIS_PROPERTY(Pen, unit_text_pen)
RENDERIVE_AXIS_PROPERTY(Brush, unit_text_background_brush)
RENDERIVE_AXIS_PROPERTY(int, label_rotation_degrees)
#undef RENDERIVE_AXIS_PROPERTY
Abs_Axis::Properties Abs_Axis::axis_state() const {
return Base::read([](const Properties& value) { return value; });
}
Abs_Axis::Properties Abs_Axis::render_axis_state() const {
return Base::render_use_state();
}
Axis_Transform Abs_Axis::transform() const {
const Properties state = axis_state();
return {
coord_range(),
state.orientation == Orientation::Horizontal ? static_cast<double>(state.x)
: static_cast<double>(state.y),
static_cast<double>(state.pixel_length)
};
}
double Abs_Axis::pixel_to_coord(double pixel) const { return transform().pixel_to_coord(pixel); }
double Abs_Axis::coord_to_pixel(double coordinate) const { return transform().coord_to_pixel(coordinate); }
double Abs_Axis::start_coord() const { return coord_range().origin; }
double Abs_Axis::end_coord() const { return coord_range().target; }
int Abs_Axis::pixel_sample_count(Range range) const {
const Axis_Transform value = transform();
const double first = value.coord_to_pixel(range.origin);
const double last = value.coord_to_pixel(range.target);
return std::max(0, static_cast<int>(std::abs(last - first)) + 1);
}
int Abs_Axis::pixel_sample_count() const {
const auto length = pixel_length();
return static_cast<int>(length) + (length > 0 ? 1 : 0);
}
double Abs_Axis::tick_step(Range range) const {
const double raw = range.size() / 5.0;
if (!(raw > 0.0) || !std::isfinite(raw))
return 1.0;
const double scale = std::pow(10.0, std::floor(std::log10(raw)));
const double normalized = raw / scale;
const double nice = normalized <= 1.0 ? 1.0 : normalized <= 2.0 ? 2.0 : normalized <= 5.0 ? 5.0 : 10.0;
return nice * scale;
}
int Abs_Axis::sub_tick_count(double) const { return 4; }
std::string Abs_Axis::tick_label(double tick) const {
return detail::localized_axis_number(tick, 2, locale());
}
void Abs_Axis::paint(detail::Painter& painter) {
const Properties state = render_axis_state();
if (state.pixel_length == 0)
return;
const Range coordinates = coord_range();
const double step = tick_step(coordinates);
if (!(step > 0.0))
return;
const Pen axis_pen{state.color, 1.0};
const PointF first{static_cast<double>(state.x), static_cast<double>(state.y)};
const PointF last = state.orientation == Orientation::Horizontal
? PointF{first.x + state.pixel_length, first.y}
: PointF{first.x, first.y + state.pixel_length};
painter.line(first, last, axis_pen);
const auto [low, high] = std::minmax(coordinates.origin, coordinates.target);
const double initial = std::ceil(low / step) * step;
int tick_index{};
for (double tick = initial; tick <= high + step * 1e-6 && tick_index < 1000;
tick += step, ++tick_index) {
const double pixel = coord_to_pixel(tick);
PointF tick_start;
PointF tick_end;
PointF label;
if (state.orientation == Orientation::Horizontal) {
tick_start = {pixel, static_cast<double>(state.y)};
tick_end = {pixel, static_cast<double>(state.y + state.tick_length)};
label = {pixel + 2.0, static_cast<double>(state.y + state.tick_length + 2)};
} else {
tick_start = {static_cast<double>(state.x), pixel};
tick_end = {static_cast<double>(state.x + state.tick_length), pixel};
label = {static_cast<double>(state.x + state.tick_length + 2), pixel - 7.0};
}
painter.line(tick_start, tick_end, axis_pen);
painter.text(label, tick_label(tick), state.unit_text_font, state.unit_text_pen,
state.label_rotation_degrees);
const int subdivisions = std::max(0, sub_tick_count(step));
for (int sub_index = 1; sub_index <= subdivisions; ++sub_index) {
const double sub_tick = tick + step * sub_index / (subdivisions + 1.0);
if (sub_tick >= high)
break;
const double sub_pixel = coord_to_pixel(sub_tick);
if (state.orientation == Orientation::Horizontal) {
painter.line({sub_pixel, static_cast<double>(state.y)},
{sub_pixel, static_cast<double>(state.y + state.sub_tick_length)},
axis_pen);
} else {
painter.line({static_cast<double>(state.x), sub_pixel},
{static_cast<double>(state.x + state.sub_tick_length), sub_pixel},
axis_pen);
}
}
}
if (!state.unit_text.empty()) {
const PointF position{last.x + 4.0, last.y + 4.0};
const double estimated_width = std::max(4.0, state.unit_text.size() * state.unit_text_font.size * 0.65);
painter.rect({position.x - 2.0, position.y - 2.0,
estimated_width + 4.0, state.unit_text_font.size * 1.5 + 4.0},
Pen{.style = Line_Style::None}, state.unit_text_background_brush);
painter.text(position, state.unit_text,
state.unit_text_font, state.unit_text_pen);
}
}
} // namespace renderive