#include "Constellation_Diagram.h" #include "../render/Blend2D_Cache.h" #include #include #include #include #include namespace renderive { namespace detail { namespace { struct Timed_Point { PointF point; std::chrono::steady_clock::time_point time; }; struct Constellation_Runtime_Base {}; struct Constellation_Runtime { std::deque points; }; using Constellation_Runtime_State = Double_State_Strategy; } struct Constellation_Diagram_Control::Impl { Impl(std::shared_ptr i, std::shared_ptr q) : i_axis(std::move(i)), q_axis(std::move(q)) {} std::shared_ptr i_axis; std::shared_ptr q_axis; Constellation_Runtime_State runtime; }; Constellation_Diagram_Control::Constellation_Diagram_Control(Plot_Core& plot, const Constellation_Diagram_Properties& properties, std::shared_ptr i_axis, std::shared_ptr q_axis) : Plottable_State(plot, properties), impl_(std::make_unique(std::move(i_axis), std::move(q_axis))) {} Constellation_Diagram_Control::~Constellation_Diagram_Control() = default; void Constellation_Diagram_Control::append_point(PointF point) { const auto now = std::chrono::steady_clock::now(); const int lifetime = get<&Constellation_Diagram_Properties::point_lifetime_ms>(); impl_->runtime.update([point, now, lifetime](Constellation_Runtime& runtime) { runtime.points.push_back({point, now}); const auto cutoff = now - std::chrono::milliseconds(lifetime); while(!runtime.points.empty() && runtime.points.front().time < cutoff) runtime.points.pop_front(); }); changed(); } std::size_t Constellation_Diagram_Control::point_count() const { return impl_->runtime.read([](const Constellation_Runtime& runtime) { return runtime.points.size(); }); } void Constellation_Diagram_Control::fit_square_to_axes() { const auto state = properties(); const double side = std::max(state.i_range.size(), state.q_range.size()); impl_->i_axis->set_coord_range({state.i_range.center() - side * 0.5, state.i_range.center() + side * 0.5}); impl_->q_axis->set_coord_range({state.q_range.center() + side * 0.5, state.q_range.center() - side * 0.5}); } void Constellation_Diagram_Control::publish() { publish_properties(); impl_->runtime.publish(); } void Constellation_Diagram_Control::paint(Painter& painter) { const auto state = render_properties(); const auto runtime = impl_->runtime.render_use_state(); const Axis_Transform x = impl_->i_axis->transform(); const Axis_Transform y = impl_->q_axis->transform(); const int count = static_cast(state.type); const double radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4; for(int index = 0; index < count; ++index) { const double angle = state.phase_offset_radians + 2.0 * std::numbers::pi * index / count; const PointF point{state.i_range.center() + std::cos(angle) * radius, state.q_range.center() + std::sin(angle) * radius}; painter.circle({x.coord_to_pixel(point.x), y.coord_to_pixel(point.y)}, 3.0, Pen{state.anchor_color}, Brush{state.anchor_color, Brush_Style::Solid}); } const auto cutoff = std::chrono::steady_clock::now() - std::chrono::milliseconds(state.point_lifetime_ms.get()); for(const auto& value : runtime.points) { if(value.time < cutoff) continue; painter.circle({x.coord_to_pixel(value.point.x), y.coord_to_pixel(value.point.y)}, 2.0, Pen{state.point_color}, Brush{state.point_color, Brush_Style::Solid}); } } } }