#include "Waterfall.h" #include "Heatmap_Utils.h" #include "Plottable_Real_Time_Data.h" #include #include #include #include namespace renderive { namespace detail { namespace { struct Waterfall_Row { int tick{}; std::vector values; }; struct Waterfall_Interaction_Base {}; struct Waterfall_Interaction { Hover_Tooltip_Runtime tooltip; }; using Waterfall_History = Plottable_History_Real_Time_Data>; using Waterfall_Interaction_State = Double_State_Strategy; } struct Waterfall_Control::Impl { Impl(Waterfall_Control& owner, std::shared_ptr frequency, std::shared_ptr time) : frequency_axis(std::move(frequency)), time_axis(std::move(time)), rows(observe_real_time_data(owner)) {} std::shared_ptr frequency_axis; std::shared_ptr time_axis; Waterfall_History rows; Waterfall_Interaction_State interaction; }; Waterfall_Control::Waterfall_Control(Plot_Core& plot, const Waterfall_Properties& properties, std::shared_ptr frequency_axis, std::shared_ptr time_axis) : Plottable_State(plot, properties), impl_(std::make_unique(*this, std::move(frequency_axis), std::move(time_axis))) {} Waterfall_Control::~Waterfall_Control() = default; void Waterfall_Control::append_row(int tick, std::span values) { const std::size_t limit = static_cast(std::max(2, impl_->time_axis->visible_time_point_count())); if(get<&Waterfall_Properties::frequency_bin_count>() <= 0) set<&Waterfall_Properties::frequency_bin_count>(static_cast(values.size())); impl_->rows.update({tick, {values.begin(), values.end()}}, limit); changed(); } void Waterfall_Control::append_row(int tick, std::pmr::vector&& values) { append_row(tick, std::span(values.data(), values.size())); } void Waterfall_Control::append_row(Time_Of_Day time, std::span values) { append_row(impl_->time_axis->append_time(time), values); } void Waterfall_Control::append_row(Time_Of_Day time, std::pmr::vector&& values) { append_row(time, std::span(values.data(), values.size())); } std::size_t Waterfall_Control::row_count() const { return impl_->rows.size(); } std::size_t Waterfall_Control::stored_point_count() const { const auto rows = impl_->rows.snapshot(); std::size_t count{}; for(const auto& row : rows) count += row.values.size(); return count; } std::size_t Waterfall_Control::rendered_cell_count() const { const auto state = properties(); const auto rows = impl_->rows.snapshot(); if(rows.empty()) return 0; const int source_width = std::min(state.frequency_bin_count.get(), static_cast(std::min_element(rows.begin(), rows.end(), [](const auto& left, const auto& right) { return left.values.size() < right.values.size(); })->values.size())); if(source_width <= 0) return 0; const auto columns = frequency_columns(state.frequency_range, impl_->frequency_axis->coord_range(), source_width, state.visible_range_only); return columns ? static_cast(columns->last - columns->first + 1) * rows.size() : 0; } void Waterfall_Control::handle_event(const Event& event) { bool updated{}; impl_->interaction.update([&](Waterfall_Interaction& interaction) { updated = update_hover_tooltip(interaction.tooltip, event); }); if(updated) changed(); } void Waterfall_Control::publish() { publish_properties(); impl_->interaction.publish(); } void Waterfall_Control::paint(Painter& painter) { const auto state = render_properties(); const auto rows = impl_->rows.snapshot(); const auto interaction = impl_->interaction.render_use_state(); if(rows.empty()) return; const int source_width = std::min(state.frequency_bin_count.get(), static_cast(std::min_element(rows.begin(), rows.end(), [](const auto& left, const auto& right) { return left.values.size() < right.values.size(); })->values.size())); const int height = static_cast(rows.size()); if(source_width <= 0 || height <= 0) return; const Axis_Transform horizontal = impl_->frequency_axis->transform(); const auto columns = frequency_columns(state.frequency_range, horizontal.coordinate_range, source_width, state.visible_range_only); if(!columns) return; const int width = columns->last - columns->first + 1; std::vector pixels(static_cast(width) * height); for(int y = 0; y < height; ++y) { const auto& row = rows[static_cast(y)].values; for(int x = 0; x < width; ++x) pixels[static_cast(y) * width + x] = state.color_map.at_normalized(normalized_value(row[static_cast(columns->first + x)], state.power_range)); } const Axis_Transform vertical = impl_->time_axis->transform(); const Range time_range{static_cast(rows.front().tick), static_cast(rows.back().tick)}; RectF target = mapped_rect(horizontal, vertical, columns->range, time_range); if(target.height < 1.0) target.height = std::max(1.0, static_cast(impl_->time_axis->pixel_length())); painter.heatmap(target, width, height, pixels, state.interpolation_mode); if(state.tooltip_enabled && interaction.tooltip.active && target.contains(interaction.tooltip.position)) { const double frequency = horizontal.pixel_to_coord(interaction.tooltip.position.x); std::ostringstream text; text << std::fixed << std::setprecision(2) << frequency << " Hz"; const RectF box{interaction.tooltip.position.x + 8.0, interaction.tooltip.position.y + 8.0, 110.0, 24.0}; painter.rect(box, Pen{state.tooltip_text_pen.color}, state.tooltip_background_brush); painter.text({box.x + 4.0, box.y + 3.0}, text.str(), state.tooltip_font, state.tooltip_text_pen); } } } }