From a7fcaf7d97d8e28f6247c3ed3c38ae1bae219047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Tue, 15 Mar 2022 16:59:52 +0100 Subject: [PATCH] Preparations for waterfall display --- .../CustomWidgets/tilewidget.cpp | 14 +- .../PC_Application/CustomWidgets/tilewidget.h | 2 + .../CustomWidgets/tilewidget.ui | 7 + Software/PC_Application/LibreVNA-GUI.pro | 2 + Software/PC_Application/Traces/trace.h | 2 +- Software/PC_Application/Traces/traceplot.cpp | 14 + Software/PC_Application/Traces/traceplot.h | 5 +- .../PC_Application/Traces/tracesmithchart.cpp | 14 - .../PC_Application/Traces/tracesmithchart.h | 2 - .../PC_Application/Traces/tracewaterfall.cpp | 486 ++++++++++++++++++ .../PC_Application/Traces/tracewaterfall.h | 84 +++ Software/PC_Application/Traces/tracexyplot.h | 2 +- 12 files changed, 613 insertions(+), 21 deletions(-) create mode 100644 Software/PC_Application/Traces/tracewaterfall.cpp create mode 100644 Software/PC_Application/Traces/tracewaterfall.h diff --git a/Software/PC_Application/CustomWidgets/tilewidget.cpp b/Software/PC_Application/CustomWidgets/tilewidget.cpp index 880bdbe..2348a8a 100644 --- a/Software/PC_Application/CustomWidgets/tilewidget.cpp +++ b/Software/PC_Application/CustomWidgets/tilewidget.cpp @@ -3,6 +3,7 @@ #include "ui_tilewidget.h" #include "Traces/tracexyplot.h" #include "Traces/tracesmithchart.h" +#include "Traces/tracewaterfall.h" #include @@ -67,6 +68,9 @@ nlohmann::json TileWidget::toJSON() case TracePlot::Type::XYPlot: plotname = "XY-plot"; break; + case TracePlot::Type::Waterfall: + plotname = "Waterfall"; + break; } j["plot"] = plotname; j["plotsettings"] = content->toJSON(); @@ -93,8 +97,10 @@ void TileWidget::fromJSON(nlohmann::json j) auto plotname = j["plot"]; if(plotname == "smithchart") { content = new TraceSmithChart(model); - } else { + } else if (plotname == "XY-plot"){ content = new TraceXYPlot(model); + } else { + content = new TraceWaterfall(model); } setContent(content); content->fromJSON(j["plotsettings"]); @@ -264,3 +270,9 @@ void TileWidget::traceDeleted(TracePlot *) hasContent = false; content = nullptr; } + +void TileWidget::on_bWaterfall_clicked() +{ + setContent(new TraceWaterfall(model)); +} + diff --git a/Software/PC_Application/CustomWidgets/tilewidget.h b/Software/PC_Application/CustomWidgets/tilewidget.h index cf29f23..a4136a9 100644 --- a/Software/PC_Application/CustomWidgets/tilewidget.h +++ b/Software/PC_Application/CustomWidgets/tilewidget.h @@ -40,6 +40,8 @@ private slots: void on_plotDoubleClicked(); void traceDeleted(TracePlot *t); + void on_bWaterfall_clicked(); + private: TileWidget(TraceModel &model, TileWidget &parent); void split(); diff --git a/Software/PC_Application/CustomWidgets/tilewidget.ui b/Software/PC_Application/CustomWidgets/tilewidget.ui index 2219e23..57d3292 100644 --- a/Software/PC_Application/CustomWidgets/tilewidget.ui +++ b/Software/PC_Application/CustomWidgets/tilewidget.ui @@ -85,6 +85,13 @@ + + + + Waterfall + + + diff --git a/Software/PC_Application/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI.pro index 7ac0c4f..799b252 100644 --- a/Software/PC_Application/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI.pro @@ -98,6 +98,7 @@ HEADERS += \ Traces/traceplot.h \ Traces/tracesmithchart.h \ Traces/tracetouchstoneexport.h \ + Traces/tracewaterfall.h \ Traces/tracewidget.h \ Traces/tracexyplot.h \ Traces/xyplotaxisdialog.h \ @@ -215,6 +216,7 @@ SOURCES += \ Traces/traceplot.cpp \ Traces/tracesmithchart.cpp \ Traces/tracetouchstoneexport.cpp \ + Traces/tracewaterfall.cpp \ Traces/tracewidget.cpp \ Traces/tracexyplot.cpp \ Traces/xyplotaxisdialog.cpp \ diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index 676c10c..e6cd72a 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -155,7 +155,7 @@ signals: void typeChanged(Trace *t); void deleted(Trace *t); void visibilityChanged(Trace *t); - void dataChanged(); + void dataChanged(unsigned int begin, unsigned int end); void nameChanged(); void pauseChanged(); void colorChanged(Trace *t); diff --git a/Software/PC_Application/Traces/traceplot.cpp b/Software/PC_Application/Traces/traceplot.cpp index add37bb..c7c9942 100644 --- a/Software/PC_Application/Traces/traceplot.cpp +++ b/Software/PC_Application/Traces/traceplot.cpp @@ -356,6 +356,11 @@ void TracePlot::createMarkerAtPosition(QPoint p) markerModel->addMarker(marker); } +bool TracePlot::dropSupported(Trace *t) +{ + return supported(t); +} + void TracePlot::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat("trace/pointer")) { @@ -391,6 +396,15 @@ void TracePlot::dragLeaveEvent(QDragLeaveEvent *event) replot(); } +void TracePlot::traceDropped(Trace *t, QPoint position) +{ + Q_UNUSED(t) + Q_UNUSED(position); + if(supported(t)) { + enableTrace(t, true); + } +} + std::set TracePlot::getPlots() { return plots; diff --git a/Software/PC_Application/Traces/traceplot.h b/Software/PC_Application/Traces/traceplot.h index 1c0de54..d134bcc 100644 --- a/Software/PC_Application/Traces/traceplot.h +++ b/Software/PC_Application/Traces/traceplot.h @@ -17,6 +17,7 @@ public: enum class Type { SmithChart, XYPlot, + Waterfall, }; TracePlot(TraceModel &model, QWidget *parent = nullptr); @@ -71,11 +72,11 @@ protected: void createMarkerAtPosition(QPoint p); // handle trace drops - virtual bool dropSupported(Trace *t) = 0; + virtual bool dropSupported(Trace *t); void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; void dragLeaveEvent(QDragLeaveEvent *event) override; - virtual void traceDropped(Trace *t, QPoint position){ Q_UNUSED(t) Q_UNUSED(position)}; + virtual void traceDropped(Trace *t, QPoint position); virtual QString mouseText(QPoint pos) {Q_UNUSED(pos) return QString();}; protected slots: diff --git a/Software/PC_Application/Traces/tracesmithchart.cpp b/Software/PC_Application/Traces/tracesmithchart.cpp index 5c0e204..6ad0359 100644 --- a/Software/PC_Application/Traces/tracesmithchart.cpp +++ b/Software/PC_Application/Traces/tracesmithchart.cpp @@ -396,15 +396,6 @@ void TraceSmithChart::draw(QPainter &p) { } } -void TraceSmithChart::traceDropped(Trace *t, QPoint position) -{ - Q_UNUSED(t) - Q_UNUSED(position); - if(supported(t)) { - enableTrace(t, true); - } -} - QString TraceSmithChart::mouseText(QPoint pos) { auto data = pixelToData(pos); @@ -485,11 +476,6 @@ void TraceSmithChart::updateContextMenu() } bool TraceSmithChart::supported(Trace *t) -{ - return dropSupported(t); -} - -bool TraceSmithChart::dropSupported(Trace *t) { if(!t->isReflection()) { return false; diff --git a/Software/PC_Application/Traces/tracesmithchart.h b/Software/PC_Application/Traces/tracesmithchart.h index 178e269..08b0b85 100644 --- a/Software/PC_Application/Traces/tracesmithchart.h +++ b/Software/PC_Application/Traces/tracesmithchart.h @@ -128,9 +128,7 @@ protected: //void paintEvent(QPaintEvent *event) override; virtual void updateContextMenu() override; bool supported(Trace *t) override; - bool dropSupported(Trace *t) override; virtual void draw(QPainter& painter) override; - virtual void traceDropped(Trace *t, QPoint position) override; QString mouseText(QPoint pos) override; bool limitToSpan; bool limitToEdge; diff --git a/Software/PC_Application/Traces/tracewaterfall.cpp b/Software/PC_Application/Traces/tracewaterfall.cpp new file mode 100644 index 0000000..8d95f20 --- /dev/null +++ b/Software/PC_Application/Traces/tracewaterfall.cpp @@ -0,0 +1,486 @@ +#include "tracewaterfall.h" + +#include "preferences.h" +#include "unit.h" +#include "Util/util.h" + +#include +#include + +using namespace std; + +TraceWaterfall::TraceWaterfall(TraceModel &model, QWidget *parent) + : TracePlot(model, parent), + pixelsPerLine(1) +{ + initializeTraceInfo(); +} + +void TraceWaterfall::enableTrace(Trace *t, bool enabled) +{ + if(enabled) { + // only one trace at a time is allowed, disable all others + for(auto t : traces) { + if(t.second) { + TracePlot::enableTrace(t.first, false); + } + } + } + TracePlot::enableTrace(t, enabled); + resetWaterfall(); +} + +void TraceWaterfall::updateSpan(double min, double max) +{ + TracePlot::updateSpan(min, max); + updateAxisTicks(); + resetWaterfall(); +} + +void TraceWaterfall::replot() +{ + if(XAxis.mode != XAxisMode::Manual) { + updateAxisTicks(); + } + TracePlot::replot(); +} + +void TraceWaterfall::fromJSON(nlohmann::json j) +{ + resetWaterfall(); + pixelsPerLine = j.value("pixelsPerLine", 1); + for(unsigned int hash : j["traces"]) { + // attempt to find the traces with this hash + bool found = false; + for(auto t : model.getTraces()) { + if(t->toHash() == hash) { + enableTrace(t, true); + found = true; + break; + } + } + if(!found) { + qWarning() << "Unable to find trace with hash" << hash; + } + } +} + +nlohmann::json TraceWaterfall::toJSON() +{ + nlohmann::json j; + j["pixelsPerLine"] = pixelsPerLine; + nlohmann::json jtraces; + for(auto t : traces) { + if(t.second) { + jtraces.push_back(t.first->toHash()); + } + } + j["traces"] = jtraces; + return j; +} + +void TraceWaterfall::setXAxis(XAxisType type, XAxisMode mode, bool log, double min, double max, double div) +{ + XAxis.type = type; + XAxis.mode = mode; + XAxis.log = log; + XAxis.rangeMin = min; + XAxis.rangeMax = max; + XAxis.rangeDiv = div; + traceRemovalPending = true; + updateAxisTicks(); + updateContextMenu(); + replot(); +} + +void TraceWaterfall::axisSetupDialog() +{ + // TODO + +} + +void TraceWaterfall::resetWaterfall() +{ + data.clear(); +} + +bool TraceWaterfall::configureForTrace(Trace *t) +{ + // TODO + return true; +} + +void TraceWaterfall::updateContextMenu() +{ + contextmenu->clear(); + auto setup = new QAction("Setup...", contextmenu); + connect(setup, &QAction::triggered, this, &TraceWaterfall::axisSetupDialog); + contextmenu->addAction(setup); + + contextmenu->addSeparator(); + auto image = new QAction("Save image...", contextmenu); + contextmenu->addAction(image); + connect(image, &QAction::triggered, [=]() { + auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, QFileDialog::DontUseNativeDialog); + if(filename.isEmpty()) { + // aborted selection + return; + } + if(filename.endsWith(".png")) { + filename.chop(4); + } + filename += ".png"; + grab().save(filename); + }); + + contextmenu->addSection("Traces"); + // Populate context menu + for(auto t : traces) { + if(!supported(t.first)) { + continue; + } + auto action = new QAction(t.first->name(), contextmenu); + action->setCheckable(true); + if(t.second) { + action->setChecked(true); + } + connect(action, &QAction::toggled, [=](bool active) { + enableTrace(t.first, active); + }); + contextmenu->addAction(action); + } + + contextmenu->addSeparator(); + auto close = new QAction("Close", contextmenu); + contextmenu->addAction(close); + connect(close, &QAction::triggered, [=]() { + markedForDeletion = true; + }); +} + +void TraceWaterfall::draw(QPainter &p) +{ + auto pref = Preferences::getInstance(); + + constexpr int yAxisSpace = 55; + constexpr int yAxisDisabledSpace = 10; + constexpr int xAxisSpace = 30; + auto w = p.window(); + auto pen = QPen(pref.Graphs.Color.axis, 0); + pen.setCosmetic(true); + p.setPen(pen); + plotAreaLeft = yAxisDisabledSpace; + plotAreaWidth = w.width() - 2 * yAxisDisabledSpace; + plotAreaTop = 10; + plotAreaBottom = w.height() - xAxisSpace; + + auto plotRect = QRect(plotAreaLeft, plotAreaTop, plotAreaWidth + 1, plotAreaBottom-plotAreaTop); + p.drawRect(plotRect); + + // draw axis types + auto font = p.font(); + font.setPixelSize(AxisLabelSize); + p.setFont(font); + p.drawText(QRect(0, w.height()-AxisLabelSize*1.5, w.width(), AxisLabelSize*1.5), Qt::AlignHCenter, AxisTypeToName(XAxis.type)); + + if(XAxis.ticks.size() >= 1) { + // draw X ticks + int significantDigits; + bool displayFullFreq; + if(XAxis.log) { + significantDigits = 5; + displayFullFreq = true; + } else { + // this only works for evenly distributed ticks: + auto max = qMax(abs(XAxis.ticks.front()), abs(XAxis.ticks.back())); + double step; + if(XAxis.ticks.size() >= 2) { + step = abs(XAxis.ticks[0] - XAxis.ticks[1]); + } else { + // only one tick, set arbitrary number of digits + step = max / 1000; + } + significantDigits = floor(log10(max)) - floor(log10(step)) + 1; + displayFullFreq = significantDigits <= 5; + } + constexpr int displayLastDigits = 4; + QString prefixes = "fpnum kMG"; + QString unit = ""; + if(pref.Graphs.showUnits) { + unit = AxisUnit(XAxis.type); + } + QString commonPrefix = QString(); + if(!displayFullFreq) { + auto fullFreq = Unit::ToString(XAxis.ticks.front(), unit, prefixes, significantDigits); + commonPrefix = fullFreq.at(fullFreq.size() - 1); + auto front = fullFreq; + front.truncate(fullFreq.size() - displayLastDigits - unit.length()); + auto back = fullFreq; + back.remove(0, front.size()); + back.append(".."); + p.setPen(QPen(QColor("orange"))); + QRect bounding; + p.drawText(QRect(2, plotAreaBottom + AxisLabelSize + 5, w.width(), AxisLabelSize), 0, front, &bounding); + p.setPen(pref.Graphs.Color.axis); + p.drawText(QRect(bounding.x() + bounding.width(), plotAreaBottom + AxisLabelSize + 5, w.width(), AxisLabelSize), 0, back); + } + + int lastTickLabelEnd = 0; + for(auto t : XAxis.ticks) { + auto xCoord = Util::Scale(t, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth, XAxis.log); + p.setPen(QPen(pref.Graphs.Color.axis, 1)); + p.drawLine(xCoord, plotAreaBottom, xCoord, plotAreaBottom + 2); + if(xCoord != plotAreaLeft && xCoord != plotAreaLeft + plotAreaWidth) { + p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine)); + p.drawLine(xCoord, plotAreaTop, xCoord, plotAreaBottom); + } + if(xCoord - 40 <= lastTickLabelEnd) { + // would overlap previous tick label, skip + continue; + } + auto tickValue = Unit::ToString(t, unit, prefixes, significantDigits); + p.setPen(QPen(pref.Graphs.Color.axis, 1)); + if(displayFullFreq) { + QRect bounding; + p.drawText(QRect(xCoord - 40, plotAreaBottom + 5, 80, AxisLabelSize), Qt::AlignHCenter, tickValue, &bounding); + lastTickLabelEnd = bounding.x() + bounding.width(); + } else { + // check if the same prefix was used as in the fullFreq string + if(tickValue.at(tickValue.size() - 1) != commonPrefix) { + // prefix changed, we reached the next order of magnitude. Force same prefix as in fullFreq and add extra digit + tickValue = Unit::ToString(t, "", commonPrefix, significantDigits + 1); + } + + tickValue.remove(0, tickValue.size() - displayLastDigits - unit.length()); + QRect bounding; + p.drawText(QRect(xCoord - 40, plotAreaBottom + 5, 80, AxisLabelSize), Qt::AlignHCenter, tickValue, &bounding); + lastTickLabelEnd = bounding.x() + bounding.width(); + p.setPen(QPen(QColor("orange"))); + p.drawText(QRect(0, plotAreaBottom + 5, bounding.x() - 1, AxisLabelSize), Qt::AlignRight, "..", &bounding); + } + } + } + + if(dropPending) { + p.setOpacity(0.5); + p.setBrush(Qt::white); + p.setPen(Qt::white); + // show drop area over whole plot + p.drawRect(plotRect); + auto font = p.font(); + font.setPixelSize(20); + p.setFont(font); + p.setOpacity(1.0); + p.setPen(Qt::white); + auto text = "Drop here to add\n" + dropTrace->name() + "\nto waterfall plot"; + p.drawText(plotRect, Qt::AlignCenter, text); + } +} + +bool TraceWaterfall::supported(Trace *t) +{ + // TODO + return true; +} + +double TraceWaterfall::nearestTracePoint(Trace *t, QPoint pixel, double *distance) +{ + // TODO + return 0; +} + +QString TraceWaterfall::mouseText(QPoint pos) +{ + // TODO + return "Test"; +} + +bool TraceWaterfall::xCoordinateVisible(double x) +{ + // TODO + return true; +} + +void TraceWaterfall::updateAxisTicks() +{ + auto createEvenlySpacedTicks = [](vector& ticks, double start, double stop, double step) { + ticks.clear(); + if(start > stop) { + swap(start, stop); + } + step = abs(step); + constexpr unsigned int maxTicks = 100; + for(double tick = start; tick - stop < numeric_limits::epsilon() && ticks.size() <= maxTicks;tick+= step) { + ticks.push_back(tick); + } + }; + + auto createAutomaticTicks = [](vector& ticks, double start, double stop, int minDivisions) -> double { + Q_ASSERT(stop > start); + ticks.clear(); + double max_div_step = (stop - start) / minDivisions; + int zeros = floor(log10(max_div_step)); + double decimals_shift = pow(10, zeros); + max_div_step /= decimals_shift; + if(max_div_step >= 5) { + max_div_step = 5; + } else if(max_div_step >= 2) { + max_div_step = 2; + } else { + max_div_step = 1; + } + auto div_step = max_div_step * decimals_shift; + // round min up to next multiple of div_step + auto start_div = ceil(start / div_step) * div_step; + for(double tick = start_div;tick <= stop;tick += div_step) { + ticks.push_back(tick); + } + return div_step; + }; + + auto createLogarithmicTicks = [](vector& ticks, double start, double stop, int minDivisions) { + // enforce usable log settings + if(start <= 0) { + start = 1.0; + } + if(stop <= start) { + stop = start + 1.0; + } + ticks.clear(); + + auto decades = log10(stop) - log10(start); + double max_div_decade = minDivisions / decades; + int zeros = floor(log10(max_div_decade)); + double decimals_shift = pow(10, zeros); + max_div_decade /= decimals_shift; + if(max_div_decade < 2) { + max_div_decade = 2; + } else if(max_div_decade < 5) { + max_div_decade = 5; + } else { + max_div_decade = 10; + } + auto step = pow(10, floor(log10(start))+1) / (max_div_decade * decimals_shift); + // round min up to next multiple of div_step + auto div = ceil(start / step) * step; + if(floor(log10(div)) != floor(log10(start))) { + // first div is already at the next decade + step *= 10; + } + do { + ticks.push_back(div); + if(ticks.size() > 1 && div != step && floor(log10(div)) != floor(log10(div - step))) { + // reached a new decade with this switch + step *= 10; + div = step; + } else { + div += step; + } + } while(div <= stop); + }; + + if(XAxis.mode == XAxisMode::Manual) { + if(XAxis.log) { + createLogarithmicTicks(XAxis.ticks, XAxis.rangeMin, XAxis.rangeMax, 20); + } else { + createEvenlySpacedTicks(XAxis.ticks, XAxis.rangeMin, XAxis.rangeMax, XAxis.rangeDiv); + } + } else { + XAxis.ticks.clear(); + // automatic mode, figure out limits + double max = std::numeric_limits::lowest(); + double min = std::numeric_limits::max(); + if(XAxis.mode == XAxisMode::UseSpan) { + min = sweep_fmin; + max = sweep_fmax; + } else if(XAxis.mode == XAxisMode::FitTraces) { + for(auto t : traces) { + bool enabled = t.second; + auto trace = t.first; + if(enabled && trace->isVisible()) { + if(!trace->size()) { + // empty trace, do not use for automatic axis calculation + continue; + } + // this trace is currently displayed + double trace_min = trace->minX(); + double trace_max = trace->maxX(); + if(XAxis.type == XAxisType::Distance) { + trace_min = trace->timeToDistance(trace_min); + trace_max = trace->timeToDistance(trace_max); + } + if(trace_min < min) { + min = trace_min; + } + if(trace_max > max) { + max = trace_max; + } + } + } + } + if(min < max) { + // found min/max values + XAxis.rangeMin = min; + XAxis.rangeMax = max; + if(XAxis.log) { + createLogarithmicTicks(XAxis.ticks, XAxis.rangeMin, XAxis.rangeMax, 20); + } else { + XAxis.rangeDiv = createAutomaticTicks(XAxis.ticks, min, max, 8); + } + } + } +} + +QString TraceWaterfall::AxisTypeToName(TraceWaterfall::XAxisType type) +{ + switch(type) { + case XAxisType::Frequency: return "Frequency"; + case XAxisType::Time: return "Time"; + case XAxisType::Distance: return "Distance"; + case XAxisType::Power: return "Power"; + default: return "Unknown"; + } +} + +QString TraceWaterfall::AxisModeToName(TraceWaterfall::XAxisMode mode) +{ + switch(mode) { + case XAxisMode::Manual: return "Manual"; break; + case XAxisMode::FitTraces: return "Fit Traces"; break; + case XAxisMode::UseSpan: return "Use Span"; break; + default: return "Unknown"; + } +} + +TraceWaterfall::XAxisType TraceWaterfall::XAxisTypeFromName(QString name) +{ + for(unsigned int i=0;i<(int) XAxisType::Last;i++) { + if(AxisTypeToName((XAxisType) i) == name) { + return (XAxisType) i; + } + } + // not found, use default + return XAxisType::Frequency; +} + +TraceWaterfall::XAxisMode TraceWaterfall::AxisModeFromName(QString name) +{ + for(unsigned int i=0;i<(int) XAxisMode::Last;i++) { + if(AxisModeToName((XAxisMode) i) == name) { + return (XAxisMode) i; + } + } + // not found, use default + return XAxisMode::UseSpan; +} + +QString TraceWaterfall::AxisUnit(XAxisType type) +{ + switch(type) { + case XAxisType::Frequency: return "Hz"; + case XAxisType::Time: return "s"; + case XAxisType::Distance: return "m"; + case XAxisType::Power: return "dBm"; + default: return ""; + } +} diff --git a/Software/PC_Application/Traces/tracewaterfall.h b/Software/PC_Application/Traces/tracewaterfall.h new file mode 100644 index 0000000..c058447 --- /dev/null +++ b/Software/PC_Application/Traces/tracewaterfall.h @@ -0,0 +1,84 @@ +#ifndef TRACEWATERFALL_H +#define TRACEWATERFALL_H + +#include "traceplot.h" + +class TraceWaterfall : public TracePlot +{ + Q_OBJECT +public: + TraceWaterfall(TraceModel &model, QWidget *parent = 0);; + + virtual void enableTrace(Trace *t, bool enabled) override; + + void updateSpan(double min, double max) override; + void replot() override; + virtual Type getType() override { return Type::Waterfall;}; + + void fromJSON(nlohmann::json j) override; + nlohmann::json toJSON() override; + + enum class XAxisType { + Frequency, + Time, + Distance, + Power, + Last, + }; + enum class XAxisMode { + UseSpan, + FitTraces, + Manual, + Last, + }; + + void setXAxis(XAxisType type, XAxisMode mode, bool log, double min, double max, double div); + + +public slots: + void axisSetupDialog(); + void resetWaterfall(); + +protected: + virtual bool configureForTrace(Trace *t) override; + virtual void updateContextMenu() override; + virtual void draw(QPainter& p) override; + virtual bool supported(Trace *t) override; + + virtual QPoint markerToPixel(Marker *m) override { Q_UNUSED(m) return QPoint(0,0);}; + virtual double nearestTracePoint(Trace *t, QPoint pixel, double *distance = nullptr) override; + + virtual QString mouseText(QPoint pos) override; + +protected slots: + virtual bool xCoordinateVisible(double x) override; +private slots: + void updateAxisTicks(); +private: + static constexpr int AxisLabelSize = 10; + static QString AxisTypeToName(XAxisType type); + static QString AxisModeToName(XAxisMode mode); + static XAxisType XAxisTypeFromName(QString name); + static XAxisMode AxisModeFromName(QString name); + + static QString AxisUnit(XAxisType type); + + class XAxis { + public: + XAxisType type; + XAxisMode mode; + bool log; + double rangeMin; + double rangeMax; + double rangeDiv; + std::vector ticks; + }; + + XAxis XAxis; + + std::vector> data; + unsigned int pixelsPerLine; + int plotAreaLeft, plotAreaWidth, plotAreaBottom, plotAreaTop; +}; + +#endif // TRACEWATERFALL_H diff --git a/Software/PC_Application/Traces/tracexyplot.h b/Software/PC_Application/Traces/tracexyplot.h index 86ce3c6..82840b3 100644 --- a/Software/PC_Application/Traces/tracexyplot.h +++ b/Software/PC_Application/Traces/tracexyplot.h @@ -92,7 +92,7 @@ private: QPointF pixelToPlotValue(QPoint pixel, int YAxis); QPoint markerToPixel(Marker *m) override; double nearestTracePoint(Trace *t, QPoint pixel, double *distance = nullptr) override; - virtual bool xCoordinateVisible(double x); + virtual bool xCoordinateVisible(double x) override; void traceDropped(Trace *t, QPoint position) override; QString mouseText(QPoint pos) override;