119 lines
6.0 KiB
C++
119 lines
6.0 KiB
C++
#include "Waterfall.h"
|
|
#include "Heatmap_Utils.h"
|
|
#include <algorithm>
|
|
#include <deque>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
namespace renderive {
|
|
namespace detail {
|
|
namespace {
|
|
struct Waterfall_Row {
|
|
int tick{};
|
|
std::vector<double> values;
|
|
};
|
|
struct Waterfall_Runtime_Base {};
|
|
struct Waterfall_Runtime {
|
|
std::deque<Waterfall_Row> rows;
|
|
Hover_Tooltip_Runtime tooltip;
|
|
};
|
|
using Waterfall_Runtime_State = Double_State_Strategy<Waterfall_Runtime_Base, Waterfall_Runtime>;
|
|
}
|
|
struct Waterfall_Control::Impl {
|
|
Impl(std::shared_ptr<Frequency_Axis> frequency, std::shared_ptr<Time_Axis> time) : frequency_axis(std::move(frequency)), time_axis(std::move(time)) {}
|
|
std::shared_ptr<Frequency_Axis> frequency_axis;
|
|
std::shared_ptr<Time_Axis> time_axis;
|
|
Waterfall_Runtime_State runtime;
|
|
};
|
|
Waterfall_Control::Waterfall_Control(Plot_Core& plot, const Waterfall_Properties& properties, std::shared_ptr<Frequency_Axis> frequency_axis, std::shared_ptr<Time_Axis> time_axis)
|
|
: Plottable_State(plot, properties), impl_(std::make_unique<Impl>(std::move(frequency_axis), std::move(time_axis))) {}
|
|
Waterfall_Control::~Waterfall_Control() = default;
|
|
void Waterfall_Control::append_row(int tick, std::span<const double> values) {
|
|
const std::size_t limit = static_cast<std::size_t>(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<int>(values.size()));
|
|
impl_->runtime.update([tick, values, limit](Waterfall_Runtime& runtime) {
|
|
runtime.rows.push_back({tick, {values.begin(), values.end()}});
|
|
while(runtime.rows.size() > limit)
|
|
runtime.rows.pop_front();
|
|
});
|
|
changed();
|
|
}
|
|
void Waterfall_Control::append_row(int tick, std::pmr::vector<double>&& values) {
|
|
append_row(tick, std::span<const double>(values.data(), values.size()));
|
|
}
|
|
void Waterfall_Control::append_row(Time_Of_Day time, std::span<const double> values) {
|
|
append_row(impl_->time_axis->append_time(time), values);
|
|
}
|
|
void Waterfall_Control::append_row(Time_Of_Day time, std::pmr::vector<double>&& values) {
|
|
append_row(time, std::span<const double>(values.data(), values.size()));
|
|
}
|
|
std::size_t Waterfall_Control::row_count() const {
|
|
return impl_->runtime.read([](const Waterfall_Runtime& runtime) { return runtime.rows.size(); });
|
|
}
|
|
std::size_t Waterfall_Control::stored_point_count() const {
|
|
return impl_->runtime.read([](const Waterfall_Runtime& runtime) {
|
|
std::size_t count{};
|
|
for(const auto& row : runtime.rows)
|
|
count += row.values.size();
|
|
return count;
|
|
});
|
|
}
|
|
std::size_t Waterfall_Control::rendered_cell_count() const {
|
|
const auto state = properties();
|
|
const auto runtime = impl_->runtime.read([](const Waterfall_Runtime& value) { return value; });
|
|
if(runtime.rows.empty())
|
|
return 0;
|
|
const int source_width = std::min(state.frequency_bin_count.get(), static_cast<int>(std::min_element(runtime.rows.begin(), runtime.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<std::size_t>(columns->last - columns->first + 1) * runtime.rows.size() : 0;
|
|
}
|
|
void Waterfall_Control::handle_event(const Event& event) {
|
|
bool updated{};
|
|
impl_->runtime.update([&](Waterfall_Runtime& runtime) { updated = update_hover_tooltip(runtime.tooltip, event); });
|
|
if(updated)
|
|
changed();
|
|
}
|
|
void Waterfall_Control::publish() {
|
|
publish_properties();
|
|
impl_->runtime.publish();
|
|
}
|
|
void Waterfall_Control::paint(Painter& painter) {
|
|
const auto state = render_properties();
|
|
const auto runtime = impl_->runtime.render_use_state();
|
|
if(runtime.rows.empty())
|
|
return;
|
|
const int source_width = std::min(state.frequency_bin_count.get(), static_cast<int>(std::min_element(runtime.rows.begin(), runtime.rows.end(), [](const auto& left, const auto& right) { return left.values.size() < right.values.size(); })->values.size()));
|
|
const int height = static_cast<int>(runtime.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<Pixel> pixels(static_cast<std::size_t>(width) * height);
|
|
for(int y = 0; y < height; ++y) {
|
|
const auto& row = runtime.rows[static_cast<std::size_t>(y)].values;
|
|
for(int x = 0; x < width; ++x)
|
|
pixels[static_cast<std::size_t>(y) * width + x] = state.color_map.at_normalized(normalized_value(row[static_cast<std::size_t>(columns->first + x)], state.power_range));
|
|
}
|
|
const Axis_Transform vertical = impl_->time_axis->transform();
|
|
const Range time_range{static_cast<double>(runtime.rows.front().tick), static_cast<double>(runtime.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<double>(impl_->time_axis->pixel_length()));
|
|
painter.heatmap(target, width, height, pixels, state.interpolation_mode);
|
|
if(state.tooltip_enabled && runtime.tooltip.active && target.contains(runtime.tooltip.position)) {
|
|
const double frequency = horizontal.pixel_to_coord(runtime.tooltip.position.x);
|
|
std::ostringstream text;
|
|
text << std::fixed << std::setprecision(2) << frequency << " Hz";
|
|
const RectF box{runtime.tooltip.position.x + 8.0, runtime.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);
|
|
}
|
|
}
|
|
}
|
|
}
|