Preparations for waterfall display
This commit is contained in:
parent
754ded1d08
commit
a7fcaf7d97
@ -3,6 +3,7 @@
|
|||||||
#include "ui_tilewidget.h"
|
#include "ui_tilewidget.h"
|
||||||
#include "Traces/tracexyplot.h"
|
#include "Traces/tracexyplot.h"
|
||||||
#include "Traces/tracesmithchart.h"
|
#include "Traces/tracesmithchart.h"
|
||||||
|
#include "Traces/tracewaterfall.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
@ -67,6 +68,9 @@ nlohmann::json TileWidget::toJSON()
|
|||||||
case TracePlot::Type::XYPlot:
|
case TracePlot::Type::XYPlot:
|
||||||
plotname = "XY-plot";
|
plotname = "XY-plot";
|
||||||
break;
|
break;
|
||||||
|
case TracePlot::Type::Waterfall:
|
||||||
|
plotname = "Waterfall";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
j["plot"] = plotname;
|
j["plot"] = plotname;
|
||||||
j["plotsettings"] = content->toJSON();
|
j["plotsettings"] = content->toJSON();
|
||||||
@ -93,8 +97,10 @@ void TileWidget::fromJSON(nlohmann::json j)
|
|||||||
auto plotname = j["plot"];
|
auto plotname = j["plot"];
|
||||||
if(plotname == "smithchart") {
|
if(plotname == "smithchart") {
|
||||||
content = new TraceSmithChart(model);
|
content = new TraceSmithChart(model);
|
||||||
} else {
|
} else if (plotname == "XY-plot"){
|
||||||
content = new TraceXYPlot(model);
|
content = new TraceXYPlot(model);
|
||||||
|
} else {
|
||||||
|
content = new TraceWaterfall(model);
|
||||||
}
|
}
|
||||||
setContent(content);
|
setContent(content);
|
||||||
content->fromJSON(j["plotsettings"]);
|
content->fromJSON(j["plotsettings"]);
|
||||||
@ -264,3 +270,9 @@ void TileWidget::traceDeleted(TracePlot *)
|
|||||||
hasContent = false;
|
hasContent = false;
|
||||||
content = nullptr;
|
content = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TileWidget::on_bWaterfall_clicked()
|
||||||
|
{
|
||||||
|
setContent(new TraceWaterfall(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,8 @@ private slots:
|
|||||||
void on_plotDoubleClicked();
|
void on_plotDoubleClicked();
|
||||||
void traceDeleted(TracePlot *t);
|
void traceDeleted(TracePlot *t);
|
||||||
|
|
||||||
|
void on_bWaterfall_clicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TileWidget(TraceModel &model, TileWidget &parent);
|
TileWidget(TraceModel &model, TileWidget &parent);
|
||||||
void split();
|
void split();
|
||||||
|
@ -85,6 +85,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="bWaterfall">
|
||||||
|
<property name="text">
|
||||||
|
<string>Waterfall</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -98,6 +98,7 @@ HEADERS += \
|
|||||||
Traces/traceplot.h \
|
Traces/traceplot.h \
|
||||||
Traces/tracesmithchart.h \
|
Traces/tracesmithchart.h \
|
||||||
Traces/tracetouchstoneexport.h \
|
Traces/tracetouchstoneexport.h \
|
||||||
|
Traces/tracewaterfall.h \
|
||||||
Traces/tracewidget.h \
|
Traces/tracewidget.h \
|
||||||
Traces/tracexyplot.h \
|
Traces/tracexyplot.h \
|
||||||
Traces/xyplotaxisdialog.h \
|
Traces/xyplotaxisdialog.h \
|
||||||
@ -215,6 +216,7 @@ SOURCES += \
|
|||||||
Traces/traceplot.cpp \
|
Traces/traceplot.cpp \
|
||||||
Traces/tracesmithchart.cpp \
|
Traces/tracesmithchart.cpp \
|
||||||
Traces/tracetouchstoneexport.cpp \
|
Traces/tracetouchstoneexport.cpp \
|
||||||
|
Traces/tracewaterfall.cpp \
|
||||||
Traces/tracewidget.cpp \
|
Traces/tracewidget.cpp \
|
||||||
Traces/tracexyplot.cpp \
|
Traces/tracexyplot.cpp \
|
||||||
Traces/xyplotaxisdialog.cpp \
|
Traces/xyplotaxisdialog.cpp \
|
||||||
|
@ -155,7 +155,7 @@ signals:
|
|||||||
void typeChanged(Trace *t);
|
void typeChanged(Trace *t);
|
||||||
void deleted(Trace *t);
|
void deleted(Trace *t);
|
||||||
void visibilityChanged(Trace *t);
|
void visibilityChanged(Trace *t);
|
||||||
void dataChanged();
|
void dataChanged(unsigned int begin, unsigned int end);
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
void pauseChanged();
|
void pauseChanged();
|
||||||
void colorChanged(Trace *t);
|
void colorChanged(Trace *t);
|
||||||
|
@ -356,6 +356,11 @@ void TracePlot::createMarkerAtPosition(QPoint p)
|
|||||||
markerModel->addMarker(marker);
|
markerModel->addMarker(marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TracePlot::dropSupported(Trace *t)
|
||||||
|
{
|
||||||
|
return supported(t);
|
||||||
|
}
|
||||||
|
|
||||||
void TracePlot::dragEnterEvent(QDragEnterEvent *event)
|
void TracePlot::dragEnterEvent(QDragEnterEvent *event)
|
||||||
{
|
{
|
||||||
if (event->mimeData()->hasFormat("trace/pointer")) {
|
if (event->mimeData()->hasFormat("trace/pointer")) {
|
||||||
@ -391,6 +396,15 @@ void TracePlot::dragLeaveEvent(QDragLeaveEvent *event)
|
|||||||
replot();
|
replot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TracePlot::traceDropped(Trace *t, QPoint position)
|
||||||
|
{
|
||||||
|
Q_UNUSED(t)
|
||||||
|
Q_UNUSED(position);
|
||||||
|
if(supported(t)) {
|
||||||
|
enableTrace(t, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::set<TracePlot *> TracePlot::getPlots()
|
std::set<TracePlot *> TracePlot::getPlots()
|
||||||
{
|
{
|
||||||
return plots;
|
return plots;
|
||||||
|
@ -17,6 +17,7 @@ public:
|
|||||||
enum class Type {
|
enum class Type {
|
||||||
SmithChart,
|
SmithChart,
|
||||||
XYPlot,
|
XYPlot,
|
||||||
|
Waterfall,
|
||||||
};
|
};
|
||||||
|
|
||||||
TracePlot(TraceModel &model, QWidget *parent = nullptr);
|
TracePlot(TraceModel &model, QWidget *parent = nullptr);
|
||||||
@ -71,11 +72,11 @@ protected:
|
|||||||
void createMarkerAtPosition(QPoint p);
|
void createMarkerAtPosition(QPoint p);
|
||||||
|
|
||||||
// handle trace drops
|
// handle trace drops
|
||||||
virtual bool dropSupported(Trace *t) = 0;
|
virtual bool dropSupported(Trace *t);
|
||||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||||
void dropEvent(QDropEvent *event) override;
|
void dropEvent(QDropEvent *event) override;
|
||||||
void dragLeaveEvent(QDragLeaveEvent *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();};
|
virtual QString mouseText(QPoint pos) {Q_UNUSED(pos) return QString();};
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
@ -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)
|
QString TraceSmithChart::mouseText(QPoint pos)
|
||||||
{
|
{
|
||||||
auto data = pixelToData(pos);
|
auto data = pixelToData(pos);
|
||||||
@ -485,11 +476,6 @@ void TraceSmithChart::updateContextMenu()
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TraceSmithChart::supported(Trace *t)
|
bool TraceSmithChart::supported(Trace *t)
|
||||||
{
|
|
||||||
return dropSupported(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TraceSmithChart::dropSupported(Trace *t)
|
|
||||||
{
|
{
|
||||||
if(!t->isReflection()) {
|
if(!t->isReflection()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -128,9 +128,7 @@ protected:
|
|||||||
//void paintEvent(QPaintEvent *event) override;
|
//void paintEvent(QPaintEvent *event) override;
|
||||||
virtual void updateContextMenu() override;
|
virtual void updateContextMenu() override;
|
||||||
bool supported(Trace *t) override;
|
bool supported(Trace *t) override;
|
||||||
bool dropSupported(Trace *t) override;
|
|
||||||
virtual void draw(QPainter& painter) override;
|
virtual void draw(QPainter& painter) override;
|
||||||
virtual void traceDropped(Trace *t, QPoint position) override;
|
|
||||||
QString mouseText(QPoint pos) override;
|
QString mouseText(QPoint pos) override;
|
||||||
bool limitToSpan;
|
bool limitToSpan;
|
||||||
bool limitToEdge;
|
bool limitToEdge;
|
||||||
|
486
Software/PC_Application/Traces/tracewaterfall.cpp
Normal file
486
Software/PC_Application/Traces/tracewaterfall.cpp
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
#include "tracewaterfall.h"
|
||||||
|
|
||||||
|
#include "preferences.h"
|
||||||
|
#include "unit.h"
|
||||||
|
#include "Util/util.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
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<double>(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<double>& 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<double>::epsilon() && ticks.size() <= maxTicks;tick+= step) {
|
||||||
|
ticks.push_back(tick);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto createAutomaticTicks = [](vector<double>& 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<double>& 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<double>::lowest();
|
||||||
|
double min = std::numeric_limits<double>::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 "";
|
||||||
|
}
|
||||||
|
}
|
84
Software/PC_Application/Traces/tracewaterfall.h
Normal file
84
Software/PC_Application/Traces/tracewaterfall.h
Normal file
@ -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<double> ticks;
|
||||||
|
};
|
||||||
|
|
||||||
|
XAxis XAxis;
|
||||||
|
|
||||||
|
std::vector<std::vector<Trace::Data>> data;
|
||||||
|
unsigned int pixelsPerLine;
|
||||||
|
int plotAreaLeft, plotAreaWidth, plotAreaBottom, plotAreaTop;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TRACEWATERFALL_H
|
@ -92,7 +92,7 @@ private:
|
|||||||
QPointF pixelToPlotValue(QPoint pixel, int YAxis);
|
QPointF pixelToPlotValue(QPoint pixel, int YAxis);
|
||||||
QPoint markerToPixel(Marker *m) override;
|
QPoint markerToPixel(Marker *m) override;
|
||||||
double nearestTracePoint(Trace *t, QPoint pixel, double *distance = nullptr) 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;
|
void traceDropped(Trace *t, QPoint position) override;
|
||||||
QString mouseText(QPoint pos) override;
|
QString mouseText(QPoint pos) override;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user