From ee3c6274adde892fbee7c2570ff5576e5aa41524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Fri, 21 Oct 2022 16:50:04 +0200 Subject: [PATCH] Move eye diagram from tools to new graph type, enable zoom/pan on graphs --- .../LibreVNA-GUI/CustomWidgets/tilewidget.cpp | 13 + .../LibreVNA-GUI/CustomWidgets/tilewidget.h | 3 +- .../LibreVNA-GUI/CustomWidgets/tilewidget.ui | 7 + .../LibreVNA-GUI/LibreVNA-GUI.pro | 6 +- .../LibreVNA-GUI/Tools/eyediagramdialog.cpp | 656 ------------ .../LibreVNA-GUI/Tools/eyediagramdialog.h | 82 -- .../LibreVNA-GUI/Tools/eyediagramdialog.ui | 285 ------ .../LibreVNA-GUI/Traces/Math/tracemath.cpp | 2 +- .../Traces/eyediagrameditdialog.ui | 482 +++++++++ .../LibreVNA-GUI/Traces/eyediagramplot.cpp | 938 ++++++++++++++++++ .../LibreVNA-GUI/Traces/eyediagramplot.h | 103 ++ .../LibreVNA-GUI/Traces/traceplot.cpp | 33 + .../LibreVNA-GUI/Traces/traceplot.h | 8 + .../LibreVNA-GUI/Traces/tracepolar.h | 4 +- .../LibreVNA-GUI/Traces/tracesmithchart.h | 4 +- .../LibreVNA-GUI/Traces/tracewaterfall.cpp | 1 - .../LibreVNA-GUI/Traces/tracexyplot.cpp | 68 +- .../LibreVNA-GUI/Traces/tracexyplot.h | 5 + .../PC_Application/LibreVNA-GUI/VNA/vna.cpp | 12 - .../PC_Application/LibreVNA-GUI/VNA/vna.h | 1 - .../PC_Application/LibreVNA-GUI/preferences.h | 3 + .../PC_Application/LibreVNA-GUI/test.setup | 327 ++++++ .../LibreVNA-Test/LibreVNA-Test.pro | 6 +- 23 files changed, 1997 insertions(+), 1052 deletions(-) delete mode 100644 Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.cpp delete mode 100644 Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.h delete mode 100644 Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.ui create mode 100644 Software/PC_Application/LibreVNA-GUI/Traces/eyediagrameditdialog.ui create mode 100644 Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp create mode 100644 Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h create mode 100644 Software/PC_Application/LibreVNA-GUI/test.setup diff --git a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.cpp b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.cpp index 72aad68..e09756c 100644 --- a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.cpp +++ b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.cpp @@ -5,6 +5,7 @@ #include "Traces/tracesmithchart.h" #include "Traces/tracewaterfall.h" #include "Traces/tracepolarchart.h" +#include "Traces/eyediagramplot.h" #include @@ -76,6 +77,9 @@ nlohmann::json TileWidget::toJSON() case TracePlot::Type::PolarChart: plotname = "PolarChart"; break; + case TracePlot::Type::EyeDiagram: + plotname = "EyeDiagram"; + break; } j["plot"] = plotname; j["plotsettings"] = content->toJSON(); @@ -108,6 +112,8 @@ void TileWidget::fromJSON(nlohmann::json j) content = new TraceWaterfall(model); } else if (plotname == "PolarChart"){ content = new TracePolarChart(model); + } else if (plotname == "EyeDiagram"){ + content = new EyeDiagramPlot(model); } if(content) { setContent(content); @@ -337,3 +343,10 @@ void TileWidget::on_bPolarchart_clicked() { setContent(new TracePolarChart(model)); } + +void TileWidget::on_eyeDiagram_clicked() +{ + auto plot = new EyeDiagramPlot(model); + setContent(plot); + plot->axisSetupDialog(); +} diff --git a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.h b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.h index 3352647..4d93034 100644 --- a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.h +++ b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.h @@ -45,10 +45,11 @@ private slots: void on_bXYplot_clicked(); void on_plotDoubleClicked(); void plotDeleted(); - void on_bWaterfall_clicked(); void on_bPolarchart_clicked(); + void on_eyeDiagram_clicked(); + private: TileWidget(TraceModel &model, TileWidget &parent); void split(bool moveContentToSecondChild = false); diff --git a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.ui b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.ui index 0724cb8..aac22da 100644 --- a/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.ui +++ b/Software/PC_Application/LibreVNA-GUI/CustomWidgets/tilewidget.ui @@ -99,6 +99,13 @@ + + + + Eye Diagram + + + diff --git a/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro index d69d6d1..278f975 100644 --- a/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro @@ -33,7 +33,6 @@ HEADERS += \ SpectrumAnalyzer/spectrumanalyzer.h \ SpectrumAnalyzer/tracewidgetsa.h \ Tools/eseries.h \ - Tools/eyediagramdialog.h \ Tools/impedancematchdialog.h \ Tools/parameters.h \ Traces/Marker/marker.h \ @@ -95,6 +94,7 @@ HEADERS += \ Traces/Math/timegate.h \ Traces/Math/tracemath.h \ Traces/Math/windowfunction.h \ + Traces/eyediagramplot.h \ Traces/fftcomplex.h \ Traces/sparamtraceselector.h \ Traces/trace.h \ @@ -177,7 +177,6 @@ SOURCES += \ SpectrumAnalyzer/spectrumanalyzer.cpp \ SpectrumAnalyzer/tracewidgetsa.cpp \ Tools/eseries.cpp \ - Tools/eyediagramdialog.cpp \ Tools/impedancematchdialog.cpp \ Tools/parameters.cpp \ Traces/Marker/marker.cpp \ @@ -228,6 +227,7 @@ SOURCES += \ Traces/Math/timegate.cpp \ Traces/Math/tracemath.cpp \ Traces/Math/windowfunction.cpp \ + Traces/eyediagramplot.cpp \ Traces/fftcomplex.cpp \ Traces/sparamtraceselector.cpp \ Traces/trace.cpp \ @@ -305,7 +305,6 @@ FORMS += \ Device/firmwareupdatedialog.ui \ Device/manualcontroldialog.ui \ Generator/signalgenwidget.ui \ - Tools/eyediagramdialog.ui \ Tools/impedancematchdialog.ui \ Traces/Marker/markerwidget.ui \ Traces/Math/dftdialog.ui \ @@ -321,6 +320,7 @@ FORMS += \ Traces/Math/timegatedialog.ui \ Traces/Math/timegateexplanationwidget.ui \ Traces/XYPlotConstantLineEditDialog.ui \ + Traces/eyediagrameditdialog.ui \ Traces/smithchartdialog.ui \ Traces/polarchartdialog.ui \ Traces/tracecsvexport.ui \ diff --git a/Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.cpp b/Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.cpp deleted file mode 100644 index 113b19e..0000000 --- a/Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.cpp +++ /dev/null @@ -1,656 +0,0 @@ -#include "eyediagramdialog.h" -#include "ui_eyediagramdialog.h" -#include "Util/prbs.h" -#include "Traces/Math/tdr.h" -#include "Util/util.h" -#include "preferences.h" -#include "Traces/fftcomplex.h" -#include "Traces/traceaxis.h" -#include "unit.h" - -#include -#include -#include - -#include - -using namespace std::chrono_literals; - -EyeDiagramDialog::EyeDiagramDialog(TraceModel &model) : - QDialog(nullptr), - ui(new Ui::EyeDiagramDialog) -{ - ui->setupUi(this); - setAttribute(Qt::WA_DeleteOnClose); - - workingBuffer = &eyeBuffer[0]; - finishedBuffer = &eyeBuffer[1]; - - updating = false; - firstUpdate = true; - - trace = nullptr; - - tdr = new Math::TDR(); - - ui->update->setEnabled(false); - - ui->datarate->setUnit("bps"); - ui->datarate->setPrefixes(" kMG"); - ui->datarate->setPrecision(3); - - ui->risetime->setUnit("s"); - ui->risetime->setPrefixes("pnum "); - ui->risetime->setPrecision(3); - - ui->falltime->setUnit("s"); - ui->falltime->setPrefixes("pnum "); - ui->falltime->setPrecision(3); - - ui->highLevel->setUnit("V"); - ui->highLevel->setPrefixes("m "); - ui->highLevel->setPrecision(3); - - ui->lowLevel->setUnit("V"); - ui->lowLevel->setPrefixes("m "); - ui->lowLevel->setPrecision(3); - - ui->noise->setUnit("V"); - ui->noise->setPrefixes("um "); - ui->noise->setPrecision(3); - - ui->jitter->setUnit("s"); - ui->jitter->setPrefixes("pnum "); - ui->jitter->setPrecision(3); - - ui->datarate->setValue(100000000); - ui->risetime->setValue(0.000000001); - ui->falltime->setValue(0.000000001); - ui->highLevel->setValue(1); - ui->lowLevel->setValue(0); - ui->noise->setValue(0.01); - ui->jitter->setValue(0.0000000001); - - ui->displayedCycles->setValue(200); - - ui->widget->setDialog(this); - - connect(this, &EyeDiagramDialog::calculationStatus, ui->status, &QLabel::setText, Qt::QueuedConnection); - connect(ui->update, &QPushButton::clicked, this, &EyeDiagramDialog::triggerUpdate); - connect(this, &EyeDiagramDialog::updateDone, ui->widget, qOverload<>(&QWidget::update)); - - connect(ui->traceSelector, qOverload(&QComboBox::currentIndexChanged), [=](){ - trace = qvariant_cast(ui->traceSelector->itemData(ui->traceSelector->currentIndex())); - tdr->assignInput(trace); - ui->update->setEnabled(true); - }); - - connect(tdr, &Math::TDR::outputSamplesChanged, [=](){ - if(ui->updateOnTraceChange->isChecked() || firstUpdate) { - triggerUpdate(); - firstUpdate = false; - } - }); - - // find applicable traces - for(auto t : model.getTraces()) { - if(t->getDataType() != Trace::DataType::Frequency) { - // wrong domain - continue; - } - if(t->isReflection()) { - // can't work with reflection measurements - continue; - } - if(t->numSamples() < 100) { - // not enough samples - continue; - } - auto start = t->getSample(0).x; - auto stop = t->getSample(t->numSamples() - 1).x; - if(stop - start < start * 100) { - // span/start is not suitable for step response TDR - continue; - } - // can use this trace - ui->traceSelector->addItem(t->name(), QVariant::fromValue(t)); - } -} - -EyeDiagramDialog::~EyeDiagramDialog() -{ - while(updating) { - std::this_thread::sleep_for(20ms); - } - delete tdr; - delete ui; -} - -unsigned int EyeDiagramDialog::getCalculatedPixelsX() -{ - return finishedBuffer->size(); -} - -unsigned int EyeDiagramDialog::getCalculatedPixelsY() -{ - if(getCalculatedPixelsX() > 0) { - return (*finishedBuffer)[0].size(); - } else { - return 0; - } -} - -double EyeDiagramDialog::getIntensity(unsigned int x, unsigned int y) -{ - if(finishedBuffer->size() > x) { - if((*finishedBuffer)[x].size() > y) { - return (*finishedBuffer)[x][y]; - } - } - return std::numeric_limits::quiet_NaN(); -} - -double EyeDiagramDialog::displayedTime() -{ - return 2 * 1.0/ui->datarate->value(); -} - -double EyeDiagramDialog::minGraphVoltage() -{ - auto highlevel = ui->highLevel->value(); - auto lowlevel = ui->lowLevel->value(); - auto eyeRange = highlevel - lowlevel; - return lowlevel - eyeRange * yOverrange; -} - -double EyeDiagramDialog::maxGraphVoltage() -{ - auto highlevel = ui->highLevel->value(); - auto lowlevel = ui->lowLevel->value(); - auto eyeRange = highlevel - lowlevel; - return highlevel + eyeRange * yOverrange; -} - -bool EyeDiagramDialog::triggerUpdate() -{ - update(ui->widget->eyeWidth(), ui->widget->eyeHeight()); -} - -bool EyeDiagramDialog::update(unsigned int width, unsigned int height) -{ - if(updating) { - // already updating, can't start again - return false; - } - updating = true; - new std::thread(&EyeDiagramDialog::updateThread, this, width, height); -} - -void EyeDiagramDialog::updateThread(unsigned int width, unsigned int height) -{ - emit calculationStatus("Starting calculation..."); - if(!trace) { - emit calculationStatus("No trace assigned"); - updating = false; - return; - } - - qDebug() << "Starting eye diagram calculation"; - - auto datarate = ui->datarate->value(); - auto highlevel = ui->highLevel->value(); - auto lowlevel = ui->lowLevel->value(); - auto risetime = ui->risetime->value(); - auto falltime = ui->falltime->value(); - auto noise = ui->noise->value(); - auto jitter = ui->jitter->value(); - bool linearEdge = ui->fallrisetype->currentIndex() == 0; - unsigned int patternbits = ui->patternLength->currentIndex() + 2; - unsigned int cycles = ui->displayedCycles->value() + 1; // first cycle will not be displayed - - // sanity check values - if(datarate >= trace->getSample(trace->numSamples() - 1).x) { - emit calculationStatus("Data rate too high"); - updating = false; - return; - } - if(datarate <= 0) { - emit calculationStatus("Data rate too low"); - updating = false; - return; - } - if(risetime > 0.3 * 1.0 / datarate) { - emit calculationStatus("Rise time too high"); - updating = false; - return; - } - if(falltime > 0.3 * 1.0 / datarate) { - emit calculationStatus("Fall time too high"); - updating = false; - return; - } - if(jitter > 0.3 * 1.0 / datarate) { - emit calculationStatus("Jitter too high"); - updating = false; - return; - } - - qDebug() << "Eye calculation: input values okay"; - - // resize working buffer - workingBuffer->clear(); - workingBuffer->resize(width); - for(auto& y : *workingBuffer) { - y.resize(height, 0.0); - } - - emit calculationStatus("Generating PRBS sequence..."); - - // calculate timestep - double timestep = displayedTime() / (width); - - auto prbs = new PRBS(patternbits); - - bool currentBit = prbs->next(); - bool nextBit = prbs->next(); - - // initialize random generator - std::random_device rd1; - std::mt19937 mt_noise(rd1()); - std::normal_distribution<> dist_noise(0, noise); - - std::random_device rd2; - std::mt19937 mt_jitter(rd2()); - std::normal_distribution<> dist_jitter(0, jitter); - - // reserve vector for input data - std::vector> inVec(width * cycles, 0.0); - - unsigned int bitcnt = 1; - double transitionTime = -10; // assume that we start with a settled input, last transition was "long" ago - for(unsigned int i=0;i= transitionTime) { - // currently within a bit transition - double edgeTime = 0; - double expTimeConstant; - if(!currentBit && nextBit) { - edgeTime = risetime; - } else if(currentBit && !nextBit) { - edgeTime = falltime; - } - if(linearEdge) { - // edge is modeled as linear rise/fall - // increase slightly to account for typical 10/90% fall/rise time - edgeTime *= 1.25; - } else { - // edge is modeled as exponential rise/fall. Adjust time constant to match - // selected rise/fall time (with 10-90% signal rise/fall within specified time) - expTimeConstant = edgeTime / 2.197224577; - edgeTime = 6 * expTimeConstant; // after six time constants, 99.7% of signal movement has happened - } - if(time >= transitionTime + edgeTime) { - // bit transition settled - voltage = nextBit ? highlevel : lowlevel; - // move on to the next bit - currentBit = nextBit; - nextBit = prbs->next(); - transitionTime = bitcnt * 1.0 / datarate + dist_jitter(mt_jitter); - bitcnt++; - } else { - // still within rise or fall time - double timeSinceEdge = time - transitionTime; - double from = currentBit ? highlevel : lowlevel; - double to = nextBit ? highlevel : lowlevel; - if(linearEdge) { - double edgeRatio = timeSinceEdge / edgeTime; - voltage = from * (1.0 - edgeRatio) + to * edgeRatio; - } else { - voltage = from + (1.0 - exp(-timeSinceEdge/expTimeConstant)) * (to - from); - } - } - } else { - // still before the next edge - voltage = currentBit ? highlevel : lowlevel; - } - voltage += dist_noise(mt_noise); - inVec[i] = voltage; - } - - // input voltage vector fully assembled - qDebug() << "Eye calculation: input data generated"; - - emit calculationStatus("Extracting impulse response..."); - - // calculate impulse response of trace - double eyeTimeShift = 0; - std::vector> impulseVec; - // determine how long the impulse response is - auto samples = tdr->numSamples(); - if(samples == 0) { - // TDR calculation not yet done, unable to update - updating = false; - emit calculationStatus("No time-domain data from trace"); - return; - } - auto length = tdr->getSample(samples - 1).x; - - // determine average delay - auto total_step = tdr->getStepResponse(samples - 1); - for(unsigned int i=0;igetStepResponse(i); - if(abs(total_step - step) <= abs(step)) { - // mid point reached - eyeTimeShift = tdr->getSample(i).x; - break; - } - } - - unsigned long convolutedSize = length / timestep; - if(convolutedSize > inVec.size()) { - // impulse response is longer than what we display, truncate - convolutedSize = inVec.size(); - } - impulseVec.resize(convolutedSize); - /* - * we can't use the impulse response directly because we most likely need samples inbetween - * the calculated values. Interpolation is available but if our sample spacing here is much - * wider than the impulse response data, we might miss peaks (or severely miscalculate their - * amplitude. - * Instead, the step response is interpolated and the impulse response determined by deriving - * it from the interpolated step response data. As the step response is the integrated imulse - * response data, we can't miss narrow peaks that way. - */ - double lastStepResponse = 0.0; - for(unsigned long i=0;igetInterpolatedStepResponse(x); - impulseVec[i] = step - lastStepResponse; - lastStepResponse = step; - } - - eyeTimeShift += (risetime + falltime) * 1.25 / 4; - eyeTimeShift += 0.5 / datarate; - int eyeXshift = eyeTimeShift / timestep; - - qDebug() << "Eye calculation: TDR calculation done"; - - emit calculationStatus("Performing convolution..."); - - unsigned int highestIntensity = 0; - - qDebug() << "Convolve via FFT start"; - std::vector> outVec; - impulseVec.resize(inVec.size(), 0.0); - outVec.resize(inVec.size()); - Fft::convolve(inVec, impulseVec, outVec); - qDebug() << "Convolve via FFT stop"; - - auto addLine = [&](int x0, int y0, int x1, int y1, bool skipFirst = true) { - bool first = true; - auto putpixel = [&](int x, int y) { - if(skipFirst && first) { - first = false; - return; - } - if(x < 0 || x >= width || y < 0 || y >= height) { - return; - } - auto &bin = (*workingBuffer)[x][y]; - bin++; - if(bin > highestIntensity) { - highestIntensity = bin; - } - }; - - int dx = abs (x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs (y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; /* error value e_xy */ - - for (;;){ /* loop */ - putpixel (x0,y0); - if (x0 == x1 && y0 == y1) break; - e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */ - if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */ - } - }; - - emit calculationStatus("Creating intensity bitmap..."); - - // got the input data and the convolution data, calculate output - int lastyBin; - for(unsigned int i=width;i(voltage, minGraphVoltage(), maxGraphVoltage(), height-1, 0); - // increment pixel bin - if(yBin < 0) { - yBin = 0; - } else if(yBin >= height) { - yBin = height - 1; - } - - auto xlast = (i-1-eyeXshift)%width; - auto xnow = (i-eyeXshift)%width; - if(xnow > xlast && i > width) { - addLine(xlast, lastyBin, xnow, yBin, xlast > 0); - } - lastyBin = yBin; - } - - qDebug() << "Eye calculation: Convolution done"; - - // normalize intensity - for(auto &y : *workingBuffer) { - for(auto &v : y) { - v /= highestIntensity; - } - } - // switch buffers - auto buf = finishedBuffer; - finishedBuffer = workingBuffer; - workingBuffer = buf; - updating = false; - - emit calculationStatus("Eye calculation complete"); - emit updateDone(); -} - -EyeDiagramPlot::EyeDiagramPlot(QDialog *dialog) -{ - Q_UNUSED(dialog) -} - -void EyeDiagramPlot::setDialog(EyeDiagramDialog *dialog) -{ - this->dialog = dialog; -} - -unsigned int EyeDiagramPlot::eyeWidth() -{ - return width() - leftSpace() - rightSpace(); -} - -unsigned int EyeDiagramPlot::eyeHeight() -{ - return height() - topSpace() - bottomSpace(); -} - -unsigned int EyeDiagramPlot::leftSpace() -{ - auto &pref = Preferences::getInstance(); - return pref.Graphs.fontSizeAxis * 5.5; -} - -unsigned int EyeDiagramPlot::bottomSpace() -{ - auto &pref = Preferences::getInstance(); - return pref.Graphs.fontSizeAxis * 3; -} - -void EyeDiagramPlot::paintEvent(QPaintEvent *event) -{ - if(!dialog) { - return; - } - - auto &pref = Preferences::getInstance(); - int plotAreaLeft = leftSpace(); - int plotAreaWidth = width() - leftSpace() - rightSpace(); - int plotAreaTop = topSpace(); - int plotAreaHeight = height() - topSpace() - bottomSpace(); - - QPainter p(this); - p.setBackground(QBrush(pref.Graphs.Color.background)); - p.fillRect(0, 0, width(), height(), QBrush(pref.Graphs.Color.background)); - - auto pen = QPen(pref.Graphs.Color.axis, 0); - pen.setCosmetic(true); - p.setPen(pen); - auto plotRect = QRect(plotAreaLeft, plotAreaTop, plotAreaWidth + 1, plotAreaHeight + 1); - p.drawRect(plotRect); - - // Y axis - QString labelY = "Voltage"; - auto font = p.font(); - font.setPixelSize(pref.Graphs.fontSizeAxis); - p.setFont(font); - p.setPen(QPen(pref.Graphs.Color.axis, 1)); - p.save(); - p.translate(0, height()-bottomSpace()); - p.rotate(-90); - p.drawText(QRect(0, 0, height()-bottomSpace(), pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, labelY); - p.restore(); - - XAxis axis; - axis.set(XAxis::Type::Time, false, true, dialog->minGraphVoltage(), dialog->maxGraphVoltage(), 10); - // draw ticks - if(axis.getTicks().size() > 0) { - // this only works for evenly distributed ticks: - auto max = qMax(abs(axis.getTicks().front()), abs(axis.getTicks().back())); - double step; - if(axis.getTicks().size() >= 2) { - step = abs(axis.getTicks()[0] - axis.getTicks()[1]); - } else { - // only one tick, set arbitrary number of digits - step = max / 1000; - } - int significantDigits = floor(log10(max)) - floor(log10(step)) + 1; - - for(unsigned int j = 0; j < axis.getTicks().size(); j++) { - auto yCoord = axis.transform(axis.getTicks()[j], plotAreaTop + plotAreaHeight, plotAreaTop); - p.setPen(QPen(pref.Graphs.Color.axis, 1)); - // draw tickmark on axis - auto tickStart = plotAreaLeft; - auto tickLen = -2; - p.drawLine(tickStart, yCoord, tickStart + tickLen, yCoord); - QString unit = ""; - QString prefix = " "; - if(pref.Graphs.showUnits) { - unit = "V"; - prefix = "um "; - } - auto tickValue = Unit::ToString(axis.getTicks()[j], unit, prefix, significantDigits); - p.drawText(QRectF(0, yCoord - pref.Graphs.fontSizeAxis/2 - 2, tickStart + 2 * tickLen, pref.Graphs.fontSizeAxis), Qt::AlignRight, tickValue); - - // tick lines - if(yCoord == plotAreaTop || yCoord == plotAreaTop + plotAreaHeight) { - // skip tick lines right on the plot borders - continue; - } - // only draw tick lines for primary axis - if (pref.Graphs.Color.Ticks.Background.enabled) { - if (j%2) - { - int yCoordTop = axis.transform(axis.getTicks()[j], plotAreaTop, plotAreaTop + plotAreaHeight); - int yCoordBot = axis.transform(axis.getTicks()[j-1], plotAreaTop, plotAreaTop + plotAreaHeight); - if(yCoordTop > yCoordBot) { - auto buf = yCoordBot; - yCoordBot = yCoordTop; - yCoordTop = buf; - } - p.setBrush(pref.Graphs.Color.Ticks.Background.background); - p.setPen(pref.Graphs.Color.Ticks.Background.background); - auto rect = QRect(plotAreaLeft+1, yCoordTop+1, plotAreaWidth-2, yCoordBot-yCoordTop-2); - p.drawRect(rect); - } - } - p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine)); - p.drawLine(plotAreaLeft, yCoord, plotAreaLeft + plotAreaWidth, yCoord); - } - } - - // use the XY-plot axes for tick calculation - axis.set(XAxis::Type::Time, false, true, 0, dialog->displayedTime(), 10); - - // X axis name - p.drawText(QRect(plotAreaLeft, height()-pref.Graphs.fontSizeAxis*1.5, plotAreaWidth, pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, axis.TypeToName()); - - // draw X axis ticks - if(axis.getTicks().size() >= 1) { - // draw X ticks - int significantDigits; - // this only works for evenly distributed ticks: - auto max = qMax(abs(axis.getTicks().front()), abs(axis.getTicks().back())); - double step; - if(axis.getTicks().size() >= 2) { - step = abs(axis.getTicks()[0] - axis.getTicks()[1]); - } else { - // only one tick, set arbitrary number of digits - step = max / 1000; - } - significantDigits = floor(log10(max)) - floor(log10(step)) + 1; - QString prefixes = "fpnum kMG"; - QString unit = ""; - if(pref.Graphs.showUnits) { - unit = axis.Unit(); - } - QString commonPrefix = QString(); - int lastTickLabelEnd = 0; - for(auto t : axis.getTicks()) { - auto xCoord = axis.transform(t, plotAreaLeft, plotAreaLeft + plotAreaWidth); - p.setPen(QPen(pref.Graphs.Color.axis, 1)); - p.drawLine(xCoord, plotAreaTop + plotAreaHeight, xCoord, plotAreaTop + plotAreaHeight + 2); - if(xCoord != plotAreaLeft && xCoord != plotAreaLeft + plotAreaWidth) { - p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine)); - p.drawLine(xCoord, plotAreaTop, xCoord, plotAreaTop + plotAreaHeight); - } - 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)); - QRect bounding; - p.drawText(QRect(xCoord - pref.Graphs.fontSizeAxis*2, plotAreaTop + plotAreaHeight + 5, pref.Graphs.fontSizeAxis*4, - pref.Graphs.fontSizeAxis), Qt::AlignHCenter, tickValue, &bounding); - lastTickLabelEnd = bounding.x() + bounding.width(); - } - } - - if(dialog->getCalculatedPixelsX() == 0 || dialog->getCalculatedPixelsY() == 0) { - // no eye data - return; - } - - // eye data is normally calculated to match the displayed pixels in this widget. - // But the window size mighe have been adjusted since the last eye calculation. - // Use scale factors until the eye data is updated - double xScale = (double) plotAreaWidth / dialog->getCalculatedPixelsX(); - double yScale = (double) plotAreaHeight / dialog->getCalculatedPixelsY(); - - for(unsigned int i=0;igetIntensity(i / xScale, j / yScale); - if(isnan(value) || value == 0) { - // do not paint, just leave the background shining through - continue; - } - auto pen = QPen(Util::getIntensityGradeColor(value)); - pen.setCosmetic(true); - p.setPen(pen); - p.drawPoint(plotAreaLeft + i + 1, plotAreaTop + j + 1); - } - } -} diff --git a/Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.h b/Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.h deleted file mode 100644 index 55e1242..0000000 --- a/Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef EYEDIAGRAMDIALOG_H -#define EYEDIAGRAMDIALOG_H - -#include "Traces/tracemodel.h" -#include "Traces/Math/tdr.h" - -#include - -#include - -namespace Ui { -class EyeDiagramDialog; -} - -class EyeDiagramDialog; - -class EyeDiagramPlot : public QWidget -{ - Q_OBJECT -public: - EyeDiagramPlot(QDialog *dialog); - - void setDialog(EyeDiagramDialog *dialog); - - unsigned int eyeWidth(); - unsigned int eyeHeight(); -private: - unsigned int leftSpace(); - unsigned int rightSpace() {return 10;} - unsigned int topSpace() {return 10;} - unsigned int bottomSpace(); - void paintEvent(QPaintEvent *event) override; - - EyeDiagramDialog *dialog; -}; - -class EyeDiagramDialog : public QDialog -{ - Q_OBJECT - -public: - explicit EyeDiagramDialog(TraceModel &model); - ~EyeDiagramDialog(); - - unsigned int getCalculatedPixelsX(); - unsigned int getCalculatedPixelsY(); - double getIntensity(unsigned int x, unsigned int y); - - double displayedTime(); - double minGraphVoltage(); - double maxGraphVoltage(); - -public slots: - bool triggerUpdate(); - bool update(unsigned int width, unsigned int height); - -signals: - void updateDone(); - -private: -signals: - void calculationStatus(QString s); - -private: - static constexpr double yOverrange = 0.2; - void updateThread(unsigned int width, unsigned int height); - - Ui::EyeDiagramDialog *ui; - - Trace *trace; - - std::vector> eyeBuffer[2]; - std::vector> *workingBuffer; - std::vector> *finishedBuffer; - - Math::TDR *tdr; - - bool updating; - bool firstUpdate; -}; - -#endif // EYEDIAGRAMDIALOG_H diff --git a/Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.ui b/Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.ui deleted file mode 100644 index 14c356e..0000000 --- a/Software/PC_Application/LibreVNA-GUI/Tools/eyediagramdialog.ui +++ /dev/null @@ -1,285 +0,0 @@ - - - EyeDiagramDialog - - - - 0 - 0 - 897 - 575 - - - - Eye Diagram - - - true - - - - - - - - Trace Selection - - - - - - Transmission line trace: - - - - - - - - - - - - - Input Datastream Configuration - - - - - - Data rate: - - - - - - - - - - Rise time: - - - - - - - - - - Fall time: - - - - - - - - - - High level: - - - - - - - - - - Low level: - - - - - - - - - - Noise (RMS): - - - - - - - - - - Jitter (RMS): - - - - - - - - - - Pattern length: - - - - - - - 7 - - - - 3 - - - - - 7 - - - - - 15 - - - - - 31 - - - - - 63 - - - - - 127 - - - - - 255 - - - - - 511 - - - - - 1023 - - - - - 2047 - - - - - - - - Rise/Fall type: - - - - - - - - Linear - - - - - Exponential - - - - - - - - - - - Eye Calculation - - - - - - - - Displayed cycles: - - - - - - - 1 - - - 1000 - - - - - - - Update when trace changes: - - - - - - - - - - - - - - - - Update - - - - - - - - - - - - - - - - - - - - - - - SIUnitEdit - QLineEdit -
CustomWidgets/siunitedit.h
-
- - EyeDiagramPlot - QWidget -
Tools/eyediagramdialog.h
- 1 -
-
- - -
diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.cpp index 4760f40..443cc51 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.cpp @@ -121,7 +121,7 @@ double TraceMath::getInterpolatedStepResponse(double x) auto it = lower_bound(data.begin(), data.end(), x, [](const Data &lhs, const double x) -> bool { return lhs.x < x; }); - if(it->x == x) { + if(it->x == x || it == data.begin()) { ret = stepResponse[it - data.begin()]; } else { // no exact match, needs to interpolate diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagrameditdialog.ui b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagrameditdialog.ui new file mode 100644 index 0000000..d7ee6f6 --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagrameditdialog.ui @@ -0,0 +1,482 @@ + + + EyeDiagramEditDialog + + + + 0 + 0 + 844 + 429 + + + + Eye Diagram Settings + + + true + + + + + + + + + + Axes Configuration + + + + + + X Axis + + + + + + Range: + + + + + + + Auto + + + + + + + Maximum: + + + + + + + + + + Minimum: + + + + + + + + + + Divisions: + + + + + + + + + + + + + Y Axis + + + + + + Range: + + + + + + + Auto + + + + + + + Maximum: + + + + + + + + + + Minimum: + + + + + + + + + + Divisions: + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Input Datastream Configuration + + + + + + Data rate: + + + + + + + + + + Rise time: + + + + + + + + + + Fall time: + + + + + + + + + + High level: + + + + + + + + + + Low level: + + + + + + + + + + Noise (RMS): + + + + + + + + + + Jitter (RMS): + + + + + + + + + + Pattern length: + + + + + + + 7 + + + + 3 + + + + + 7 + + + + + 15 + + + + + 31 + + + + + 63 + + + + + 127 + + + + + 255 + + + + + 511 + + + + + 1023 + + + + + 2047 + + + + + + + + Rise/Fall type: + + + + + + + + Linear + + + + + Exponential + + + + + + + + Signal levels: + + + + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Eye Calculation + + + + + + Displayed cycles: + + + + + + + 1 + + + 1000 + + + + + + + Points per cycle: + + + + + + + 20 + + + 1000 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SIUnitEdit + QLineEdit +
CustomWidgets/siunitedit.h
+
+
+ + + + buttonBox + accepted() + EyeDiagramEditDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + EyeDiagramEditDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp new file mode 100644 index 0000000..155e55e --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp @@ -0,0 +1,938 @@ +#include "eyediagramplot.h" + +#include "ui_eyediagrameditdialog.h" +#include "unit.h" +#include "Util/prbs.h" +#include "Util/util.h" +#include "fftcomplex.h" +#include "preferences.h" +#include "appwindow.h" + +#include +#include +#include + +#include +#include +#include + +using namespace std::chrono_literals; + +EyeDiagramPlot::EyeDiagramPlot(TraceModel &model, QWidget *parent) + : TracePlot(model, parent), + trace(nullptr), + updating(false), + updateScheduled(false), + xSamples(200), + datarate(100000000), + highlevel(1.0), + lowlevel(0.0), + bitsPerSymbol(1), + risetime(0.000000001), + falltime(0.000000001), + noise(0.01), + jitter(0.0000000001), + linearEdge(true), + patternbits(9), + cycles(200) +{ + plotAreaTop = 0; + plotAreaLeft = 0; + plotAreaWidth = 0; + plotAreaBottom = 0; + + tdr = new Math::TDR; + + calcData = &data[0]; + displayData = &data[1]; + + xAxis.set(XAxis::Type::Time, false, true, 0, 0.000001, 1); + yAxis.set(YAxis::Type::Real, false, true, -1, 1, 1); + initializeTraceInfo(); + + connect(tdr, &Math::TDR::outputSamplesChanged, this, &EyeDiagramPlot::triggerUpdate); + + replot(); +} + +EyeDiagramPlot::~EyeDiagramPlot() +{ + while(updating) { + std::this_thread::sleep_for(20ms); + } + delete tdr; +} + +void EyeDiagramPlot::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) { + enableTrace(t.first, false); + break; + } + } + } + TracePlot::enableTrace(t, enabled); + if(enabled) { + trace = t; + tdr->assignInput(trace); + } else { + if(trace) { + tdr->removeInput(); + while(updating) { + std::this_thread::sleep_for(20ms); + } + displayData->clear(); + calcData->clear(); + } + trace = nullptr; + } +} + +void EyeDiagramPlot::replot() +{ + if(xAxis.getAutorange()) { + xAxis.set(xAxis.getType(), false, true, 0, calculatedTime(), 8); + } + if(yAxis.getAutorange()) { + yAxis.set(yAxis.getType(), false, true, minDisplayVoltage(), maxDisplayVoltage(), 8); + } + TracePlot::replot(); +} + +void EyeDiagramPlot::move(const QPoint &vect) +{ + if(!xAxis.getLog()) { + // can only move axis in linear mode + // calculate amount of movement + double distance = xAxis.inverseTransform(vect.x(), 0, plotAreaWidth) - xAxis.getRangeMin(); + xAxis.set(xAxis.getType(), false, false, xAxis.getRangeMin() - distance, xAxis.getRangeMax() - distance, xAxis.getRangeDiv()); + } + if(!yAxis.getLog()) { + // can only move axis in linear mode + // calculate amount of movement + double distance = yAxis.inverseTransform(vect.y(), 0, plotAreaTop - plotAreaBottom) - yAxis.getRangeMin(); + yAxis.set(yAxis.getType(), false, false, yAxis.getRangeMin() - distance, yAxis.getRangeMax() - distance, yAxis.getRangeDiv()); + } + replot(); +} + +void EyeDiagramPlot::zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically) +{ + if(horizontally && !xAxis.getLog()) { + // can only zoom axis in linear mode + // calculate center point + double cp = xAxis.inverseTransform(center.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth); + double min = ((xAxis.getRangeMin() - cp) * factor) + cp; + double max = ((xAxis.getRangeMax() - cp) * factor) + cp; + xAxis.set(xAxis.getType(), false, false, min, max, xAxis.getRangeDiv() * factor); + } + if(vertically) { + // can only move axis in linear mode + // calculate center point + double cp = yAxis.inverseTransform(center.y(), plotAreaBottom, plotAreaTop); + double min = ((yAxis.getRangeMin() - cp) * factor) + cp; + double max = ((yAxis.getRangeMax() - cp) * factor) + cp; + yAxis.set(yAxis.getType(), false, false, min, max, yAxis.getRangeDiv() * factor); + } + replot(); +} + +void EyeDiagramPlot::setAuto(bool horizontally, bool vertically) +{ + if(horizontally) { + xAxis.set(xAxis.getType(), xAxis.getLog(), true, xAxis.getRangeMin(), xAxis.getRangeMax(), xAxis.getRangeDiv()); + } + if(vertically) { + yAxis.set(yAxis.getType(), yAxis.getLog(), true, yAxis.getRangeMin(), yAxis.getRangeMax(), yAxis.getRangeDiv()); + } + replot(); +} + +void EyeDiagramPlot::fromJSON(nlohmann::json j) +{ + auto jX = j["XAxis"]; + bool xAuto = jX.value("autorange", xAxis.getAutorange()); + double xMin = jX.value("min", xAxis.getRangeMin()); + double xMax = jX.value("max", xAxis.getRangeMax()); + double xDivs = jX.value("div", xAxis.getRangeDiv()); + xAxis.set(xAxis.getType(), false, xAuto, xMin, xMax, xDivs); + + auto jY = j["YAxis"]; + bool yAuto = jY.value("autorange", yAxis.getAutorange()); + double yMin = jY.value("min", yAxis.getRangeMin()); + double yMax = jY.value("max", yAxis.getRangeMax()); + double yDivs = jY.value("div", yAxis.getRangeDiv()); + yAxis.set(yAxis.getType(), false, yAuto, yMin, yMax, yDivs); + + datarate = j.value("datarate", datarate); + risetime = j.value("risetime", risetime); + falltime = j.value("falltime", falltime); + linearEdge = j.value("linearEdge", linearEdge); + highlevel = j.value("highlevel", highlevel); + lowlevel = j.value("lowlevel", lowlevel); + bitsPerSymbol = j.value("bitPerSymbol", bitsPerSymbol); + noise = j.value("noise", noise); + jitter = j.value("jitter", jitter); + patternbits = j.value("patternBits", patternbits); + cycles = j.value("cycles", cycles); + xSamples = j.value("xSamples", xSamples); + + 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 EyeDiagramPlot::toJSON() +{ + nlohmann::json j; + nlohmann::json jX; + jX["autorange"] = yAxis.getAutorange(); + jX["min"] = xAxis.getRangeMin(); + jX["max"] = xAxis.getRangeMax(); + jX["div"] = xAxis.getRangeDiv(); + j["XAxis"] = jX; + nlohmann::json jY; + jY["autorange"] = yAxis.getAutorange(); + jY["min"] = yAxis.getRangeMin(); + jY["max"] = yAxis.getRangeMax(); + jY["div"] = yAxis.getRangeDiv(); + j["YAxis"] = jY; + nlohmann::json jtraces; + for(auto t : traces) { + if(t.second) { + jtraces.push_back(t.first->toHash()); + } + } + j["traces"] = jtraces; + + j["datarate"] = datarate; + j["risetime"] = risetime; + j["falltime"] = falltime; + j["linearEdge"] = linearEdge; + j["highlevel"] = highlevel; + j["lowlevel"] = lowlevel; + j["bitPerSymbol"] = bitsPerSymbol; + j["noise"] = noise; + j["jitter"] = jitter; + j["patternBits"] = patternbits; + j["cycles"] = cycles; + j["xSamples"] = xSamples; + return j; +} + +void EyeDiagramPlot::axisSetupDialog() +{ + auto d = new QDialog(this); + d->setAttribute(Qt::WA_DeleteOnClose); + auto ui = new Ui::EyeDiagramEditDialog; + ui->setupUi(d); + + ui->datarate->setUnit("bps"); + ui->datarate->setPrefixes(" kMG"); + ui->datarate->setPrecision(3); + + ui->risetime->setUnit("s"); + ui->risetime->setPrefixes("pnum "); + ui->risetime->setPrecision(3); + + ui->falltime->setUnit("s"); + ui->falltime->setPrefixes("pnum "); + ui->falltime->setPrecision(3); + + ui->highLevel->setUnit("V"); + ui->highLevel->setPrefixes("m "); + ui->highLevel->setPrecision(3); + + ui->lowLevel->setUnit("V"); + ui->lowLevel->setPrefixes("m "); + ui->lowLevel->setPrecision(3); + + ui->noise->setUnit("V"); + ui->noise->setPrefixes("um "); + ui->noise->setPrecision(3); + + ui->jitter->setUnit("s"); + ui->jitter->setPrefixes("pnum "); + ui->jitter->setPrecision(3); + + ui->Xmin->setUnit("s"); + ui->Xmin->setPrefixes("pnum "); + ui->Xmin->setPrecision(5); + + ui->Xmax->setUnit("s"); + ui->Xmax->setPrefixes("pnum "); + ui->Xmax->setPrecision(5); + + ui->Xdivs->setUnit("s"); + ui->Xdivs->setPrefixes("pnum "); + ui->Xdivs->setPrecision(3); + + ui->Ymin->setUnit("V"); + ui->Ymin->setPrefixes("um "); + ui->Ymin->setPrecision(4); + + ui->Ymax->setUnit("V"); + ui->Ymax->setPrefixes("um "); + ui->Ymax->setPrecision(4); + + ui->Ydivs->setUnit("V"); + ui->Ydivs->setPrefixes("um "); + ui->Ydivs->setPrecision(3); + + // set initial values + ui->datarate->setValue(datarate); + ui->risetime->setValue(risetime); + ui->falltime->setValue(falltime); + ui->highLevel->setValue(highlevel); + ui->lowLevel->setValue(lowlevel); + ui->noise->setValue(noise); + ui->jitter->setValue(jitter); + + ui->signalLevels->setCurrentIndex(bitsPerSymbol - 1); + ui->patternLength->setCurrentIndex(patternbits - 2); + ui->fallrisetype->setCurrentIndex(linearEdge ? 0 : 1); + + ui->displayedCycles->setValue(cycles); + ui->pointsPerCycle->setValue(xSamples); + + connect(ui->Xauto, &QCheckBox::toggled, [=](bool checked) { + ui->Xmin->setEnabled(!checked); + ui->Xmax->setEnabled(!checked); + ui->Xdivs->setEnabled(!checked); + }); + ui->Xauto->setChecked(xAxis.getAutorange()); + ui->Xmin->setValue(xAxis.getRangeMin()); + ui->Xmax->setValue(xAxis.getRangeMax()); + ui->Xdivs->setValue(xAxis.getRangeDiv()); + + connect(ui->Yauto, &QCheckBox::toggled, [=](bool checked) { + ui->Ymin->setEnabled(!checked); + ui->Ymax->setEnabled(!checked); + ui->Ydivs->setEnabled(!checked); + }); + ui->Yauto->setChecked(yAxis.getAutorange()); + ui->Ymin->setValue(yAxis.getRangeMin()); + ui->Ymax->setValue(yAxis.getRangeMax()); + ui->Ydivs->setValue(yAxis.getRangeDiv()); + + auto updateValues = [=](){ + std::lock_guard guard(calcMutex); + datarate = ui->datarate->value(); + risetime = ui->risetime->value(); + falltime = ui->falltime->value(); + highlevel = ui->highLevel->value(); + lowlevel = ui->lowLevel->value(); + noise = ui->noise->value(); + jitter = ui->jitter->value(); + + bitsPerSymbol = ui->signalLevels->currentIndex() + 1; + patternbits = ui->patternLength->currentIndex() + 2; + linearEdge = ui->fallrisetype->currentIndex() == 0; + + cycles = ui->displayedCycles->value(); + xSamples = ui->pointsPerCycle->value(); + + xAxis.set(xAxis.getType(), false, ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value()); + yAxis.set(yAxis.getType(), false, ui->Yauto->isChecked(), ui->Ymin->value(), ui->Ymax->value(), ui->Ydivs->value()); + }; + + connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [=](){ + updateValues(); + }); + connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, [=](){ + updateValues(); + }); + + if(AppWindow::showGUI()) { + d->show(); + } +} + +void EyeDiagramPlot::updateContextMenu() +{ + contextmenu->clear(); + auto setup = new QAction("Setup...", contextmenu); + connect(setup, &QAction::triggered, this, &EyeDiagramPlot::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 : orderedTraces()) { + if(!supported(t)) { + continue; + } + auto action = new QAction(t->name(), contextmenu); + action->setCheckable(true); + if(traces[t]) { + action->setChecked(true); + } + connect(action, &QAction::toggled, [=](bool active) { + enableTrace(t, active); + }); + contextmenu->addAction(action); + } + + finishContextMenu(); +} + +bool EyeDiagramPlot::positionWithinGraphArea(const QPoint &p) +{ + return p.x() >= plotAreaLeft && p.x() <= plotAreaLeft + plotAreaWidth + && p.y() >= plotAreaTop && p.y() <= plotAreaBottom; +} + +void EyeDiagramPlot::draw(QPainter &p) +{ + auto &pref = Preferences::getInstance(); + + auto w = p.window(); + auto yAxisSpace = pref.Graphs.fontSizeAxis * 5.5; + auto xAxisSpace = pref.Graphs.fontSizeAxis * 3; + plotAreaLeft = yAxisSpace; + plotAreaWidth = w.width() - plotAreaLeft - 10; + plotAreaTop = 10; + plotAreaBottom = w.height() - xAxisSpace; + + p.setBackground(QBrush(pref.Graphs.Color.background)); + p.fillRect(0, 0, width(), height(), QBrush(pref.Graphs.Color.background)); + + auto pen = QPen(pref.Graphs.Color.axis, 0); + pen.setCosmetic(true); + p.setPen(pen); + auto plotRect = QRect(plotAreaLeft, plotAreaTop, plotAreaWidth + 1, plotAreaBottom - plotAreaTop + 1); + p.drawRect(plotRect); + + // Y axis + QString labelY = "Voltage"; + auto font = p.font(); + font.setPixelSize(pref.Graphs.fontSizeAxis); + p.setFont(font); + p.setPen(QPen(pref.Graphs.Color.axis, 1)); + p.save(); + p.translate(0, w.height()-xAxisSpace); + p.rotate(-90); + p.drawText(QRect(0, 0, w.height()-xAxisSpace, pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, labelY); + p.restore(); + + // draw ticks + if(yAxis.getTicks().size() > 0) { + // this only works for evenly distributed ticks: + auto max = qMax(abs(yAxis.getTicks().front()), abs(yAxis.getTicks().back())); + double step; + if(yAxis.getTicks().size() >= 2) { + step = abs(yAxis.getTicks()[0] - yAxis.getTicks()[1]); + } else { + // only one tick, set arbitrary number of digits + step = max / 1000; + } + int significantDigits = floor(log10(max)) - floor(log10(step)) + 1; + + for(unsigned int j = 0; j < yAxis.getTicks().size(); j++) { + auto yCoord = yAxis.transform(yAxis.getTicks()[j], plotAreaBottom, plotAreaTop); + p.setPen(QPen(pref.Graphs.Color.axis, 1)); + // draw tickmark on axis + auto tickStart = plotAreaLeft; + auto tickLen = -2; + p.drawLine(tickStart, yCoord, tickStart + tickLen, yCoord); + QString unit = ""; + QString prefix = " "; + if(pref.Graphs.showUnits) { + unit = "V"; + prefix = "um "; + } + auto tickValue = Unit::ToString(yAxis.getTicks()[j], unit, prefix, significantDigits); + p.drawText(QRectF(0, yCoord - pref.Graphs.fontSizeAxis/2 - 2, tickStart + 2 * tickLen, pref.Graphs.fontSizeAxis), Qt::AlignRight, tickValue); + + // tick lines + if(yCoord == plotAreaTop || yCoord == plotAreaBottom) { + // skip tick lines right on the plot borders + continue; + } + // only draw tick lines for primary axis + if (pref.Graphs.Color.Ticks.Background.enabled) { + if (j%2) + { + int yCoordTop = yAxis.transform(yAxis.getTicks()[j], plotAreaTop, plotAreaBottom); + int yCoordBot = yAxis.transform(yAxis.getTicks()[j-1], plotAreaTop, plotAreaBottom); + if(yCoordTop > yCoordBot) { + auto buf = yCoordBot; + yCoordBot = yCoordTop; + yCoordTop = buf; + } + p.setBrush(pref.Graphs.Color.Ticks.Background.background); + p.setPen(pref.Graphs.Color.Ticks.Background.background); + auto rect = QRect(plotAreaLeft+1, yCoordTop+1, plotAreaWidth-2, yCoordBot-yCoordTop-2); + p.drawRect(rect); + } + } + p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine)); + p.drawLine(plotAreaLeft, yCoord, plotAreaLeft + plotAreaWidth, yCoord); + } + } + + // X axis name + p.setPen(QPen(pref.Graphs.Color.axis, 1)); + p.drawText(QRect(plotAreaLeft, w.height()-pref.Graphs.fontSizeAxis*1.5, plotAreaWidth, pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, "Time"); + + // draw X axis ticks + if(xAxis.getTicks().size() >= 1) { + // draw X ticks + int significantDigits; + // this only works for evenly distributed ticks: + auto max = qMax(abs(xAxis.getTicks().front()), abs(xAxis.getTicks().back())); + double step; + if(xAxis.getTicks().size() >= 2) { + step = abs(xAxis.getTicks()[0] - xAxis.getTicks()[1]); + } else { + // only one tick, set arbitrary number of digits + step = max / 1000; + } + significantDigits = floor(log10(max)) - floor(log10(step)) + 1; + QString prefixes = "fpnum kMG"; + QString unit = ""; + if(pref.Graphs.showUnits) { + unit = xAxis.Unit(); + } + int lastTickLabelEnd = 0; + for(auto t : xAxis.getTicks()) { + auto xCoord = xAxis.transform(t, plotAreaLeft, plotAreaLeft + plotAreaWidth); + 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)); + QRect bounding; + p.drawText(QRect(xCoord - pref.Graphs.fontSizeAxis*2, plotAreaBottom + 5, pref.Graphs.fontSizeAxis*4, + pref.Graphs.fontSizeAxis), Qt::AlignHCenter, tickValue, &bounding); + lastTickLabelEnd = bounding.x() + bounding.width(); + } + } + + if(displayData->size() >= 2) { + std::lock_guard guard(bufferSwitchMutex); + unsigned int pxWidth = plotAreaWidth; + unsigned int pxHeight = plotAreaBottom - plotAreaTop; + std::vector> bitmap; + bitmap.resize(pxWidth); + for(auto &y : bitmap) { + y.resize(pxHeight, 0); + } + unsigned int highestIntensity = 0; + unsigned int numTraces = (*displayData)[0].y.size(); + + auto addLine = [&](int x0, int y0, int x1, int y1, bool skipFirst = true) { + bool first = true; + auto putpixel = [&](int x, int y) { + if(skipFirst && first) { + first = false; + return; + } + if(x < 0 || x >= (int) pxWidth || y < 0 || y >= (int) pxHeight) { + return; + } + auto &bin = bitmap[x][y]; + bin++; + if(bin > highestIntensity) { + highestIntensity = bin; + } + }; + + int dx = abs (x1 - x0), sx = x0 < x1 ? 1 : -1; + int dy = -abs (y1 - y0), sy = y0 < y1 ? 1 : -1; + int err = dx + dy, e2; /* error value e_xy */ + + for (;;){ /* loop */ + putpixel (x0,y0); + if (x0 == x1 && y0 == y1) break; + e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */ + if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */ + } + }; + + // Assemble the bitmap + for(unsigned int i=1;i= (int) pxWidth && x1 >= (int) pxWidth)) { + // completely out of the frame + continue; + } + for(unsigned int j=0;j 1); + } + } + + // draw the bitmap + pen = QPen(); + pen.setCosmetic(true); + for(unsigned int i=1;i 0) { + double value = (double) bitmap[i][j] / highestIntensity; + pen.setColor(Util::getIntensityGradeColor(value)); + p.setPen(pen); + p.drawPoint(plotAreaLeft + i + 1, plotAreaTop + j + 1); + } + } + } + } + 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 EyeDiagramPlot::supported(Trace *t) +{ + if(t->getDataType() != Trace::DataType::Frequency) { + // wrong domain + return false; + } + if(t->isReflection()) { + // can't work with reflection measurements + return false; + } + return true; +} + +QString EyeDiagramPlot::mouseText(QPoint pos) +{ + QString ret; + if(positionWithinGraphArea(pos)) { + // cursor within plot area + QPointF coords = pixelToPlotValue(pos); + int significantDigits = floor(log10(abs(xAxis.getRangeMax()))) - floor(log10((abs(xAxis.getRangeMax() - xAxis.getRangeMin())) / 1000.0)) + 1; + ret += Unit::ToString(coords.x(), xAxis.Unit(), "fpnum kMG", significantDigits) + "\n"; + auto max = qMax(abs(yAxis.getRangeMax()), abs(yAxis.getRangeMin())); + auto step = abs(yAxis.getRangeMax() - yAxis.getRangeMin()) / 1000.0; + significantDigits = floor(log10(max)) - floor(log10(step)) + 1; + ret += Unit::ToString(coords.y(), "V", yAxis.Prefixes(), significantDigits) + "\n"; + } + return ret; +} + +QPoint EyeDiagramPlot::plotValueToPixel(QPointF plotValue) +{ + QPoint p; + p.setX(xAxis.transform(plotValue.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth)); + p.setY(yAxis.transform(plotValue.y(), plotAreaBottom, plotAreaTop)); + return p; +} + +QPointF EyeDiagramPlot::pixelToPlotValue(QPoint pixel) +{ + QPointF p; + p.setX(xAxis.inverseTransform(pixel.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth)); + p.setY(yAxis.inverseTransform(pixel.y(), plotAreaBottom, plotAreaTop)); + return p; +} + +void EyeDiagramPlot::updateThread(unsigned int xSamples) +{ + std::lock_guard calc(calcMutex); + do { + updateScheduled = false; + setStatus("Starting calculation..."); + if(!trace) { + setStatus("No trace assigned"); + continue; + } + + qDebug() << "Starting eye diagram calculation"; + + // sanity check values + if(datarate >= trace->getSample(trace->numSamples() - 1).x) { + setStatus("Data rate too high"); + continue; + } + if(datarate <= 0) { + setStatus("Data rate too low"); + continue; + } + if(risetime > 0.3 * 1.0 / datarate) { + setStatus("Rise time too high"); + continue; + } + if(falltime > 0.3 * 1.0 / datarate) { + setStatus("Fall time too high"); + continue; + } + if(jitter > 0.3 * 1.0 / datarate) { + setStatus("Jitter too high"); + continue; + } + + qDebug() << "Eye calculation: input values okay"; + + // calculate timestep + double timestep = calculatedTime() / xSamples; + // reserve vector for input data + std::vector> inVec(xSamples * (cycles + 1), 0.0); // needs to calculate one more cycle than required for the display (settling) + + // resize working buffer + calcData->clear(); + calcData->resize(xSamples); + for(auto& s : *calcData) { + s.y.resize(cycles, 0.0); + } + + setStatus("Extracting impulse response..."); + + // calculate impulse response of trace + double eyeTimeShift = 0; + std::vector> impulseVec; + // determine how long the impulse response is + auto samples = tdr->numSamples(); + if(samples == 0) { + // TDR calculation not yet done, unable to update + updating = false; + setStatus("No time-domain data from trace"); + return; + } + auto length = tdr->getSample(samples - 1).x; + + // determine average delay + auto total_step = tdr->getStepResponse(samples - 1); + for(unsigned int i=0;igetStepResponse(i); + if(abs(total_step - step) <= abs(step)) { + // mid point reached + eyeTimeShift = tdr->getSample(i).x; + break; + } + } + + unsigned long convolutedSize = length / timestep; + if(convolutedSize > inVec.size()) { + // impulse response is longer than what we display, truncate + convolutedSize = inVec.size(); + } + impulseVec.resize(convolutedSize); + /* + * we can't use the impulse response directly because we most likely need samples inbetween + * the calculated values. Interpolation is available but if our sample spacing here is much + * wider than the impulse response data, we might miss peaks (or severely miscalculate their + * amplitude. + * Instead, the step response is interpolated and the impulse response determined by deriving + * it from the interpolated step response data. As the step response is the integrated imulse + * response data, we can't miss narrow peaks that way. + */ + double lastStepResponse = 0.0; + for(unsigned long i=0;igetInterpolatedStepResponse(x); + impulseVec[i] = step - lastStepResponse; + lastStepResponse = step; + } + + eyeTimeShift += (risetime + falltime) * 1.25 / 4; + eyeTimeShift += 0.5 / datarate; + int eyeXshift = eyeTimeShift / timestep; + + qDebug() << "Eye calculation: TDR calculation done"; + + setStatus("Generating PRBS sequence..."); + + auto prbs = new PRBS(patternbits); + + auto getNextLevel = [&]() -> unsigned int { + unsigned int level = 0; + for(unsigned int i=0;inext()) { + level |= 0x01; + } + } + return level; + }; + + auto levelToVoltage = [=](unsigned int level) -> double { + unsigned int maxLevel = (0x01 << bitsPerSymbol) - 1; + return Util::Scale((double) level, 0.0, (double) maxLevel, lowlevel, highlevel); + }; + + unsigned int currentSignal = getNextLevel(); + unsigned int nextSignal = getNextLevel(); + + // initialize random generator + std::random_device rd1; + std::mt19937 mt_noise(rd1()); + std::normal_distribution<> dist_noise(0, noise); + + std::random_device rd2; + std::mt19937 mt_jitter(rd2()); + std::normal_distribution<> dist_jitter(0, jitter); + + unsigned int bitcnt = 1; + double transitionTime = -10; // assume that we start with a settled input, last transition was "long" ago + for(unsigned int i=0;i= transitionTime) { + // currently within a bit transition + double edgeTime = 0; + double expTimeConstant; + if(currentSignal < nextSignal) { + edgeTime = risetime; + } else if(currentSignal > nextSignal) { + edgeTime = falltime; + } + if(linearEdge) { + // edge is modeled as linear rise/fall + // increase slightly to account for typical 10/90% fall/rise time + edgeTime *= 1.25; + } else { + // edge is modeled as exponential rise/fall. Adjust time constant to match + // selected rise/fall time (with 10-90% signal rise/fall within specified time) + expTimeConstant = edgeTime / 2.197224577; + edgeTime = 6 * expTimeConstant; // after six time constants, 99.7% of signal movement has happened + } + if(time >= transitionTime + edgeTime) { + // bit transition settled + voltage = levelToVoltage(nextSignal); + // move on to the next bit + currentSignal = nextSignal; + nextSignal = getNextLevel(); + transitionTime = bitcnt * 1.0 / datarate + dist_jitter(mt_jitter); + bitcnt++; + } else { + // still within rise or fall time + double timeSinceEdge = time - transitionTime; + double from = levelToVoltage(currentSignal); + double to = levelToVoltage(nextSignal); + if(linearEdge) { + double edgeRatio = timeSinceEdge / edgeTime; + voltage = from * (1.0 - edgeRatio) + to * edgeRatio; + } else { + voltage = from + (1.0 - exp(-timeSinceEdge/expTimeConstant)) * (to - from); + } + } + } else { + // still before the next edge + voltage = levelToVoltage(currentSignal); + } + voltage += dist_noise(mt_noise); + inVec[i] = voltage; + } + + // input voltage vector fully assembled + qDebug() << "Eye calculation: input data generated"; + + setStatus("Performing convolution..."); + + qDebug() << "Convolve via FFT start"; + std::vector> outVec; + impulseVec.resize(inVec.size(), 0.0); + outVec.resize(inVec.size()); + Fft::convolve(inVec, impulseVec, outVec); + qDebug() << "Convolve via FFT stop"; + + // fill data from outVec + for(unsigned int i=0;i guard(bufferSwitchMutex); + // switch buffers + auto buf = displayData; + displayData = calcData; + calcData = buf; + } + + setStatus("Eye calculation complete"); + replot(); + } while (updateScheduled); + updating = false; +} + +void EyeDiagramPlot::triggerUpdate() +{ + if(updating) { + // already updating, can't start again, schedule for later + updateScheduled = true; + } else { + updating = true; + new std::thread(&EyeDiagramPlot::updateThread, this, xSamples); + } +} + +void EyeDiagramPlot::setStatus(QString s) +{ + status = s; + emit statusChanged(s); +} + +double EyeDiagramPlot::calculatedTime() +{ + return 2.0 / datarate; +} + +double EyeDiagramPlot::minDisplayVoltage() +{ + auto eyeRange = highlevel - lowlevel; + return lowlevel - eyeRange * yOverrange; +} + +double EyeDiagramPlot::maxDisplayVoltage() +{ + auto eyeRange = highlevel - lowlevel; + return highlevel + eyeRange * yOverrange; +} diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h new file mode 100644 index 0000000..a6d0022 --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h @@ -0,0 +1,103 @@ +#ifndef EYEDIAGRAMPLOT_H +#define EYEDIAGRAMPLOT_H + +#include "traceplot.h" +#include "traceaxis.h" +#include "Traces/Math/tdr.h" + +#include + +#include + +class EyeDiagramPlot : public TracePlot +{ +// friend class WaterfallAxisDialog; + Q_OBJECT +public: + EyeDiagramPlot(TraceModel &model, QWidget *parent = 0); + ~EyeDiagramPlot(); + + virtual void enableTrace(Trace *t, bool enabled) override; + void replot() override; + + virtual void move(const QPoint &vect) override; + virtual void zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically) override; + virtual void setAuto(bool horizontally, bool vertically) override; + + virtual Type getType() override { return Type::EyeDiagram;} + + void fromJSON(nlohmann::json j) override; + nlohmann::json toJSON() override; + +public slots: + void axisSetupDialog(); +signals: + void statusChanged(QString); + +protected: + virtual void updateContextMenu() override; + virtual bool positionWithinGraphArea(const QPoint &p) 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);} // never used but most be implemented + virtual double nearestTracePoint(Trace *t, QPoint pixel, double *distance = nullptr) override {Q_UNUSED(t)Q_UNUSED(pixel)Q_UNUSED(distance)return 0.0;} // never used but most be implemented + + virtual QString mouseText(QPoint pos) override; + +protected slots: + virtual bool markerVisible(double x) override {Q_UNUSED(x)return false;} // never show markers + +private slots: + void triggerUpdate(); +private: + static constexpr double yOverrange = 0.2; + QPoint plotValueToPixel(QPointF plotValue); + QPointF pixelToPlotValue(QPoint pixel); + void updateThread(unsigned int xSamples); + void setStatus(QString s); + double calculatedTime(); + double minDisplayVoltage(); + double maxDisplayVoltage(); + + Math::TDR *tdr; + + Trace *trace; + + XAxis xAxis; + YAxis yAxis; + + class Xdata { + public: + double x; + std::vector y; + }; + + std::vector data[2]; + std::vector *displayData; + std::vector *calcData; + bool updating; + bool updateScheduled; + + unsigned int xSamples; + double datarate; + double highlevel; + double lowlevel; + unsigned int bitsPerSymbol; + double risetime; + double falltime; + double noise; + double jitter; + bool linearEdge; + unsigned int patternbits; + unsigned int cycles; + + int plotAreaLeft, plotAreaWidth, plotAreaBottom, plotAreaTop; + + QString status; + + std::mutex bufferSwitchMutex; + std::mutex calcMutex; +}; + +#endif // EYEDIAGRAMPLOT_H diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.cpp index 2d9dcf3..f40ecf6 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.cpp @@ -11,6 +11,7 @@ #include #include #include +#include std::set TracePlot::plots; @@ -20,6 +21,7 @@ TracePlot::TracePlot(TraceModel &model, QWidget *parent) : QWidget(parent), model(model), selectedMarker(nullptr), + movingGraph(false), traceRemovalPending(false), dropPending(false), dropTrace(nullptr), @@ -326,15 +328,29 @@ void TracePlot::mousePressEvent(QMouseEvent *event) { if(event->buttons() == Qt::LeftButton) { selectedMarker = markerAtPosition(event->pos(), true); + if(!selectedMarker && positionWithinGraphArea(event->pos())) { + // no marker at the position, enter trace moving mode + movingGraph = true; + lastMousePoint = event->pos(); + cursorLabel->hide(); + } } else { selectedMarker = nullptr; } + if(event->button() == Qt::MiddleButton) { + bool horizontally = !(QApplication::keyboardModifiers() & Qt::ShiftModifier); + bool vertically = !(QApplication::keyboardModifiers() & Qt::ControlModifier); + setAuto(horizontally, vertically); + } + event->accept(); } void TracePlot::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event) selectedMarker = nullptr; + movingGraph = false; + event->accept(); } void TracePlot::mouseMoveEvent(QMouseEvent *event) @@ -344,6 +360,9 @@ void TracePlot::mouseMoveEvent(QMouseEvent *event) auto trace = selectedMarker->getTrace(); selectedMarker->setPosition(nearestTracePoint(trace, clickPoint)); cursorLabel->hide(); + } else if(movingGraph) { + move(event->pos() - lastMousePoint); + lastMousePoint = event->pos(); } else { auto text = mouseText(clickPoint); if(!text.isEmpty()) { @@ -358,6 +377,7 @@ void TracePlot::mouseMoveEvent(QMouseEvent *event) cursorLabel->hide(); } } + event->accept(); } void TracePlot::leaveEvent(QEvent *event) @@ -365,6 +385,19 @@ void TracePlot::leaveEvent(QEvent *event) Q_UNUSED(event); cursorLabel->hide(); selectedMarker = nullptr; + movingGraph = false; + event->accept(); +} + +void TracePlot::wheelEvent(QWheelEvent *event) +{ + auto &pref = Preferences::getInstance(); + if(positionWithinGraphArea(event->pos())) { + bool horizontally = !(QApplication::keyboardModifiers() & Qt::ShiftModifier); + bool vertically = !(QApplication::keyboardModifiers() & Qt::ControlModifier); + double factor = pow((1.0-pref.Graphs.zoomFactor), (double) event->angleDelta().y() / 120.0); + zoom(event->pos(), factor, horizontally, vertically); + } } Marker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable) diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.h b/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.h index 5918452..b28606c 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/traceplot.h @@ -21,6 +21,7 @@ public: XYPlot, Waterfall, PolarChart, + EyeDiagram, }; TracePlot(TraceModel &model, QWidget *parent = nullptr); @@ -60,6 +61,9 @@ protected: virtual void updateContextMenu(){} // adds common entries at bottom of context menu. Should be called at the end of derived udpateContextMenu functions void finishContextMenu(); + virtual void move(const QPoint &vect) { Q_UNUSED(vect) } + virtual void zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically) {Q_UNUSED(center)Q_UNUSED(factor)Q_UNUSED(horizontally)Q_UNUSED(vertically)} + virtual void setAuto(bool horizontally, bool vertically) {Q_UNUSED(horizontally)Q_UNUSED(vertically)} virtual void replot(){update();} virtual void draw(QPainter& p) = 0; virtual bool supported(Trace *t) = 0; @@ -77,8 +81,10 @@ protected: void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void leaveEvent(QEvent *event) override; + void wheelEvent(QWheelEvent *event) override; Marker *markerAtPosition(QPoint p, bool onlyMovable = false); + virtual bool positionWithinGraphArea(const QPoint &p) {Q_UNUSED(p) return false;} void createMarkerAtPosition(QPoint p); @@ -106,6 +112,8 @@ protected: double sweep_fmin, sweep_fmax; TraceModel &model; Marker *selectedMarker; + bool movingGraph; + QPoint lastMousePoint; TileWidget *parentTile; // graph settings have been changed, check and possibly remove incompatible traces before next paint event diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracepolar.h b/Software/PC_Application/LibreVNA-GUI/Traces/tracepolar.h index 377ccdc..563bf2d 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracepolar.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracepolar.h @@ -24,7 +24,7 @@ public: void wheelEvent(QWheelEvent *event) override; public slots: - virtual void axisSetupDialog() {}; + virtual void axisSetupDialog() {} protected: static constexpr double polarCoordMax = 4096; @@ -41,7 +41,7 @@ protected: virtual bool markerVisible(double x) override; virtual void updateContextMenu() override; - virtual bool supported(Trace *t) override {Q_UNUSED(t) return false;}; + virtual bool supported(Trace *t) override {Q_UNUSED(t) return false;} // given two points and a circle, the two points are adjusted in such a way that the line they describe // is constrained within the circle. Returns true if there is a remaining line segment in the circle, false diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracesmithchart.h b/Software/PC_Application/LibreVNA-GUI/Traces/tracesmithchart.h index 932a132..6de4539 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracesmithchart.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracesmithchart.h @@ -58,7 +58,7 @@ private: class SmithChartTypeDelegate : public QStyledItemDelegate { - Q_OBJECT; + Q_OBJECT QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override; QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; @@ -66,7 +66,7 @@ class SmithChartTypeDelegate : public QStyledItemDelegate class SmithChartParamDelegate : public QStyledItemDelegate { - Q_OBJECT; + Q_OBJECT QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const override; QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracewaterfall.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracewaterfall.cpp index 9dc7a58..1e0425e 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracewaterfall.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracewaterfall.cpp @@ -54,7 +54,6 @@ void TraceWaterfall::enableTrace(Trace *t, bool enabled) } trace = nullptr; } - } void TraceWaterfall::replot() diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp index 0c0c3a5..acc6b82 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp @@ -1,4 +1,4 @@ -#include "tracexyplot.h" +#include "tracexyplot.h" #include "trace.h" #include "CustomWidgets/informationbox.h" @@ -96,6 +96,64 @@ void TraceXYPlot::replot() TracePlot::replot(); } +void TraceXYPlot::move(const QPoint &vect) +{ + if(!xAxis.getLog()) { + // can only move axis in linear mode + // calculate amount of movement + double distance = xAxis.inverseTransform(vect.x(), 0, plotAreaWidth) - xAxis.getRangeMin(); + xAxis.set(xAxis.getType(), false, false, xAxis.getRangeMin() - distance, xAxis.getRangeMax() - distance, xAxis.getRangeDiv()); + xAxisMode = XAxisMode::Manual; + } + for(int i=0;i<2;i++) { + if(!yAxis[i].getLog()) { + // can only move axis in linear mode + // calculate amount of movement + double distance = yAxis[i].inverseTransform(vect.y(), 0, plotAreaTop - plotAreaBottom) - yAxis[i].getRangeMin(); + yAxis[i].set(yAxis[i].getType(), false, false, yAxis[i].getRangeMin() - distance, yAxis[i].getRangeMax() - distance, yAxis[i].getRangeDiv()); + } + } + replot(); +} + +void TraceXYPlot::zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically) +{ + if(horizontally && !xAxis.getLog()) { + // can only zoom axis in linear mode + // calculate center point + double cp = xAxis.inverseTransform(center.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth); + double min = ((xAxis.getRangeMin() - cp) * factor) + cp; + double max = ((xAxis.getRangeMax() - cp) * factor) + cp; + xAxis.set(xAxis.getType(), false, false, min, max, xAxis.getRangeDiv() * factor); + xAxisMode = XAxisMode::Manual; + } + for(int i=0;i<2;i++) { + if(vertically && yAxis[i].getType() != YAxis::Type::Disabled && !yAxis[i].getLog()) { + // can only move axis in linear mode + // calculate center point + double cp = yAxis[i].inverseTransform(center.y(), plotAreaBottom, plotAreaTop); + double min = ((yAxis[i].getRangeMin() - cp) * factor) + cp; + double max = ((yAxis[i].getRangeMax() - cp) * factor) + cp; + yAxis[i].set(yAxis[i].getType(), false, false, min, max, yAxis[i].getRangeDiv() * factor); + } + } + replot(); +} + +void TraceXYPlot::setAuto(bool horizontally, bool vertically) +{ + if(horizontally) { + xAxisMode = XAxisMode::FitTraces; + xAxis.set(xAxis.getType(), xAxis.getLog(), true, xAxis.getRangeMin(), xAxis.getRangeMax(), xAxis.getRangeDiv()); + } + for(int i=0;i<2;i++) { + if(vertically && yAxis[i].getType() != YAxis::Type::Disabled) { + yAxis[i].set(yAxis[i].getType(), yAxis[i].getLog(), true, yAxis[i].getRangeMin(), yAxis[i].getRangeMax(), yAxis[i].getRangeDiv()); + } + } + replot(); +} + nlohmann::json TraceXYPlot::toJSON() { nlohmann::json j; @@ -325,6 +383,12 @@ void TraceXYPlot::updateContextMenu() finishContextMenu(); } +bool TraceXYPlot::positionWithinGraphArea(const QPoint &p) +{ + return p.x() >= plotAreaLeft && p.x() <= plotAreaLeft + plotAreaWidth + && p.y() >= plotAreaTop && p.y() <= plotAreaBottom; +} + bool TraceXYPlot::dropSupported(Trace *t) { if(domainMatch(t) && !supported(t)) { @@ -1057,7 +1121,7 @@ void TraceXYPlot::traceDropped(Trace *t, QPoint position) QString TraceXYPlot::mouseText(QPoint pos) { QString ret; - if(QRect(plotAreaLeft, 0, plotAreaWidth + 1, plotAreaBottom).contains(pos)) { + if(positionWithinGraphArea(pos)) { // cursor within plot area QPointF coords[2]; coords[0] = pixelToPlotValue(pos, 0); diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.h b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.h index daca0d7..17a87c3 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.h @@ -74,6 +74,10 @@ public: void updateSpan(double min, double max) override; void replot() override; + virtual void move(const QPoint &vect) override; + virtual void zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically) override; + virtual void setAuto(bool horizontally, bool vertically) override; + virtual Type getType() override { return Type::XYPlot;} virtual nlohmann::json toJSON() override; virtual void fromJSON(nlohmann::json j) override; @@ -88,6 +92,7 @@ public slots: protected: virtual bool configureForTrace(Trace *t) override; virtual void updateContextMenu() override; + virtual bool positionWithinGraphArea(const QPoint &p) override; virtual bool dropSupported(Trace *t) override; virtual void draw(QPainter &p) override; diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp index 967aa0c..9cf8c1e 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp @@ -12,7 +12,6 @@ #include "CustomWidgets/siunitedit.h" #include "Traces/Marker/markerwidget.h" #include "Tools/impedancematchdialog.h" -#include "Tools/eyediagramdialog.h" #include "ui_main.h" #include "Device/firmwareupdatedialog.h" #include "preferences.h" @@ -183,8 +182,6 @@ VNA::VNA(AppWindow *window, QString name) actions.insert(toolsMenu->menuAction()); auto impedanceMatching = toolsMenu->addAction("Impedance Matching"); connect(impedanceMatching, &QAction::triggered, this, &VNA::StartImpedanceMatching); - auto eyeDiagram = toolsMenu->addAction("Eye Diagram"); - connect(eyeDiagram, &QAction::triggered, this, &VNA::StartEyeDiagram); defaultCalMenu = new QMenu("Default Calibration", window); assignDefaultCal = defaultCalMenu->addAction("Assign..."); @@ -955,15 +952,6 @@ void VNA::StartImpedanceMatching() } } -void VNA::StartEyeDiagram() -{ - auto dialog = new EyeDiagramDialog(traceModel); - if(AppWindow::showGUI()) { - dialog->show(); - } -} - - void VNA::SetSweepType(SweepType sw) { if(settings.sweepType != sw) { diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/vna.h b/Software/PC_Application/LibreVNA-GUI/VNA/vna.h index 21a480f..45f493f 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/vna.h +++ b/Software/PC_Application/LibreVNA-GUI/VNA/vna.h @@ -81,7 +81,6 @@ public slots: private slots: void NewDatapoint(VirtualDevice::VNAMeasurement m); void StartImpedanceMatching(); - void StartEyeDiagram(); // Sweep control void SetSweepType(SweepType sw); void SetStartFreq(double freq); diff --git a/Software/PC_Application/LibreVNA-GUI/preferences.h b/Software/PC_Application/LibreVNA-GUI/preferences.h index 00876c9..2a1de84 100644 --- a/Software/PC_Application/LibreVNA-GUI/preferences.h +++ b/Software/PC_Application/LibreVNA-GUI/preferences.h @@ -128,6 +128,8 @@ public: int fontSizeMarkerData; int fontSizeTraceNames; int fontSizeCursorOverlay; + + double zoomFactor; } Graphs; struct { struct { @@ -211,6 +213,7 @@ private: {&Graphs.fontSizeCursorOverlay, "Graphs.fontSizeCursorOverlay", 12}, {&Graphs.fontSizeMarkerData, "Graphs.fontSizeMarkerData", 12}, {&Graphs.fontSizeTraceNames, "Graphs.fontSizeTraceNames", 12}, + {&Graphs.zoomFactor, "Graphs.zoomFactor", 0.2}, {&Marker.defaultBehavior.showDataOnGraphs, "Marker.defaultBehavior.ShowDataOnGraphs", true}, {&Marker.defaultBehavior.showAllData, "Marker.defaultBehavior.ShowAllData", false}, {&Marker.interpolatePoints, "Marker.interpolatePoints", false}, diff --git a/Software/PC_Application/LibreVNA-GUI/test.setup b/Software/PC_Application/LibreVNA-GUI/test.setup new file mode 100644 index 0000000..4269ed3 --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/test.setup @@ -0,0 +1,327 @@ +{ + "Modes": [ + { + "name": "Vector Network Analyzer", + "settings": { + "de-embedding": null, + "de-embedding_enabled": false, + "markers": null, + "sweep": { + "IFBW": 10000.0, + "frequency": { + "log": false, + "power": -10.0, + "start": 1000000.0, + "stop": 6000000000.0 + }, + "points": 501, + "power": { + "frequency": 1000000000.0, + "start": -30.0, + "stop": -15.0 + }, + "single": false, + "type": "Frequency" + }, + "tiles": { + "orientation": "vertical", + "sizes": [ + 373, + 372 + ], + "split": true, + "tile1": { + "orientation": "horizontal", + "sizes": [ + 797, + 796 + ], + "split": true, + "tile1": { + "plot": "smithchart", + "plotsettings": { + "Z0": 50.0, + "constantLines": null, + "edge_reflection": 1.0, + "frequency_override": false, + "limit_to_edge": true, + "limit_to_span": true, + "offset_axis_x": 0.0, + "override_max": 6000000000.0, + "override_min": 0.0, + "traces": [ + 753243053 + ] + }, + "split": false + }, + "tile2": { + "plot": "EyeDiagram", + "plotsettings": { + "XAxis": { + "autorange": false, + "div": 7.62939453125e-09, + "max": 5.653089396158854e-08, + "min": -1.9763051350911456e-08 + }, + "YAxis": { + "autorange": false, + "div": 0.3814697265625, + "max": 2.9595544473356057, + "min": -2.381021724539393 + }, + "bitPerSymbol": 1, + "cycles": 200, + "datarate": 100000000.0, + "falltime": 1e-09, + "highlevel": 1.0, + "jitter": 1e-10, + "linearEdge": true, + "lowlevel": 0.0, + "noise": 0.01, + "patternBits": 9, + "risetime": 1e-09, + "traces": [ + 3153058534 + ], + "xSamples": 200 + }, + "split": false + } + }, + "tile2": { + "orientation": "horizontal", + "sizes": [ + 797, + 796 + ], + "split": true, + "tile1": { + "plot": "EyeDiagram", + "plotsettings": { + "XAxis": { + "autorange": true, + "div": 1e-07, + "max": 1e-06, + "min": 0.0 + }, + "YAxis": { + "autorange": true, + "div": 0.1, + "max": 1.2, + "min": -0.2 + }, + "bitPerSymbol": 2, + "cycles": 200, + "datarate": 2000000.0, + "falltime": 1e-09, + "highlevel": 1.0, + "jitter": 4e-10, + "linearEdge": true, + "lowlevel": 0.0, + "noise": 0.03, + "patternBits": 9, + "risetime": 1e-09, + "traces": [ + 2663219575 + ], + "xSamples": 200 + }, + "split": false + }, + "tile2": { + "plot": "smithchart", + "plotsettings": { + "Z0": 50.0, + "constantLines": null, + "edge_reflection": 1.0, + "frequency_override": false, + "limit_to_edge": true, + "limit_to_span": true, + "offset_axis_x": 0.0, + "override_max": 6000000000.0, + "override_min": 0.0, + "traces": [ + 1360958916 + ] + }, + "split": false + } + } + }, + "traces": [ + { + "color": "#ffff00", + "hash": 753243053, + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "S11", + "parameter": "S11", + "paused": false, + "reflection": true, + "type": "Live", + "velocityFactor": 0.66, + "visible": true + }, + { + "color": "#0000ff", + "hash": 3153058534, + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "S12", + "parameter": "S12", + "paused": false, + "reflection": false, + "type": "Live", + "velocityFactor": 0.66, + "visible": true + }, + { + "color": "#00ff00", + "hash": 2663219575, + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "S21", + "parameter": "S21", + "paused": false, + "reflection": false, + "type": "Live", + "velocityFactor": 0.66, + "visible": true + }, + { + "color": "#ff0000", + "hash": 1360958916, + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "S22", + "parameter": "S22", + "paused": false, + "reflection": true, + "type": "Live", + "velocityFactor": 0.66, + "visible": true + } + ] + }, + "type": "Vector Network Analyzer" + }, + { + "name": "Signal Generator", + "settings": { + "frequency": 1000000000.0, + "port": 0, + "power": 0.0, + "sweep": { + "dwell": 1.0, + "enabled": false, + "span": 0.0, + "steps": 100.0 + } + }, + "type": "Signal Generator" + }, + { + "name": "Spectrum Analyzer", + "settings": { + "markers": null, + "sweep": { + "acquisition": { + "RBW": 100.0, + "detector": "+Peak", + "signal ID": true, + "window": "Kaiser" + }, + "frequency": { + "start": 999975000.0, + "stop": 1000025000.0 + }, + "single": false, + "trackingGenerator": { + "enabled": false, + "offset": 0.0, + "port": 1, + "power": -20.0 + } + }, + "tiles": { + "plot": "XY-plot", + "plotsettings": { + "XAxis": { + "div": 5000.0, + "log": false, + "max": 1000025000.0, + "min": 999975000.0, + "mode": "Use Span", + "type": "Frequency" + }, + "YPrimary": { + "autorange": false, + "div": 10.0, + "log": false, + "max": 0.0, + "min": -120.0, + "traces": [ + 3115643686, + 1375490686 + ], + "type": "Magnitude" + }, + "YSecondary": { + "autorange": true, + "div": 0.0, + "log": false, + "max": 1.0, + "min": -1.0, + "traces": null, + "type": "Disabled" + }, + "limitLines": null + }, + "split": false + }, + "traces": [ + { + "color": "#ffff00", + "hash": 1375490686, + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "PORT1", + "parameter": "PORT1", + "paused": false, + "reflection": false, + "type": "Live", + "velocityFactor": 0.66, + "visible": true + }, + { + "color": "#0000ff", + "hash": 3115643686, + "livetype": 0, + "math": null, + "math_enabled": false, + "name": "PORT2", + "parameter": "PORT2", + "paused": false, + "reflection": false, + "type": "Live", + "velocityFactor": 0.66, + "visible": true + } + ] + }, + "type": "Spectrum Analyzer" + } + ], + "Reference": { + "Mode": "Internal", + "Output": "Off" + }, + "activeMode": "Vector Network Analyzer", + "version": "1.5.0-alpha.1-329f4487e" +} diff --git a/Software/PC_Application/LibreVNA-Test/LibreVNA-Test.pro b/Software/PC_Application/LibreVNA-Test/LibreVNA-Test.pro index e355957..cb96374 100644 --- a/Software/PC_Application/LibreVNA-Test/LibreVNA-Test.pro +++ b/Software/PC_Application/LibreVNA-Test/LibreVNA-Test.pro @@ -40,7 +40,6 @@ SOURCES += \ ../LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp \ ../LibreVNA-GUI/SpectrumAnalyzer/tracewidgetsa.cpp \ ../LibreVNA-GUI/Tools/eseries.cpp \ - ../LibreVNA-GUI/Tools/eyediagramdialog.cpp \ ../LibreVNA-GUI/Tools/impedancematchdialog.cpp \ ../LibreVNA-GUI/Tools/parameters.cpp \ ../LibreVNA-GUI/Traces/Marker/marker.cpp \ @@ -91,6 +90,7 @@ SOURCES += \ ../LibreVNA-GUI/Traces/Math/timegate.cpp \ ../LibreVNA-GUI/Traces/Math/tracemath.cpp \ ../LibreVNA-GUI/Traces/Math/windowfunction.cpp \ + ../LibreVNA-GUI/Traces/eyediagramplot.cpp \ ../LibreVNA-GUI/Traces/fftcomplex.cpp \ ../LibreVNA-GUI/Traces/sparamtraceselector.cpp \ ../LibreVNA-GUI/Traces/trace.cpp \ @@ -203,7 +203,6 @@ HEADERS += \ ../LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.h \ ../LibreVNA-GUI/SpectrumAnalyzer/tracewidgetsa.h \ ../LibreVNA-GUI/Tools/eseries.h \ - ../LibreVNA-GUI/Tools/eyediagramdialog.h \ ../LibreVNA-GUI/Tools/impedancematchdialog.h \ ../LibreVNA-GUI/Tools/parameters.h \ ../LibreVNA-GUI/Traces/Marker/marker.h \ @@ -265,6 +264,7 @@ HEADERS += \ ../LibreVNA-GUI/Traces/Math/timegate.h \ ../LibreVNA-GUI/Traces/Math/tracemath.h \ ../LibreVNA-GUI/Traces/Math/windowfunction.h \ + ../LibreVNA-GUI/Traces/eyediagramplot.h \ ../LibreVNA-GUI/Traces/fftcomplex.h \ ../LibreVNA-GUI/Traces/sparamtraceselector.h \ ../LibreVNA-GUI/Traces/trace.h \ @@ -293,7 +293,6 @@ HEADERS += \ ../LibreVNA-GUI/VNA/Deembedding/matchingnetwork.h \ ../LibreVNA-GUI/VNA/Deembedding/portextension.h \ ../LibreVNA-GUI/VNA/Deembedding/twothru.h \ - ../LibreVNA-GUI/VNA/portextensiondialog.h \ ../LibreVNA-GUI/VNA/tracewidgetvna.h \ ../LibreVNA-GUI/VNA/vna.h \ ../LibreVNA-GUI/about.h \ @@ -343,7 +342,6 @@ FORMS += \ ../LibreVNA-GUI/Device/firmwareupdatedialog.ui \ ../LibreVNA-GUI/Device/manualcontroldialog.ui \ ../LibreVNA-GUI/Generator/signalgenwidget.ui \ - ../LibreVNA-GUI/Tools/eyediagramdialog.ui \ ../LibreVNA-GUI/Tools/impedancematchdialog.ui \ ../LibreVNA-GUI/Traces/Marker/markerwidget.ui \ ../LibreVNA-GUI/Traces/Math/dftdialog.ui \