改造一些

This commit is contained in:
2026-08-12 00:06:44 +08:00
parent aca480d988
commit abd1b595c7
57 changed files with 883 additions and 732 deletions
+3 -135
View File
@@ -1,64 +1,14 @@
#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(Plot_Core& plot) : Renderable(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);
@@ -66,90 +16,8 @@ int Abs_Axis::pixel_sample_count(Range range) const {
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);
}
int Abs_Axis::sub_tick_count(double) const noexcept {
return 4;
}
} // namespace renderive
+7 -57
View File
@@ -3,71 +3,21 @@
#include "../renderable/Renderable.h"
#include "Axis_Types.h"
#include <renderive/state/Double_State_Strategy.hpp>
#include <string>
#include <utility>
namespace renderive {
class LIB_DECL Abs_Axis : public Double_State_Strategy<Renderable, Axis_Base_Properties> {
class LIB_DECL Abs_Axis : public Renderable {
public:
using Properties = Axis_Base_Properties;
using Base = Double_State_Strategy<Renderable, Properties>;
explicit Abs_Axis(Plot_Core& plot, Orientation orientation = Orientation::Horizontal);
Abs_Axis(Plot_Core& plot, const Properties& properties);
explicit Abs_Axis(Plot_Core& plot);
~Abs_Axis() override;
[[nodiscard]] int x() const;
void set_x(int value);
[[nodiscard]] int y() const;
void set_y(int value);
[[nodiscard]] Orientation orientation() const;
void set_orientation(Orientation value);
[[nodiscard]] std::size_t pixel_length() const;
void set_pixel_length(std::size_t value);
[[nodiscard]] int tick_length() const;
void set_tick_length(int value);
[[nodiscard]] int sub_tick_length() const;
void set_sub_tick_length(int value);
[[nodiscard]] Color color() const;
void set_color(Color value);
[[nodiscard]] Number_Locale locale() const;
void set_locale(Number_Locale value);
[[nodiscard]] std::string unit_text() const;
void set_unit_text(std::string value);
[[nodiscard]] Font unit_text_font() const;
void set_unit_text_font(Font value);
[[nodiscard]] Pen unit_text_pen() const;
void set_unit_text_pen(Pen value);
[[nodiscard]] Brush unit_text_background_brush() const;
void set_unit_text_background_brush(Brush value);
[[nodiscard]] int label_rotation_degrees() const;
void set_label_rotation_degrees(int value);
[[nodiscard]] virtual Range coord_range() const = 0;
[[nodiscard]] virtual double pixel_to_coord(double pixel) const;
[[nodiscard]] virtual double coord_to_pixel(double coordinate) const;
[[nodiscard]] Axis_Transform transform() const;
[[nodiscard]] double start_coord() const;
[[nodiscard]] double end_coord() const;
[[nodiscard]] virtual Axis_Transform transform() const = 0;
[[nodiscard]] virtual Axis_Transform transform(const Render_State_View& state) const noexcept = 0;
[[nodiscard]] int pixel_sample_count(Range range) const;
[[nodiscard]] int pixel_sample_count() const;
[[nodiscard]] virtual double tick_step(Range range) const;
[[nodiscard]] virtual int sub_tick_count(double major_step) const;
[[nodiscard]] virtual std::string tick_label(double tick) const;
protected:
[[nodiscard]] Properties axis_state() const;
[[nodiscard]] Properties render_axis_state() const;
void paint(detail::Painter& painter) override;
template <auto Member, Property_Member_Assignable<Properties, Member> Value>
void set_axis_property(Value&& value) {
Base::template set<Member>(std::forward<Value>(value));
changed();
}
[[nodiscard]] virtual double tick_step(Range range) const = 0;
[[nodiscard]] int sub_tick_count(double major_step) const noexcept;
[[nodiscard]] virtual std::string tick_label(double tick) const = 0;
};
} // namespace renderive
+156
View File
@@ -0,0 +1,156 @@
#pragma once
#include "Abs_Axis.h"
#include "Axis_Format.h"
#include "../render/Blend2D_Cache.h"
#include <renderive/base/Atomic_Mutex.hpp>
#include <renderive/base/observer/Observer.hpp>
#include <renderive/state/Double_State_Strategy.hpp>
#include <algorithm>
#include <cmath>
#include <type_traits>
#include <utility>
namespace renderive::detail {
template <class State>
requires std::is_base_of_v<Axis_Base_Properties, State>
class Axis_State_Strategy
: public Double_State_Strategy<Abs_Axis, State, Atomic_Spin_Mutex,
Observer_State<Renderable_State_Observer>> {
using State_Observer = Observer_State<Renderable_State_Observer>;
using Base = Double_State_Strategy<Abs_Axis, State, Atomic_Spin_Mutex, State_Observer>;
public:
Axis_State_Strategy(Plot_Core& plot, State state)
: Base(state,
With_Observer<State_Observer>{State_Observer(Renderable_State_Observer(this))},
plot) {}
[[nodiscard]] Axis_Transform transform() const final {
return Base::read([this](const State& state) {
return make_transform(state, coordinate_range(state));
});
}
[[nodiscard]] Axis_Transform transform(const Render_State_View& view) const noexcept final {
const auto& state = view.get(static_cast<const Base&>(*this));
return make_transform(state, coordinate_range(state));
}
[[nodiscard]] double tick_step(Range range) const final {
return Base::read([this, range](const State& state) {
return calculate_tick_step(range, state);
});
}
[[nodiscard]] std::string tick_label(double tick) const final {
return Base::read([this, tick](const State& state) {
return format_tick_label(tick, state);
});
}
protected:
[[nodiscard]] virtual Range coordinate_range(const State& state) const noexcept = 0;
[[nodiscard]] virtual double calculate_tick_step(Range range, const State&) 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;
}
[[nodiscard]] virtual std::string format_tick_label(double tick, const State& state) const {
int precision = 2;
if constexpr (requires { state.precision; })
precision = state.precision.get();
return localized_axis_number(tick, precision, state.locale);
}
private:
static Axis_Transform make_transform(const State& state, Range coordinates) noexcept {
return {
coordinates,
state.orientation == Orientation::Horizontal ? static_cast<double>(state.x)
: static_cast<double>(state.y),
static_cast<double>(state.pixel_length)
};
}
void paint(Painter& painter, const Render_State_View& view) final {
const auto& state = view.get(static_cast<const Base&>(*this));
if (state.pixel_length == 0)
return;
const Range coordinates = coordinate_range(state);
const Axis_Transform axis = make_transform(state, coordinates);
const double step = calculate_tick_step(coordinates, state);
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 = axis.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, format_tick_label(tick, state), state.unit_text_font,
state.unit_text_pen, state.label_rotation_degrees);
const int subdivisions = std::max(0, this->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 = axis.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::detail
+5 -4
View File
@@ -9,13 +9,14 @@ namespace renderive::detail {
Frequency_Axis_Control::Frequency_Axis_Control(Plot_Core& plot, const Axis_Properties& properties)
: Axis(plot, properties) {}
std::string Frequency_Axis_Control::tick_label(double tick) const {
std::string Frequency_Axis_Control::format_tick_label(double tick,
const Axis_Properties& state) const {
const double absolute = std::abs(tick);
if (absolute >= 1'000'000.0)
return localized_axis_number(tick / 1'000'000.0, label_precision(), locale()) + " MHz";
return localized_axis_number(tick / 1'000'000.0, state.precision.get(), state.locale) + " MHz";
if (absolute >= 1'000.0)
return localized_axis_number(tick / 1'000.0, label_precision(), locale()) + " kHz";
return localized_axis_number(tick, label_precision(), locale()) + " Hz";
return localized_axis_number(tick / 1'000.0, state.precision.get(), state.locale) + " kHz";
return localized_axis_number(tick, state.precision.get(), state.locale) + " Hz";
}
} // namespace renderive::detail
+2 -1
View File
@@ -9,7 +9,8 @@ namespace detail {
class LIB_DECL Frequency_Axis_Control : public Axis {
public:
Frequency_Axis_Control(Plot_Core& plot, const Axis_Properties& properties);
[[nodiscard]] std::string tick_label(double tick) const override;
protected:
[[nodiscard]] std::string format_tick_label(double tick, const Axis_Properties& state) const override;
};
} // namespace detail
+24 -98
View File
@@ -1,96 +1,27 @@
#include "Numeric_Axis.h"
#include "Axis_Format.h"
#include <algorithm>
#include <cmath>
namespace renderive::detail {
namespace {
bool valid_range(Range range) {
return std::isfinite(range.origin) && std::isfinite(range.target) && range.size() > 0.0;
}
Numeric_Axis_State numeric_state_from(const Axis_Properties& properties) {
return {
properties.coordinates,
std::clamp(properties.precision, 0, 12),
properties.wheel,
properties.drag
};
}
} // namespace
Axis_Control::Axis_Control(Plot_Core& plot, const Axis_Properties& properties)
: Abs_Axis(plot, properties), numeric_(numeric_state_from(properties)) {}
Range Axis_Control::coord_range() const {
return numeric_.get<&Numeric_Axis_State::coordinates>();
}
int Axis_Control::label_precision() const {
return numeric_.get<&Numeric_Axis_State::precision>();
}
void Axis_Control::set_label_precision(int value) {
numeric_.set<&Numeric_Axis_State::precision>(std::clamp(value, 0, 12));
changed();
}
double Axis_Control::coord_start() const { return coord_range().origin; }
void Axis_Control::set_coord_start(double value) {
auto range = coord_range();
set_coord_range({value, value + range.length()});
}
double Axis_Control::coord_length() const { return coord_range().length(); }
void Axis_Control::set_coord_length(double value) {
auto range = coord_range();
set_coord_range({range.origin, range.origin + value});
}
void Axis_Control::set_coord_range(Range range) {
if (!valid_range(range))
return;
numeric_.set<&Numeric_Axis_State::coordinates>(range);
changed();
}
void Axis_Control::set_use_wheel(bool value) {
numeric_.set<&Numeric_Axis_State::wheel>(value);
changed();
}
void Axis_Control::set_use_drag(bool value) {
numeric_.set<&Numeric_Axis_State::drag>(value);
changed();
}
bool Axis_Control::use_wheel() const {
return numeric_.get<&Numeric_Axis_State::wheel>();
}
bool Axis_Control::use_drag() const {
return numeric_.get<&Numeric_Axis_State::drag>();
}
: Axis_State_Strategy(plot, properties) {}
void Axis_Control::handle_event(const Event& event) {
if (event.type == Event_Type::Wheel && use_wheel()) {
if (event.type == Event_Type::Wheel && get<&Axis_Properties::wheel>()) {
const auto& wheel = static_cast<const Wheel_Event&>(event);
Range range = coord_range();
const double anchor_pixel = orientation() == Orientation::Horizontal ? wheel.position.x : wheel.position.y;
const double anchor = pixel_to_coord(anchor_pixel);
const Range range = get<&Axis_Properties::coordinates>();
const Orientation orientation = get<&Axis_Base_Properties::orientation>();
const double anchor_pixel = orientation == Orientation::Horizontal
? wheel.position.x
: wheel.position.y;
const double anchor = transform().pixel_to_coord(anchor_pixel);
const double factor = wheel.angle_delta_y >= 0.0 ? 0.9 : 1.1;
set_coord_range({anchor + (range.origin - anchor) * factor,
anchor + (range.target - anchor) * factor});
const Range next{anchor + (range.origin - anchor) * factor,
anchor + (range.target - anchor) * factor};
set<&Axis_Properties::coordinates>(next);
event.accept();
return;
}
if (!use_drag())
if (!get<&Axis_Properties::drag>())
return;
if (event.type == Event_Type::Pointer_Press) {
const auto& pointer = static_cast<const Pointer_Event&>(event);
@@ -114,12 +45,16 @@ void Axis_Control::handle_event(const Event& event) {
});
if (!dragging)
return;
const double delta = orientation() == Orientation::Horizontal
const Orientation orientation = get<&Axis_Base_Properties::orientation>();
const double delta = orientation == Orientation::Horizontal
? pointer.position.x - previous.x
: pointer.position.y - previous.y;
Range range = coord_range();
const double shift = pixel_length() == 0 ? 0.0 : -delta * range.length() / pixel_length();
set_coord_range({range.origin + shift, range.target + shift});
const Range range = get<&Axis_Properties::coordinates>();
const std::size_t pixel_length = get<&Axis_Base_Properties::pixel_length>();
const double shift = pixel_length == 0
? 0.0
: -delta * range.length() / static_cast<double>(pixel_length);
set<&Axis_Properties::coordinates>(Range{range.origin + shift, range.target + shift});
event.accept();
} else if (event.type == Event_Type::Pointer_Release) {
bool accepted{};
@@ -132,26 +67,17 @@ void Axis_Control::handle_event(const Event& event) {
}
}
std::string Axis_Control::tick_label(double tick) const {
return localized_axis_number(tick, label_precision(), locale());
}
void Axis_Control::publish() {
Abs_Axis::publish();
numeric_.publish();
Axis_State_Strategy::publish();
interaction_.publish();
}
std::uint64_t Axis_Control::state_revision() const {
return Abs_Axis::state_revision() + numeric_.state_revision() + interaction_.state_revision();
return Axis_State_Strategy::state_revision() + interaction_.state_revision();
}
Numeric_Axis_State Axis_Control::numeric_state() const {
return numeric_.read([](const Numeric_Axis_State& value) { return value; });
}
Numeric_Axis_State Axis_Control::render_numeric_state() const {
return numeric_.render_use_state();
Range Axis_Control::coordinate_range(const Axis_Properties& state) const noexcept {
return state.coordinates.get();
}
} // namespace renderive::detail
+18 -32
View File
@@ -1,65 +1,51 @@
#pragma once
#include "Abs_Axis.h"
#include "Axis_State_Strategy.hpp"
#include "Axis_Builder.h"
#include <renderive/base/property/Attach_Builder.hpp>
#include <renderive/state/Double_State_Strategy.hpp>
#include <renderive/base/property/Property.hpp>
#include <cmath>
#include <stdexcept>
namespace renderive {
struct Axis_Coordinate_Range_Validator {
void operator()(const Range& value) const {
if (!std::isfinite(value.origin) || !std::isfinite(value.target) || !(value.size() > 0.0))
throw std::invalid_argument("axis coordinate range must be finite and non-empty");
}
};
struct Axis_Properties : Axis_Base_Properties {
Range coordinates{0.0, 20.0};
int precision = 2;
Validated_Value<Range, Axis_Coordinate_Range_Validator> coordinates{Range{0.0, 20.0}};
Range_Value<int, 0, 12> precision{2};
bool wheel{};
bool drag{};
};
namespace detail {
struct Numeric_Axis_State {
Range coordinates{0.0, 20.0};
int precision = 2;
bool wheel{};
bool drag{};
};
struct Axis_Interaction_State {
bool dragging{};
PointF last_pointer{};
};
class LIB_DECL Axis_Control : public Abs_Axis, public Event_Handler {
class LIB_DECL Axis_Control : public Axis_State_Strategy<Axis_Properties>, public Event_Handler {
public:
Axis_Control(Plot_Core& plot, const Axis_Properties& properties);
[[nodiscard]] Range coord_range() const override;
[[nodiscard]] int label_precision() const;
void set_label_precision(int value);
[[nodiscard]] double coord_start() const;
void set_coord_start(double value);
[[nodiscard]] double coord_length() const;
void set_coord_length(double value);
void set_coord_range(Range range);
void set_use_wheel(bool value);
void set_use_drag(bool value);
[[nodiscard]] bool use_wheel() const;
[[nodiscard]] bool use_drag() const;
void handle_event(const Event& event) override;
[[nodiscard]] std::string tick_label(double tick) const override;
void publish() override;
[[nodiscard]] std::uint64_t state_revision() const override;
protected:
[[nodiscard]] Numeric_Axis_State numeric_state() const;
[[nodiscard]] Numeric_Axis_State render_numeric_state() const;
private:
struct Numeric_Base {};
struct Interaction_Base {};
using Numeric_State = Double_State_Strategy<Numeric_Base, Numeric_Axis_State>;
using Interaction_State = Double_State_Strategy<Interaction_Base, Axis_Interaction_State>;
Numeric_State numeric_;
protected:
[[nodiscard]] Range coordinate_range(const Axis_Properties& state) const noexcept override;
private:
Interaction_State interaction_;
};
+27 -82
View File
@@ -9,117 +9,62 @@ namespace renderive::detail {
namespace {
Time_Axis_State time_state_from(const Time_Axis_Properties& properties) {
return {
std::max(2, properties.visible_count),
std::max(0, properties.tick_label_spacing_px),
properties.format,
properties.newest_at_start,
0,
{}
};
Time_Axis_State state;
static_cast<Time_Axis_Properties&>(state) = properties;
return state;
}
} // namespace
Time_Axis_Control::Time_Axis_Control(Plot_Core& plot, const Time_Axis_Properties& properties)
: Abs_Axis(plot, properties), time_(time_state_from(properties)) {}
int Time_Axis_Control::visible_time_point_count() const {
return time_.get<&Time_Axis_State::visible_count>();
}
void Time_Axis_Control::set_visible_time_point_count(int value) {
time_.set<&Time_Axis_State::visible_count>(std::max(2, value));
changed();
}
int Time_Axis_Control::tick_label_spacing_px() const {
return time_.get<&Time_Axis_State::tick_label_spacing_px>();
}
void Time_Axis_Control::set_tick_label_spacing_px(int value) {
time_.set<&Time_Axis_State::tick_label_spacing_px>(std::max(0, value));
changed();
}
std::string Time_Axis_Control::time_format() const {
return time_.get<&Time_Axis_State::format>();
}
void Time_Axis_Control::set_time_format(std::string value) {
time_.set<&Time_Axis_State::format>(std::move(value));
changed();
}
Font Time_Axis_Control::font() const { return unit_text_font(); }
void Time_Axis_Control::set_font(Font value) { set_unit_text_font(value); }
bool Time_Axis_Control::newest_at_axis_start() const {
return time_.get<&Time_Axis_State::newest_at_start>();
}
void Time_Axis_Control::set_newest_at_axis_start(bool value) {
time_.set<&Time_Axis_State::newest_at_start>(value);
changed();
}
: Axis_State_Strategy(plot, time_state_from(properties)) {}
std::size_t Time_Axis_Control::time_point_count() const {
return time_.read([](const Time_Axis_State& state) { return state.samples.size(); });
return read([](const Time_Axis_State& state) { return state.samples.size(); });
}
int Time_Axis_Control::append_time(Time_Of_Day time) {
int tick{};
time_.update([&](Time_Axis_State& state) {
update([&](Time_Axis_State& state) {
tick = state.next_tick++;
state.samples.emplace_back(tick, time);
const auto limit = static_cast<std::size_t>(std::max(512, state.visible_count * 4));
const auto limit = static_cast<std::size_t>(std::max(512, state.visible_count.get() * 4));
while (state.samples.size() > limit)
state.samples.pop_front();
});
changed();
return tick;
}
Time_Of_Day Time_Axis_Control::tick_to_time(int tick) const {
return time_.read([tick](const Time_Axis_State& state) {
auto iterator = std::find_if(state.samples.begin(), state.samples.end(),
[tick](const auto& value) { return value.first == tick; });
return read([tick](const Time_Axis_State& state) {
const auto iterator = std::find_if(state.samples.begin(), state.samples.end(),
[tick](const auto& value) { return value.first == tick; });
return iterator == state.samples.end() ? Time_Of_Day{} : iterator->second;
});
}
Range Time_Axis_Control::coord_range() const {
return time_.read([](const Time_Axis_State& state) {
const int latest = std::max(1, state.next_tick - 1);
const int earliest = std::max(0, latest - state.visible_count + 1);
return state.newest_at_start ? Range{static_cast<double>(latest), static_cast<double>(earliest)}
: Range{static_cast<double>(earliest), static_cast<double>(latest)};
});
Range Time_Axis_Control::coordinate_range(const Time_Axis_State& state) const noexcept {
const int latest = std::max(1, state.next_tick - 1);
const int earliest = std::max(0, latest - state.visible_count.get() + 1);
return state.newest_at_start ? Range{static_cast<double>(latest), static_cast<double>(earliest)}
: Range{static_cast<double>(earliest), static_cast<double>(latest)};
}
double Time_Axis_Control::tick_step(Range range) const {
const double available = static_cast<double>(pixel_length());
const double label_width = std::max(48.0, font().size * 7.0);
const double spacing = static_cast<double>(tick_label_spacing_px());
const double label_count = std::max(1.0, available / (label_width + spacing));
double Time_Axis_Control::calculate_tick_step(Range range, const Time_Axis_State& state) const {
const double available = static_cast<double>(state.pixel_length);
const double label_width = std::max(48.0, state.unit_text_font.size * 7.0);
const double label_count =
std::max(1.0, available / (label_width + state.tick_label_spacing_px.get()));
return std::max(1.0, std::ceil(range.size() / label_count));
}
std::string Time_Axis_Control::tick_label(double tick) const {
const Time_Of_Day time = tick_to_time(static_cast<int>(std::llround(tick)));
if (!time.valid())
return {};
return formatted_axis_time(time, time_format());
}
void Time_Axis_Control::publish() {
Abs_Axis::publish();
time_.publish();
}
std::uint64_t Time_Axis_Control::state_revision() const {
return Abs_Axis::state_revision() + time_.state_revision();
std::string Time_Axis_Control::format_tick_label(double tick, const Time_Axis_State& state) const {
const int target = static_cast<int>(std::llround(tick));
const auto iterator = std::find_if(state.samples.begin(), state.samples.end(),
[target](const auto& value) { return value.first == target; });
return iterator == state.samples.end()
? std::string{}
: formatted_axis_time(iterator->second, state.format);
}
} // namespace renderive::detail
+10 -30
View File
@@ -1,61 +1,41 @@
#pragma once
#include "Abs_Axis.h"
#include "Axis_State_Strategy.hpp"
#include "Axis_Builder.h"
#include <renderive/base/property/Attach_Builder.hpp>
#include <renderive/state/Double_State_Strategy.hpp>
#include <renderive/base/property/Property.hpp>
#include <deque>
#include <limits>
#include <utility>
namespace renderive {
struct Time_Axis_Properties : Axis_Base_Properties {
int visible_count = 100;
int tick_label_spacing_px = 8;
Range_Value<int, 2, std::numeric_limits<int>::max()> visible_count{100};
Range_Value<int, 0, std::numeric_limits<int>::max()> tick_label_spacing_px{8};
std::string format = "mm:ss.zzz";
bool newest_at_start{};
};
namespace detail {
struct Time_Axis_State {
int visible_count = 100;
int tick_label_spacing_px = 8;
std::string format = "mm:ss.zzz";
bool newest_at_start{};
struct Time_Axis_State : Time_Axis_Properties {
int next_tick{};
std::deque<std::pair<int, Time_Of_Day>> samples;
};
class LIB_DECL Time_Axis_Control : public Abs_Axis {
class LIB_DECL Time_Axis_Control : public Axis_State_Strategy<Time_Axis_State> {
public:
Time_Axis_Control(Plot_Core& plot, const Time_Axis_Properties& properties);
[[nodiscard]] int visible_time_point_count() const;
void set_visible_time_point_count(int value);
[[nodiscard]] int tick_label_spacing_px() const;
void set_tick_label_spacing_px(int value);
[[nodiscard]] std::string time_format() const;
void set_time_format(std::string value);
[[nodiscard]] Font font() const;
void set_font(Font value);
[[nodiscard]] bool newest_at_axis_start() const;
void set_newest_at_axis_start(bool value);
[[nodiscard]] std::size_t time_point_count() const;
int append_time(Time_Of_Day time);
[[nodiscard]] Time_Of_Day tick_to_time(int tick) const;
[[nodiscard]] Range coord_range() const override;
[[nodiscard]] double tick_step(Range range) const override;
[[nodiscard]] std::string tick_label(double tick) const override;
void publish() override;
[[nodiscard]] std::uint64_t state_revision() const override;
private:
struct Time_Base {};
using Time_State = Double_State_Strategy<Time_Base, Time_Axis_State>;
Time_State time_;
[[nodiscard]] Range coordinate_range(const Time_Axis_State& state) const noexcept override;
[[nodiscard]] double calculate_tick_step(Range range, const Time_Axis_State& state) const override;
[[nodiscard]] std::string format_tick_label(double tick, const Time_Axis_State& state) const override;
};
} // namespace detail