This commit is contained in:
2026-08-21 15:12:22 +08:00
parent d2fa9b4ee0
commit b280e2f5ee
5 changed files with 403 additions and 64 deletions
+24 -1
View File
@@ -18,7 +18,30 @@ struct Graph_WebSocket::Private {
Graph_WebSocket::Graph_WebSocket(drogon::WebSocketConnectionPtr connection, std::shared_ptr<Plot> plot) : d(std::make_unique<Private>()) { d->connection = connection; d->plot = std::move(plot); d->owner = connection.get(); }
Graph_WebSocket::~Graph_WebSocket() { close(); }
void Graph_WebSocket::start() { if (d->attached) return; const auto weak = weak_from_this(); d->plot->attach(d->owner, [weak](std::string pixels) { const auto socket = weak.lock(); if (!socket) return; const auto connection = socket->d->connection.lock(); if (connection && connection->connected()) connection->send(pixels.data(), pixels.size(), drogon::WebSocketMessageType::Binary); }); d->attached = true; }
void Graph_WebSocket::receive(std::string_view message) { const auto json = nlohmann::json::parse(message, nullptr, false); if (json.is_discarded() || !json.is_object()) return; Plot_Event event; if (const auto value = json.find("time"); value != json.end() && value->is_number()) event.time_milliseconds = value->get<double>(); if (const auto value = json.find("width"); value != json.end() && value->is_number_unsigned()) event.width = std::clamp(value->get<std::uint32_t>(), 160U, 1920U); if (const auto value = json.find("height"); value != json.end() && value->is_number_unsigned()) event.height = std::clamp(value->get<std::uint32_t>(), 120U, 1080U); d->plot->submit(event); }
void Graph_WebSocket::receive(std::string_view message) {
const auto json = nlohmann::json::parse(message, nullptr, false);
if (json.is_discarded() || !json.is_object()) return;
Plot_Event event;
if (const auto value = json.find("time"); value != json.end() && value->is_number())
event.time_milliseconds = value->get<double>();
if (const auto value = json.find("width"); value != json.end() && value->is_number_unsigned())
event.width = std::clamp(value->get<std::uint32_t>(), 160U, 1920U);
if (const auto value = json.find("height"); value != json.end() && value->is_number_unsigned())
event.height = std::clamp(value->get<std::uint32_t>(), 120U, 1080U);
if (const auto value = json.find("wheel"); value != json.end() && value->is_object()) {
const auto x = value->find("x");
const auto y = value->find("y");
const auto delta_y = value->find("delta_y");
if (x != value->end() && x->is_number() && y != value->end() && y->is_number()
&& delta_y != value->end() && delta_y->is_number()) {
event.wheel = Plot_Wheel_Event{
std::clamp(x->get<double>(), 0.0, static_cast<double>(event.width)),
std::clamp(y->get<double>(), 0.0, static_cast<double>(event.height)),
std::clamp(delta_y->get<double>(), -120.0, 120.0)};
}
}
d->plot->submit(event);
}
void Graph_WebSocket::close() { if (!d->attached) return; d->plot->detach(d->owner); d->attached = false; }
Graph_WebSocket_Controller::Graph_WebSocket_Controller(Plot_Resolver resolver) : resolve_plot(std::move(resolver)) {}
+40 -6
View File
@@ -14,6 +14,7 @@
#include <cmath>
#include <cstring>
#include <mutex>
#include <numbers>
#include <stdexcept>
#include <tuple>
#include <unordered_map>
@@ -172,7 +173,21 @@ Time_Axis_Object::Builder time_axis_builder(
template <typename... Axes>
void resize_axes(Size viewport, Axes*... axes) {
(axes->template set<&Abs_Axis::Prop::canvas_size>(viewport), ...);
const double left = std::clamp(static_cast<double>(viewport.width) * 0.10, 42.0, 68.0);
const double right = std::clamp(static_cast<double>(viewport.width) * 0.05, 18.0, 34.0);
const double top = std::clamp(static_cast<double>(viewport.height) * 0.07, 16.0, 30.0);
const double bottom = std::clamp(static_cast<double>(viewport.height) * 0.13, 36.0, 52.0);
const Point_F origin{left, static_cast<double>(viewport.height) - bottom};
const Axis_Pixel_Length horizontal_length = std::max(1.0, static_cast<double>(viewport.width) - left - right);
const Axis_Pixel_Length vertical_length = -std::max(1.0, static_cast<double>(viewport.height) - top - bottom);
const auto resize_axis = [&](auto* axis) {
const auto orientation = axis->template read_prop<Abs_Axis::Base_Tag>().orientation;
axis->template set<&Abs_Axis::Prop::canvas_size>(viewport);
axis->template set<&Abs_Axis::Prop::position>(origin);
axis->template set<&Abs_Axis::Prop::pixel_length>(
orientation == Axis_Orientation::horizontal ? horizontal_length : vertical_length);
};
(resize_axis(axes), ...);
}
}
@@ -361,14 +376,16 @@ std::shared_ptr<Plot> make_waterfall_plot(asio::any_io_executor executor) {
.set(&Render_Scene_2D::Prop::view_active, true);
scene_builder.add_renderable(waterfall.get());
auto scene = *scene_builder.build();
auto update = [raw = waterfall.get(), frequency = frequency.get(), time = time.get(), tick = std::uint64_t{}](const Plot_Event& event) mutable {
auto update = [raw = waterfall.get(), frequency = frequency.get(), time = time.get()](const Plot_Event& event) {
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, time);
std::array<double, 192> values{};
for (std::size_t i = 0; i < values.size(); ++i)
values[i] = -100.0 + 70.0 * std::exp(-240.0 * std::pow(
static_cast<double>(i) / values.size() - 0.5
- 0.22 * std::sin(event.time_milliseconds * 0.0006), 2.0));
raw->append_row(tick++, values);
constexpr double day_milliseconds = 86'400'000.0;
raw->append_row(Time_Of_Day{static_cast<std::int64_t>(
std::fmod(std::max(0.0, event.time_milliseconds), day_milliseconds))}, values);
};
auto view = make_scene_view<
Prop_Field<&Waterfall::Prop::tooltip_enabled, "tooltip_enabled", "Enables value inspection tooltips over waterfall cells.">,
@@ -409,9 +426,20 @@ std::shared_ptr<Plot> make_constellation_plot(asio::any_io_executor executor) {
auto scene = *scene_builder.build();
auto update = [raw = constellation.get(), horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Event& event) {
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, horizontal, vertical);
const double phase = event.time_milliseconds * 0.003;
raw->append_point({std::cos(phase) * 0.82 + 0.04 * std::sin(phase * 7.0),
std::sin(phase) * 0.82 + 0.04 * std::cos(phase * 5.0)});
const auto& state = raw->template read_prop<Constellation_Diagram::Base_Tag>();
const int anchor_count = static_cast<int>(state.type);
const double radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4;
const double phase = event.time_milliseconds * 0.001;
for (int index = 0; index < anchor_count; ++index) {
const double angle = state.phase_offset_radians
+ 2.0 * std::numbers::pi * static_cast<double>(index) / anchor_count;
const double noise_i = 0.025 * std::sin(phase * 11.0 + index * 1.73)
+ 0.012 * std::cos(phase * 23.0 + index * 0.61);
const double noise_q = 0.025 * std::cos(phase * 13.0 + index * 1.37)
+ 0.012 * std::sin(phase * 19.0 + index * 0.47);
raw->append_point({state.i_range.center() + std::cos(angle) * radius + noise_i,
state.q_range.center() + std::sin(angle) * radius + noise_q});
}
};
auto view = make_scene_view<
Prop_Field<&Constellation_Diagram::Prop::point_lifetime_ms, "point_lifetime_ms", "Time in milliseconds that an appended constellation point remains visible.">,
@@ -538,6 +566,12 @@ void Plot::ensure_started() {
if (auto* scene = std::get_if<std::unique_ptr<Scene_2D>>(&self->d->scene)) {
(*scene)->set<&Render_Scene_2D::Prop::viewport>(
Size{static_cast<int>(event.width), static_cast<int>(event.height)});
if (event.wheel) {
Wheel_Event wheel;
wheel.position = {event.wheel->position_x, event.wheel->position_y};
wheel.angle_delta_y = event.wheel->delta_y;
(*scene)->dispatch_event(wheel);
}
(*scene)->render();
} else {
auto& scene_3d = std::get<std::unique_ptr<Scene_3D>>(self->d->scene);
+7
View File
@@ -6,13 +6,20 @@
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
namespace aethera::web {
struct Plot_Wheel_Event {
double position_x{}; /* Pointer position in the rendered viewport, in physical pixels. */
double position_y{}; /* Pointer position in the rendered viewport, in physical pixels. */
double delta_y{}; /* Normalized vertical wheel angle delta. */
};
struct Plot_Event {
double time_milliseconds{};
std::uint32_t width{720};
std::uint32_t height{420};
std::optional<Plot_Wheel_Event> wheel{}; /* Canvas interaction attached to this render request. */
};
class Plot final : public std::enable_shared_from_this<Plot> {
public: