#include "Constellation_Diagram.h" #include "Plottable_Real_Time_Data.h" #include "Heatmap_Utils.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; }; using Constellation_History = Plottable_History_Real_Time_Data>; } struct Constellation_Diagram_Control::Impl { struct Prepare_Buffer { std::vector anchors; std::vector points; }; Impl(Constellation_Diagram_Control& owner, std::shared_ptr i, std::shared_ptr q) : i_axis(std::move(i)), q_axis(std::move(q)), points(owner) {} std::shared_ptr i_axis; std::shared_ptr q_axis; Constellation_History points; Prepare_Buffer prepare_buffer; }; Constellation_Diagram_Control::Constellation_Diagram_Control(::Scene_Base& scene, const Constellation_Diagram_Properties& properties, std::shared_ptr i_axis, std::shared_ptr q_axis) : Plottable_State(scene, properties), impl_(std::make_unique(*this, 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>(); const auto cutoff = now - std::chrono::milliseconds(lifetime); const auto cutoff_ns = std::chrono::duration_cast(cutoff.time_since_epoch()).count(); impl_->points.update({point, now}); impl_->points.discard_before_time_ns(static_cast(cutoff_ns)); changed(); } std::size_t Constellation_Diagram_Control::point_count() const { return impl_->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<&Axis_Properties::coordinates>( Range{state.i_range.center() - side * 0.5, state.i_range.center() + side * 0.5}); impl_->q_axis->set<&Axis_Properties::coordinates>( Range{state.q_range.center() + side * 0.5, state.q_range.center() - side * 0.5}); } void Constellation_Diagram_Control::publish() { publish_properties(); } void Constellation_Diagram_Control::prepare_frame( const Prepare_Render_Context& context) { const auto& view = context.frame.render_state; const auto& state = render_properties(view); const auto& points = view.get(impl_->points); const Axis_Transform x = impl_->i_axis->transform(view); const Axis_Transform y = impl_->q_axis->transform(view); auto& output = impl_->prepare_buffer; output = {}; const int count = static_cast(state.type); const double radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4; output.anchors.reserve(static_cast(count)); 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}; output.anchors.push_back(mapped_point(x, point.x, y, point.y)); } const auto cutoff = std::chrono::steady_clock::now() - std::chrono::milliseconds(state.point_lifetime_ms.get()); output.points.reserve(points.size()); for (const auto& value : points) { if (value.time < cutoff) continue; output.points.push_back(mapped_point(x, value.point.x, y, value.point.y)); } } void Constellation_Diagram_Control::paint(Painter& painter, const Paint_Render_Context& context) { const auto& output = impl_->prepare_buffer; const auto& view = context.frame.render_state; const auto& state = render_properties(view); for (const PointF point : output.anchors) painter.circle(point, 3.0, Pen{state.anchor_color}, Brush{state.anchor_color, Brush_Style::Solid}); for (const PointF point : output.points) painter.circle(point, 2.0, Pen{state.point_color}, Brush{state.point_color, Brush_Style::Solid}); } } }