改造一些
This commit is contained in:
+3
-135
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user