Files
MARTe_IO_Components/Client/NativeUI-Qt/src/plotwidget.h
T
2026-05-27 15:46:50 +02:00

134 lines
4.9 KiB
C++

#pragma once
#include "source.h"
#include "signal_buffer.h"
#include <QColor>
#include <QPair>
#include <QWidget>
#include <memory>
#include <vector>
#include "qcustomplot.h"
/**
* A single plot panel backed by QCustomPlot.
*
* Rendering pipeline per trace each timer tick:
* 1. select ring buffer or archive depending on window coverage
* 2. read_window_decimated → kRawPts (uniform stride)
* 3. lttb(kRawPts → kDisplayPts) — visually optimal 3000-point line
*
* Zoom behaviour:
* - Live mode: rolling window updated each tick UNLESS userZoomed_ is set.
* - Zoomed (userZoomed_): axis stays at user range; re-queries buffer with
* that range → zooming in gives higher resolution from the ring buffer.
* - "Reset Zoom" context menu clears userZoomed_ and returns to live mode.
*
* x-range adapts to actual available data so the plot never shows empty space
* on the left when the signal has just started.
*/
class PlotWidget : public QWidget {
Q_OBJECT
public:
explicit PlotWidget(int plotId, QWidget* parent = nullptr);
int plotId() const { return plotId_; }
// ── Signal management ─────────────────────────────────────────────
void addSignal(std::shared_ptr<Source> src, int sigIdx);
void removeSignal(std::shared_ptr<Source> src, int sigIdx);
bool hasSignal(const std::shared_ptr<Source>& src, int sigIdx) const;
/** For saving/restoring traces across layout changes. */
struct SignalId { std::shared_ptr<Source> src; int sigIdx; };
std::vector<SignalId> getSignals() const;
// ── View control (called from MainWindow timer) ───────────────────
void refresh(double viewTMax, double windowSec, bool paused);
/** Force the visible range (e.g. when a trigger fires). */
void setViewRange(double tMin, double tMax);
/** Return to live rolling window. */
void resetZoom();
/** Apply a specific x range (for zoom-back; suppresses history emission). */
void applyZoomRange(double lo, double hi);
/** Fit Y axis to currently visible data. */
void fitYAxis();
/** Current x axis range. */
QPair<double,double> currentXRange() const;
/** Sync X range from another plot (cross-plot zoom). */
void syncXRange(double lo, double hi);
/** Push current cursor state to display (called from MainWindow). */
void refreshCursors(double tA, double tB, bool aActive, bool bActive);
signals:
void signalDropped(int srcIdx, int sigIdx);
void xRangeChanged(double lo, double hi); // emitted on user zoom/pan
void cursorAPlaced(double t); // emitted on Ctrl+Left click
void cursorBPlaced(double t); // emitted on Ctrl+Right click
protected:
void dragEnterEvent(QDragEnterEvent* e) override;
void dropEvent(QDropEvent* e) override;
bool eventFilter(QObject* obj, QEvent* ev) override;
private slots:
void onLegendDoubleClick(QCPLegend* legend, QCPAbstractLegendItem* item);
void onContextMenuRequest(QPoint pos);
private:
struct Trace {
std::shared_ptr<Source> src;
int sigIdx;
QCPGraph* graphMin = nullptr;
QCPGraph* graphMax = nullptr;
QCPGraph* graphLine = nullptr;
size_t lastHead = 0;
double lastTMin = 0.0;
double lastTMax = 0.0;
QColor color; // persistent user color (invalid = use palette)
qreal lineWidth = 1.5;
};
void applyDarkTheme();
void rebuildTrace(Trace& tr);
void updateCursorItems(); // update QCPItemLine positions + visibility
int plotId_;
QCustomPlot* qcp_ = nullptr;
std::vector<Trace> traces_;
// Zoom / sync state
bool userZoomed_ = false;
bool zoomGuard_ = false;
bool syncGuard_ = false; // prevent re-entrant xRangeChanged during syncXRange
// A/B cursor items (owned by qcp_, positions set via refreshCursors)
QCPItemLine* cursorA_ = nullptr;
QCPItemLine* cursorB_ = nullptr;
double cursorATime_ = 0.0;
double cursorBTime_ = 0.0;
bool cursorAActive_ = false;
bool cursorBActive_ = false;
// Rendering pipeline scratch buffers
// kFullResPts: full-res read budget (light path). Must be >= 4*kMaxBuckets so
// the light-path threshold never exceeds what read_window can return.
static constexpr size_t kFullResPts = 65'536;
// kRawPts: stride-decimated read budget (heavy path, >kModerateThreshold samples)
static constexpr size_t kRawPts = 8'192;
// kMaxBuckets: number of min-max buckets (moderate path + LTTB output)
static constexpr size_t kMaxBuckets = 4'096;
std::vector<double> tmpT_, tmpV_; // full-res / stride-decimated intermediary
std::vector<double> tmpMinV_, tmpMaxV_; // min-max decimation intermediary
std::vector<double> outT_, outV_; // LTTB output (resized as needed)
};