diff --git a/Documentation/UserManual/Screenshots/GraphEyeDiagram.png b/Documentation/UserManual/Screenshots/GraphEyeDiagram.png new file mode 100644 index 0000000..422dac8 Binary files /dev/null and b/Documentation/UserManual/Screenshots/GraphEyeDiagram.png differ diff --git a/Documentation/UserManual/Screenshots/GraphEyeDiagramSetup.png b/Documentation/UserManual/Screenshots/GraphEyeDiagramSetup.png new file mode 100644 index 0000000..f05c201 Binary files /dev/null and b/Documentation/UserManual/Screenshots/GraphEyeDiagramSetup.png differ diff --git a/Documentation/UserManual/Screenshots/GraphXYplotSetup.png b/Documentation/UserManual/Screenshots/GraphXYplotSetup.png index fcbcb59..85479b4 100644 Binary files a/Documentation/UserManual/Screenshots/GraphXYplotSetup.png and b/Documentation/UserManual/Screenshots/GraphXYplotSetup.png differ diff --git a/Documentation/UserManual/manual.pdf b/Documentation/UserManual/manual.pdf index 42f3ff6..c298146 100644 Binary files a/Documentation/UserManual/manual.pdf and b/Documentation/UserManual/manual.pdf differ diff --git a/Documentation/UserManual/manual.tex b/Documentation/UserManual/manual.tex index bce319f..e59022f 100644 --- a/Documentation/UserManual/manual.tex +++ b/Documentation/UserManual/manual.tex @@ -1029,7 +1029,7 @@ Different types are also available for the Y-axes. The Y-axis type determines ho \hline Inductance & Extracted inductance from a reflection measurement\\ \hline - Quality Facotr & Quality Factor of the impedance from a reflection measurement\\ + Quality Factor & Quality Factor of the impedance from a reflection measurement\\ \hline Group Delay & Group Delay of a transmission measurement\\ \hline @@ -1137,6 +1137,11 @@ Both plots are still completely independent of each other. For the alignment to The Polar Chart looks similar to the smithchart but doesn't perform the transformation from S-paramter to impedance. Furthermore, through measurements can be displayed as well. The available settings are identical to the smithchart but the Polar Chart does not support adding custom constant lines: \screenshot{0.6}{GraphPolarchartSetup.png} +\subsection{Eye Diagram} +\screenshot{1.0}{GraphEyeDiagram.png} +The eye diagram graph shows how a simulated signal would look like after being passed through a transmission line. The transmission line is created from a through measurement (e.g. S21). The simulated signal is a PRBS sequence with additional noise, jitter and limited rise and fall times. All parameters can be edited in the setup dialog: +\screenshot{1.0}{GraphEyeDiagramSetup.png} + \section{Markers} Markers provide an easy read-out of trace data at specific points. Each marker is assigned to one trace and will show up on any graph that show the trace at the marker position. @@ -1234,7 +1239,7 @@ This marker type is only available for through measurements on power sweeps. It \subsection{Marker Data} The trace data at the marker position can be displayed in the marker dock and on the graphs in various formats. The available formats depend on the marker type as well as the domain of the trace data. Only one of the available formats can be displayed in the marker dock at a time. On graphs, any amount of formats can be displayed at once. The shown formats can be selected in the context menu of the marker. - +\begin{footnotesize} \begin{center} \begin{threeparttable} \begin{tabularx}{\textwidth}{L{3cm}|X|L{7cm}} @@ -1303,6 +1308,8 @@ $Quality factor$\\ & & dB + angle\\ \cline{3-3} & & Real/Imaginary \\ + \cline{3-3} + & & Group Delay \\ \cline{3-3} & & \multirow{6}{*}{$\left.\begin{array}{l} $Impedance$\\ @@ -1355,6 +1362,7 @@ $Quality factor$\\ \end{tabularx} \end{threeparttable} \end{center} +\end{footnotesize} \subsection{Linking markers} \label{marker:linking} diff --git a/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp index aee235c..b1e4d16 100644 --- a/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -1249,8 +1249,8 @@ void SpectrumAnalyzer::createDefaultTracesAndGraphs(int ports) { tiles->clear(); auto traceXY = new TraceXYPlot(traceModel); - traceXY->setYAxis(0, YAxis::Type::Magnitude, false, false, -120,0,10); - traceXY->setYAxis(1, YAxis::Type::Disabled, false, true, 0,0,1); + traceXY->setYAxis(0, YAxis::Type::Magnitude, false, false, -120,0,12,false); + traceXY->setYAxis(1, YAxis::Type::Disabled, false, true, 0, 0, 10, false); traceXY->updateSpan(settings.freqStart, settings.freqStop); tiles->setPlot(traceXY); diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.cpp index eb5b63c..c50bfb4 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.cpp @@ -127,6 +127,7 @@ QString Marker::formatToString(Marker::Format f) case Format::Capacitance: return "Capacitance"; case Format::Inductance: return "Inductance"; case Format::QualityFactor: return "Quality Factor"; + case Format::GroupDelay: return "Group Delay"; case Format::TOI: return "Third order intercept"; case Format::AvgTone: return "Average Tone Level"; case Format::AvgModulationProduct: return "Average Modulation Product Level"; @@ -234,6 +235,7 @@ std::vector Marker::applicableFormats() ret.push_back(Format::dB); ret.push_back(Format::dBAngle); ret.push_back(Format::RealImag); + ret.push_back(Format::GroupDelay); } if(parentTrace) { if(parentTrace->isReflection()) { @@ -346,6 +348,9 @@ std::vector Marker::defaultActiveFormats() if(pref.Marker.defaultBehavior.showQualityFactor) { ret.push_back(Format::QualityFactor); } + if(pref.Marker.defaultBehavior.showGroupDelay) { + ret.push_back(Format::GroupDelay); + } if(pref.Marker.defaultBehavior.showNoise) { ret.push_back(Format::Noise); } @@ -477,6 +482,7 @@ QString Marker::readableData(Format f) case Format::Capacitance: return "Δ:"+Unit::ToString(Util::SparamToCapacitance(data, position, trace()->getReferenceImpedance()) - Util::SparamToCapacitance(delta->data, delta->position, trace()->getReferenceImpedance()), "F", "pnum ", 4); case Format::Inductance: return "Δ:"+Unit::ToString(Util::SparamToInductance(data, position, trace()->getReferenceImpedance()) - Util::SparamToInductance(delta->data, delta->position, trace()->getReferenceImpedance()), "H", "pnum ", 4); case Format::QualityFactor: return "ΔQ:" + Unit::ToString(Util::SparamToQualityFactor(data) - Util::SparamToQualityFactor(delta->data), "", " ", 3); + case Format::GroupDelay: return "Δτg:"+Unit::ToString(trace()->getGroupDelay(position) - delta->trace()->getGroupDelay(delta->position), "s", "pnum ", 4); case Format::Noise: return "Δ:"+Unit::ToString(parentTrace->getNoise(position) - delta->parentTrace->getNoise(delta->position), "dbm/Hz", " ", 3); default: return "Invalid"; } @@ -501,6 +507,7 @@ QString Marker::readableData(Format f) case Format::Capacitance: return Unit::ToString(Util::SparamToCapacitance(data, position, trace()->getReferenceImpedance()), "F", "pnum ", 4); case Format::Inductance: return Unit::ToString(Util::SparamToInductance(data, position, trace()->getReferenceImpedance()), "H", "pnum ", 4); case Format::QualityFactor: return "Q:" + Unit::ToString(Util::SparamToQualityFactor(data), "", " ", 3); + case Format::GroupDelay: return "τg:"+Unit::ToString(trace()->getGroupDelay(position), "s", "pnum ", 4); case Format::Noise: return Unit::ToString(parentTrace->getNoise(position), "dbm/Hz", " ", 3); case Format::TOI: { auto avgFundamental = (helperMarkers[0]->toDecibel() + helperMarkers[1]->toDecibel()) / 2; diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.h b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.h index 5f1ac47..7ebac8e 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Marker/marker.h @@ -34,6 +34,7 @@ public: Capacitance, Inductance, QualityFactor, + GroupDelay, // Noise marker parameters Noise, PhaseNoise, diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagrameditdialog.ui b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagrameditdialog.ui index 0485f4e..69f035a 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagrameditdialog.ui +++ b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagrameditdialog.ui @@ -32,7 +32,7 @@ X Axis - + @@ -75,7 +75,18 @@ - + + + + + + + + Auto + + + + @@ -85,7 +96,7 @@ Y Axis - + @@ -128,7 +139,18 @@ - + + + + + + + + Auto + + + + diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp index 118f990..b19e2fb 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp @@ -45,8 +45,8 @@ EyeDiagramPlot::EyeDiagramPlot(TraceModel &model, QWidget *parent) 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); + xAxis.set(XAxis::Type::Time, false, true, 0, 0.000001, 10, true); + yAxis.set(YAxis::Type::Real, false, true, -1, 1, 10, true); initializeTraceInfo(); destructing = false; @@ -101,10 +101,10 @@ void EyeDiagramPlot::enableTrace(Trace *t, bool enabled) void EyeDiagramPlot::replot() { if(xAxis.getAutorange()) { - xAxis.set(xAxis.getType(), false, true, 0, calculatedTime(), 8); + xAxis.set(xAxis.getType(), false, true, 0, calculatedTime(), 10, false); } if(yAxis.getAutorange()) { - yAxis.set(yAxis.getType(), false, true, minDisplayVoltage(), maxDisplayVoltage(), 8); + yAxis.set(yAxis.getType(), false, true, minDisplayVoltage(), maxDisplayVoltage(), 10, false); } TracePlot::replot(); } @@ -115,13 +115,13 @@ void EyeDiagramPlot::move(const QPoint &vect) // 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()); + xAxis.set(xAxis.getType(), false, false, xAxis.getRangeMin() - distance, xAxis.getRangeMax() - distance, xAxis.getDivs(), xAxis.getAutoDivs()); } 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()); + yAxis.set(yAxis.getType(), false, false, yAxis.getRangeMin() - distance, yAxis.getRangeMax() - distance, yAxis.getDivs(), yAxis.getAutoDivs()); } replot(); } @@ -134,7 +134,7 @@ void EyeDiagramPlot::zoom(const QPoint ¢er, double factor, bool horizontally 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); + xAxis.set(xAxis.getType(), false, false, min, max, xAxis.getDivs(), xAxis.getAutoDivs()); } if(vertically) { // can only move axis in linear mode @@ -142,7 +142,7 @@ void EyeDiagramPlot::zoom(const QPoint ¢er, double factor, bool horizontally 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); + yAxis.set(yAxis.getType(), false, false, min, max, yAxis.getDivs(), yAxis.getAutoDivs()); } replot(); } @@ -150,10 +150,10 @@ void EyeDiagramPlot::zoom(const QPoint ¢er, double factor, bool horizontally void EyeDiagramPlot::setAuto(bool horizontally, bool vertically) { if(horizontally) { - xAxis.set(xAxis.getType(), xAxis.getLog(), true, xAxis.getRangeMin(), xAxis.getRangeMax(), xAxis.getRangeDiv()); + xAxis.set(xAxis.getType(), xAxis.getLog(), true, xAxis.getRangeMin(), xAxis.getRangeMax(), xAxis.getDivs(), xAxis.getAutoDivs()); } if(vertically) { - yAxis.set(yAxis.getType(), yAxis.getLog(), true, yAxis.getRangeMin(), yAxis.getRangeMax(), yAxis.getRangeDiv()); + yAxis.set(yAxis.getType(), yAxis.getLog(), true, yAxis.getRangeMin(), yAxis.getRangeMax(), yAxis.getDivs(), yAxis.getAutoDivs()); } replot(); } @@ -164,15 +164,25 @@ void EyeDiagramPlot::fromJSON(nlohmann::json j) 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); + double xDivs = jX.value("divs", xAxis.getDivs()); + // older formats specified the spacing instead of the number of divisions + if(jX.contains("div")) { + xDivs = (xMax - xMin) / jX.value("div", (xMax - xMin) / xDivs); + } + auto xautodivs = jX.value("autoDivs", false); + xAxis.set(xAxis.getType(), false, xAuto, xMin, xMax, xDivs, xautodivs); 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); + double yDivs = jY.value("divs", yAxis.getDivs()); + // older formats specified the spacing instead of the number of divisions + if(jY.contains("div")) { + yDivs = (yMax - yMin) / jY.value("div", (yMax - yMin) / yDivs); + } + auto yautodivs = jY.value("autoDivs", false); + yAxis.set(yAxis.getType(), false, yAuto, yMin, yMax, yDivs, yautodivs); datarate = j.value("datarate", datarate); risetime = j.value("risetime", risetime); @@ -211,13 +221,15 @@ nlohmann::json EyeDiagramPlot::toJSON() jX["autorange"] = yAxis.getAutorange(); jX["min"] = xAxis.getRangeMin(); jX["max"] = xAxis.getRangeMax(); - jX["div"] = xAxis.getRangeDiv(); + jX["divs"] = xAxis.getDivs(); + jX["autoDivs"] = xAxis.getAutoDivs(); j["XAxis"] = jX; nlohmann::json jY; jY["autorange"] = yAxis.getAutorange(); jY["min"] = yAxis.getRangeMin(); jY["max"] = yAxis.getRangeMax(); - jY["div"] = yAxis.getRangeDiv(); + jY["divs"] = yAxis.getDivs(); + jY["autoDivs"] = yAxis.getAutoDivs(); j["YAxis"] = jY; nlohmann::json jtraces; for(auto t : traces) { @@ -286,10 +298,6 @@ void EyeDiagramPlot::axisSetupDialog() 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); @@ -298,10 +306,6 @@ void EyeDiagramPlot::axisSetupDialog() 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); @@ -322,22 +326,30 @@ void EyeDiagramPlot::axisSetupDialog() connect(ui->Xauto, &QCheckBox::toggled, [=](bool checked) { ui->Xmin->setEnabled(!checked); ui->Xmax->setEnabled(!checked); + ui->Xdivs->setEnabled(!checked && !ui->XautoDivs->isChecked()); + ui->XautoDivs->setEnabled(!checked); + }); + connect(ui->XautoDivs, &QCheckBox::toggled, [=](bool 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()); + ui->Xdivs->setValue(xAxis.getDivs()); connect(ui->Yauto, &QCheckBox::toggled, [=](bool checked) { ui->Ymin->setEnabled(!checked); ui->Ymax->setEnabled(!checked); + ui->Ydivs->setEnabled(!checked && !ui->YautoDivs->isChecked()); + ui->YautoDivs->setEnabled(!checked); + }); + connect(ui->YautoDivs, &QCheckBox::toggled, [=](bool 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()); + ui->Ydivs->setValue(yAxis.getDivs()); auto updateValues = [=](){ std::lock_guard guard(calcMutex); @@ -357,8 +369,8 @@ void EyeDiagramPlot::axisSetupDialog() xSamples = ui->pointsPerCycle->value(); traceBlurring = ui->traceBlurring->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()); + xAxis.set(xAxis.getType(), false, ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value(), ui->XautoDivs->isChecked()); + yAxis.set(yAxis.getType(), false, ui->Yauto->isChecked(), ui->Ymin->value(), ui->Ymax->value(), ui->Ydivs->value(), ui->YautoDivs->isChecked()); }; connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [=](){ diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp index 9267dc4..4a251a8 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp @@ -1572,6 +1572,48 @@ double Trace::getNoise(double frequency) return dbm; } +double Trace::getGroupDelay(double frequency) +{ + if(!isVNAParameter(liveParam) || lastMath->getDataType() != DataType::Frequency) { + // data not suitable for group delay calculation + return std::numeric_limits::quiet_NaN(); + } + + // get index that matches frequency best + unsigned int sample = index(frequency); + + auto &p = Preferences::getInstance(); + const unsigned int requiredSamples = p.Acquisition.groupDelaySamples; + if(size() < requiredSamples) { + // unable to calculate + return std::numeric_limits::quiet_NaN(); + + } + // needs at least some samples before/after current sample for calculating the derivative. + // For samples too far at either end of the trace, return group delay of "inner" trace sample instead + if(sample < requiredSamples / 2) { + sample = requiredSamples / 2; + } else if(sample >= size() - requiredSamples / 2) { + sample = size() - requiredSamples / 2 - 1; + } + + // got enough samples at either end to calculate derivative. + // acquire phases of the required samples + std::vector phases; + phases.reserve(requiredSamples); + for(unsigned int index = sample - requiredSamples / 2;index <= sample + requiredSamples / 2;index++) { + phases.push_back(arg(this->sample(index).y)); + } + // make sure there are no phase jumps + Util::unwrapPhase(phases); + // calculate linearRegression to get derivative + double B_0, B_1; + Util::linearRegression(phases, B_0, B_1); + // B_1 now contains the derived phase vs. the sample. Scale by frequency to get group delay + double freq_step = this->sample(sample).x - this->sample(sample - 1).x; + return -B_1 / (2.0*M_PI * freq_step); +} + int Trace::index(double x) { auto lower = lower_bound(lastMath->rData().begin(), lastMath->rData().end(), x, [](const Data &lhs, const double x) -> bool { diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/trace.h b/Software/PC_Application/LibreVNA-GUI/Traces/trace.h index 8582c55..69fe0ab 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/trace.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/trace.h @@ -104,6 +104,7 @@ public: unsigned int getFileParameter() const; /* Returns the noise in dbm/Hz for spectrum analyzer measurements. May return NaN if calculation not possible */ double getNoise(double frequency); + double getGroupDelay(double frequency); int index(double x); std::set getMarkers() const; void setCalibration(); diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/traceaxis.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/traceaxis.cpp index 9af22b3..d4b07d1 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/traceaxis.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/traceaxis.cpp @@ -1,23 +1,24 @@ #include "traceaxis.h" #include "Util/util.h" +#include "preferences.h" #include using namespace std; -static void createEvenlySpacedTicks(vector& ticks, double start, double stop, double step) { +static void createEvenlySpacedTicks(vector& ticks, double start, double stop, unsigned int divisions) { ticks.clear(); if(start > stop) { swap(start, stop); } - step = abs(step); + auto step = (stop - start) / divisions; constexpr unsigned int maxTicks = 100; for(double tick = start; tick - stop < numeric_limits::epsilon() && ticks.size() <= maxTicks;tick+= step) { ticks.push_back(tick); } } -static double createAutomaticTicks(vector& ticks, double start, double stop, int minDivisions) { +static unsigned int createAutomaticTicks(vector& ticks, double start, double stop, int minDivisions) { Q_ASSERT(stop > start); ticks.clear(); double max_div_step = (stop - start) / minDivisions; @@ -37,7 +38,7 @@ static double createAutomaticTicks(vector& ticks, double start, double s for(double tick = start_div;tick <= stop;tick += div_step) { ticks.push_back(tick); } - return div_step; + return (stop - start) / div_step; } static void createLogarithmicTicks(vector& ticks, double start, double stop, int minDivisions) { @@ -132,37 +133,11 @@ double YAxis::sampleToCoordinate(Trace::Data data, Trace *t, unsigned int sample return Util::SparamToInductance(data.y, data.x); case YAxis::Type::QualityFactor: return Util::SparamToQualityFactor(data.y); - case YAxis::Type::GroupDelay: { - constexpr int requiredSamples = 5; - if(!t || t->size() < requiredSamples) { - // unable to calculate + case YAxis::Type::GroupDelay: + if(!t) { return 0.0; - } - // needs at least some samples before/after current sample for calculating the derivative. - // For samples too far at either end of the trace, return group delay of "inner" trace sample instead - if(sample < requiredSamples / 2) { - return sampleToCoordinate(data, t, requiredSamples / 2); - } else if(sample >= t->size() - requiredSamples / 2) { - return sampleToCoordinate(data, t, t->size() - requiredSamples / 2 - 1); - } else { - // got enough samples at either end to calculate derivative. - // acquire phases of the required samples - std::vector phases; - phases.reserve(requiredSamples); - for(unsigned int index = sample - requiredSamples / 2;index <= sample + requiredSamples / 2;index++) { - phases.push_back(arg(t->sample(index).y)); - } - // make sure there are no phase jumps - Util::unwrapPhase(phases); - // calculate linearRegression to get derivative - double B_0, B_1; - Util::linearRegression(phases, B_0, B_1); - // B_1 now contains the derived phase vs. the sample. Scale by frequency to get group delay - double freq_step = t->sample(sample).x - t->sample(sample - 1).x; - return -B_1 / (2.0*M_PI * freq_step); - } - } + return t->getGroupDelay(data.x); case YAxis::Type::ImpulseReal: return real(data.y); case YAxis::Type::ImpulseMag: @@ -190,14 +165,15 @@ double YAxis::sampleToCoordinate(Trace::Data data, Trace *t, unsigned int sample return 0.0; } -void YAxis::set(Type type, bool log, bool autorange, double min, double max, double div) +void YAxis::set(Type type, bool log, bool autorange, double min, double max, unsigned int divs, bool autoDivs) { this->type = type; this->log = log; this->autorange = autorange; this->rangeMin = min; this->rangeMax = max; - this->rangeDiv = div; + this->divs = divs; + this->autoDivs = autoDivs; if(type != Type::Disabled) { updateTicks(); } @@ -440,9 +416,13 @@ void Axis::updateTicks() rangeMin -= 1.0; rangeMax += 1.0; } - rangeDiv = createAutomaticTicks(ticks, rangeMin, rangeMax, 8); + divs = createAutomaticTicks(ticks, rangeMin, rangeMax, 8); } else { - createEvenlySpacedTicks(ticks, rangeMin, rangeMax, rangeDiv); + if(autoDivs) { + divs = createAutomaticTicks(ticks, rangeMin, rangeMax, 8); + } else { + createEvenlySpacedTicks(ticks, rangeMin, rangeMax, divs); + } } } @@ -451,9 +431,14 @@ const std::vector &Axis::getTicks() const return ticks; } -double Axis::getRangeDiv() const +unsigned int Axis::getDivs() const { - return rangeDiv; + return divs; +} + +bool Axis::getAutoDivs() const +{ + return autoDivs; } double Axis::getRangeMax() const @@ -495,7 +480,7 @@ double XAxis::sampleToCoordinate(Trace::Data data, Trace *t, unsigned int sample } } -void XAxis::set(Type type, bool log, bool autorange, double min, double max, double div) +void XAxis::set(Type type, bool log, bool autorange, double min, double max, unsigned int divs, bool autoDivs) { if(max <= min) { auto info = DeviceDriver::getInfo(DeviceDriver::getActiveDriver()); @@ -529,7 +514,8 @@ void XAxis::set(Type type, bool log, bool autorange, double min, double max, dou this->autorange = autorange; this->rangeMin = min; this->rangeMax = max; - this->rangeDiv = div; + this->divs = divs; + this->autoDivs = autoDivs; updateTicks(); } @@ -589,7 +575,8 @@ Axis::Axis() autorange = true; rangeMin = -1.0; rangeMax = 1.0; - rangeDiv = 1.0; + divs = 10; + autoDivs = false; ticks.clear(); } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/traceaxis.h b/Software/PC_Application/LibreVNA-GUI/Traces/traceaxis.h index dfde29a..8f67c08 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/traceaxis.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/traceaxis.h @@ -17,7 +17,8 @@ public: bool getAutorange() const; double getRangeMin() const; double getRangeMax() const; - double getRangeDiv() const; + unsigned int getDivs() const; + bool getAutoDivs() const; const std::vector &getTicks() const; protected: @@ -26,7 +27,8 @@ protected: bool autorange; double rangeMin; double rangeMax; - double rangeDiv; + unsigned int divs; + bool autoDivs; std::vector ticks; }; @@ -42,7 +44,7 @@ public: }; XAxis(); double sampleToCoordinate(Trace::Data data, Trace *t = nullptr, unsigned int sample = 0) override; - void set(Type type, bool log, bool autorange, double min, double max, double div); + void set(Type type, bool log, bool autorange, double min, double max, unsigned int divs, bool autoDivs); static QString TypeToName(Type type); static Type TypeFromName(QString name); static QString Unit(Type type); @@ -88,7 +90,7 @@ public: YAxis(); double sampleToCoordinate(Trace::Data data, Trace *t = nullptr, unsigned int sample = 0) override; - void set(Type type, bool log, bool autorange, double min, double max, double div); + void set(Type type, bool log, bool autorange, double min, double max, unsigned int divs, bool autoDivs); static QString TypeToName(Type type); static Type TypeFromName(QString name); static QString Unit(Type type, TraceModel::DataSource source = TraceModel::DataSource::VNA); diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp index bb808c6..5f92a90 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp @@ -94,7 +94,7 @@ void TraceCSVExport::on_buttonBox_accepted() for(auto trace : traces) { for(auto ytype : getSelectedYAxisTypes()) { auto axis = YAxis(); - axis.set(ytype, false, false, 0, 1, 1); + axis.set(ytype, false, false, 0, 1, 10, false); auto samples = trace->numSamples(); vector values; for(unsigned int i=0;ioutputType()) { case Trace::DataType::Frequency: - xAxis.set(XAxis::Type::Frequency, false, true, 0, 1, 0.1); + xAxis.set(XAxis::Type::Frequency, false, true, 0, 1, 10, false); yDefault = YAxis::Type::Magnitude; break; case Trace::DataType::Power: - xAxis.set(XAxis::Type::Power, false, true, 0, 1, 0.1); + xAxis.set(XAxis::Type::Power, false, true, 0, 1, 10, false); yDefault = YAxis::Type::Magnitude; break; case Trace::DataType::Time: @@ -177,7 +177,7 @@ bool TraceWaterfall::configureForTrace(Trace *t) return false; } if(!yAxis.isSupported(xAxis.getType(), getModel().getSource())) { - yAxis.set(yDefault, false, true, 0, 1, 1.0); + yAxis.set(yDefault, false, true, 0, 1, 10, false); } traceRemovalPending = true; return true; @@ -556,7 +556,7 @@ void TraceWaterfall::traceDataChanged(unsigned int begin, unsigned int end) if(min_x != xAxis.getRangeMin() || max_x != xAxis.getRangeMax()) { resetWaterfall(); // adjust axis - xAxis.set(xAxis.getType(), xAxis.getLog(), true, min_x, max_x, 0); + xAxis.set(xAxis.getType(), xAxis.getLog(), true, min_x, max_x, xAxis.getDivs(), xAxis.getAutoDivs()); } } bool YAxisUpdateRequired = false; @@ -593,7 +593,7 @@ void TraceWaterfall::traceDataChanged(unsigned int begin, unsigned int end) } if(yAxis.getAutorange() && !YAxisUpdateRequired && (min != yAxis.getRangeMin() || max != yAxis.getRangeMax())) { // axis scaling needs update due to new trace data - yAxis.set(yAxis.getType(), yAxis.getLog(), true, min, max, 0); + yAxis.set(yAxis.getType(), yAxis.getLog(), true, min, max, yAxis.getDivs(), yAxis.getAutoDivs()); } else if(YAxisUpdateRequired) { updateYAxis(); } @@ -619,7 +619,7 @@ void TraceWaterfall::updateYAxis() } } if(max > min) { - yAxis.set(yAxis.getType(), yAxis.getLog(), true, min, max, 0); + yAxis.set(yAxis.getType(), yAxis.getLog(), true, min, max, yAxis.getDivs(), yAxis.getAutoDivs()); } } } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp index 0974d46..1656946 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.cpp @@ -25,11 +25,11 @@ TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent) xAxisMode = XAxisMode::UseSpan; // Setup default axis - setYAxis(0, YAxis::Type::Magnitude, false, false, -120, 20, 10); - setYAxis(1, YAxis::Type::Phase, false, false, -180, 180, 30); + setYAxis(0, YAxis::Type::Magnitude, false, false, -120, 20, 14, true); + setYAxis(1, YAxis::Type::Phase, false, false, -180, 180, 12, true); // enable autoscaling and set for full span (no information about actual span available yet) updateSpan(0, 6000000000); - setXAxis(XAxis::Type::Frequency, XAxisMode::UseSpan, false, 0, 6000000000, 600000000); + setXAxis(XAxis::Type::Frequency, XAxisMode::UseSpan, false, 0, 6000000000, 10, true); initializeTraceInfo(); } TraceXYPlot::~TraceXYPlot() @@ -39,7 +39,7 @@ TraceXYPlot::~TraceXYPlot() } } -void TraceXYPlot::setYAxis(int axis, YAxis::Type type, bool log, bool autorange, double min, double max, double div) +void TraceXYPlot::setYAxis(int axis, YAxis::Type type, bool log, bool autorange, double min, double max, unsigned int divs, bool autoDivs) { if(yAxis[axis].getType() != type) { // remove traces that are active but not supported with the new axis type @@ -55,19 +55,19 @@ void TraceXYPlot::setYAxis(int axis, YAxis::Type type, bool log, bool autorange, } } while(erased); } - yAxis[axis].set(type, log, autorange, min, max, div); + yAxis[axis].set(type, log, autorange, min, max, divs, autoDivs); traceRemovalPending = true; updateContextMenu(); replot(); } -void TraceXYPlot::setXAxis(XAxis::Type type, XAxisMode mode, bool log, double min, double max, double div) +void TraceXYPlot::setXAxis(XAxis::Type type, XAxisMode mode, bool log, double min, double max, unsigned int divs, bool autoDivs) { bool autorange = false; if(mode == XAxisMode::FitTraces || mode == XAxisMode::UseSpan) { autorange = true; } - xAxis.set(type, log, autorange, min, max, div); + xAxis.set(type, log, autorange, min, max, divs, autoDivs); xAxisMode = mode; traceRemovalPending = true; updateContextMenu(); @@ -101,7 +101,7 @@ void TraceXYPlot::move(const QPoint &vect) // 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()); + xAxis.set(xAxis.getType(), false, false, xAxis.getRangeMin() - distance, xAxis.getRangeMax() - distance, xAxis.getDivs(), xAxis.getAutoDivs()); xAxisMode = XAxisMode::Manual; } for(int i=0;i<2;i++) { @@ -109,7 +109,7 @@ void TraceXYPlot::move(const QPoint &vect) // 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()); + yAxis[i].set(yAxis[i].getType(), false, false, yAxis[i].getRangeMin() - distance, yAxis[i].getRangeMax() - distance, yAxis[i].getDivs(), yAxis[i].getAutoDivs()); } } replot(); @@ -123,7 +123,7 @@ void TraceXYPlot::zoom(const QPoint ¢er, double factor, bool horizontally, b 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); + xAxis.set(xAxis.getType(), false, false, min, max, xAxis.getDivs(), xAxis.getAutoDivs()); xAxisMode = XAxisMode::Manual; } for(int i=0;i<2;i++) { @@ -133,7 +133,7 @@ void TraceXYPlot::zoom(const QPoint ¢er, double factor, bool horizontally, b 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); + yAxis[i].set(yAxis[i].getType(), false, false, min, max, yAxis[i].getDivs(), yAxis[i].getAutoDivs()); } } replot(); @@ -143,11 +143,11 @@ 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()); + xAxis.set(xAxis.getType(), xAxis.getLog(), true, xAxis.getRangeMin(), xAxis.getRangeMax(), xAxis.getDivs(), xAxis.getAutoDivs()); } 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()); + yAxis[i].set(yAxis[i].getType(), yAxis[i].getLog(), true, yAxis[i].getRangeMin(), yAxis[i].getRangeMax(), yAxis[i].getDivs(), yAxis[i].getAutoDivs()); } } replot(); @@ -162,7 +162,8 @@ nlohmann::json TraceXYPlot::toJSON() jX["log"] = xAxis.getLog(); jX["min"] = xAxis.getRangeMin(); jX["max"] = xAxis.getRangeMax(); - jX["div"] = xAxis.getRangeDiv(); + jX["divs"] = xAxis.getDivs(); + jX["autoDivs"] = xAxis.getAutoDivs(); j["XAxis"] = jX; for(unsigned int i=0;i<2;i++) { nlohmann::json jY; @@ -171,7 +172,8 @@ nlohmann::json TraceXYPlot::toJSON() jY["autorange"] = yAxis[i].getAutorange(); jY["min"] = yAxis[i].getRangeMin(); jY["max"] = yAxis[i].getRangeMax(); - jY["div"] = yAxis[i].getRangeDiv(); + jY["divs"] = yAxis[i].getDivs(); + jY["autoDivs"] = yAxis[i].getAutoDivs(); nlohmann::json jtraces; for(auto t : tracesAxis[i]) { jtraces.push_back(t->toHash()); @@ -212,9 +214,14 @@ void TraceXYPlot::fromJSON(nlohmann::json j) // auto xlog = jX.value("log", false); auto xmin = jX.value("min", 0.0); auto xmax = jX.value("max", 6000000000.0); - auto xdiv = jX.value("div", 600000000.0); + auto xdivs = jX.value("divs", 10); + // older formats specified the spacing instead of the number of divisions + if(jX.contains("div")) { + xdivs = (xmax - xmin) / jX.value("div", 600000000.0); + } + auto xautodivs = jX.value("autoDivs", false); auto xlog = jX.value("log", false); - setXAxis(xtype, xmode, xlog, xmin, xmax, xdiv); + setXAxis(xtype, xmode, xlog, xmin, xmax, xdivs, xautodivs); nlohmann::json jY[2] = {j["YPrimary"], j["YSecondary"]}; for(unsigned int i=0;i<2;i++) { YAxis::Type ytype; @@ -227,8 +234,13 @@ void TraceXYPlot::fromJSON(nlohmann::json j) auto ylog = jY[i].value("log", false); auto ymin = jY[i].value("min", -120.0); auto ymax = jY[i].value("max", 20.0); - auto ydiv = jY[i].value("div", 10.0); - setYAxis(i, ytype, ylog, yauto, ymin, ymax, ydiv); + auto ydivs = jY[i].value("divs", 10); + // older formats specified the spacing instead of the number of divisions + if(jY[i].contains("div")) { + ydivs = (ymax - ymin) / jY[i].value("div", 10); + } + auto yautodivs = jY[i].value("autoDivs", false); + setYAxis(i, ytype, ylog, yauto, ymin, ymax, ydivs, yautodivs); for(unsigned int hash : jY[i]["traces"]) { // attempt to find the traces with this hash bool found = false; @@ -292,21 +304,21 @@ bool TraceXYPlot::configureForTrace(Trace *t) switch(t->outputType()) { case Trace::DataType::Frequency: - setXAxis(XAxis::Type::Frequency, XAxisMode::FitTraces, false, 0, 1, 0.1); + setXAxis(XAxis::Type::Frequency, XAxisMode::FitTraces, false, 0, 1, 10, false); yLeftDefault = YAxis::Type::Magnitude; yRightDefault = YAxis::Type::Phase; break; case Trace::DataType::Time: - setXAxis(XAxis::Type::Time, XAxisMode::FitTraces, false, 0, 1, 0.1); + setXAxis(XAxis::Type::Time, XAxisMode::FitTraces, false, 0, 1, 10, false); yLeftDefault = YAxis::Type::ImpulseMag; break; case Trace::DataType::Power: - setXAxis(XAxis::Type::Power, XAxisMode::FitTraces, false, 0, 1, 0.1); + setXAxis(XAxis::Type::Power, XAxisMode::FitTraces, false, 0, 1, 10, false); yLeftDefault = YAxis::Type::Magnitude; yRightDefault = YAxis::Type::Phase; break; case Trace::DataType::TimeZeroSpan: - setXAxis(XAxis::Type::TimeZeroSpan, XAxisMode::FitTraces, false, 0, 1, 0.1); + setXAxis(XAxis::Type::TimeZeroSpan, XAxisMode::FitTraces, false, 0, 1, 10, false); yLeftDefault = YAxis::Type::Magnitude; yRightDefault = YAxis::Type::Phase; break; @@ -315,10 +327,10 @@ bool TraceXYPlot::configureForTrace(Trace *t) return false; } if(!yAxis[0].isSupported(xAxis.getType(), getModel().getSource())) { - setYAxis(0, yLeftDefault, false, true, 0, 1, 1.0); + setYAxis(0, yLeftDefault, false, true, 0, 1, 10, false); } if(!yAxis[1].isSupported(xAxis.getType(), getModel().getSource())) { - setYAxis(1, yRightDefault, false, true, 0, 1, 1.0); + setYAxis(1, yRightDefault, false, true, 0, 1, 10, false); } traceRemovalPending = true; return true; @@ -899,7 +911,7 @@ void TraceXYPlot::updateAxisTicks() } } } - xAxis.set(xAxis.getType(), xAxis.getLog(), true, min, max, 0); + xAxis.set(xAxis.getType(), xAxis.getLog(), true, min, max, 0, xAxis.getAutoDivs()); } for(int i=0;i<2;i++) { @@ -981,7 +993,7 @@ void TraceXYPlot::updateAxisTicks() min = 0.1; } } - yAxis[i].set(yAxis[i].getType(), yAxis[i].getLog(), true, min, max, 0); + yAxis[i].set(yAxis[i].getType(), yAxis[i].getLog(), true, min, max, 0, xAxis.getAutoDivs()); } } } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.h b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.h index 0b31b91..2230cbb 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracexyplot.h @@ -68,8 +68,8 @@ public: Last, }; - void setYAxis(int axis, YAxis::Type type, bool log, bool autorange, double min, double max, double div); - void setXAxis(XAxis::Type type, XAxisMode mode, bool log, double min, double max, double div); + void setYAxis(int axis, YAxis::Type type, bool log, bool autorange, double min, double max, unsigned int divs, bool autoDivs); + void setXAxis(XAxis::Type type, XAxisMode mode, bool log, double min, double max, unsigned int divs, bool autoDivs); void enableTrace(Trace *t, bool enabled) override; void updateSpan(double min, double max) override; void replot() override; diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/waterfallaxisdialog.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/waterfallaxisdialog.cpp index 7b18cd4..133ac81 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/waterfallaxisdialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/waterfallaxisdialog.cpp @@ -115,8 +115,8 @@ WaterfallAxisDialog::~WaterfallAxisDialog() void WaterfallAxisDialog::on_buttonBox_accepted() { // set plot values to the ones selected in the dialog - plot->xAxis.set(plot->xAxis.getType(), ui->Xlog->isChecked(), true, plot->xAxis.getRangeMin(), plot->xAxis.getRangeMax(), 0); - plot->yAxis.set((YAxis::Type) ui->Wtype->currentIndex(), ui->Wlog->isChecked(), ui->Wauto->isChecked(), ui->Wmin->value(), ui->Wmax->value(), 2); + plot->xAxis.set(plot->xAxis.getType(), ui->Xlog->isChecked(), true, plot->xAxis.getRangeMin(), plot->xAxis.getRangeMax(), 10, false); + plot->yAxis.set((YAxis::Type) ui->Wtype->currentIndex(), ui->Wlog->isChecked(), ui->Wauto->isChecked(), ui->Wmin->value(), ui->Wmax->value(), 2, false); if(ui->Wdir->currentIndex() == 0) { plot->dir = TraceWaterfall::Direction::TopToBottom; } else { diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.cpp index 512b136..2b6caf0 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.cpp @@ -49,85 +49,114 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : } // Setup GUI connections - connect(ui->Y1type, qOverload(&QComboBox::currentIndexChanged), [=](int index) { - ui->Y1log->setEnabled(index != 0); - ui->Y1linear->setEnabled(index != 0); - ui->Y1auto->setEnabled(index != 0); - bool autoRange = ui->Y1auto->isChecked(); - ui->Y1min->setEnabled(index != 0 && !autoRange); - ui->Y1max->setEnabled(index != 0 && !autoRange); - ui->Y1divs->setEnabled(index != 0 && !autoRange); - auto type = (YAxis::Type) index; - QString unit = YAxis::Unit(type); - QString prefixes = YAxis::Prefixes(type); - ui->Y1min->setUnit(unit); - ui->Y1min->setPrefixes(prefixes); - ui->Y1max->setUnit(unit); - ui->Y1max->setPrefixes(prefixes); - ui->Y1divs->setUnit(unit); - ui->Y1divs->setPrefixes(prefixes); + auto updateYenableState = [](QComboBox *type, QRadioButton *linear, QRadioButton *log, QCheckBox *CBauto, SIUnitEdit *min, SIUnitEdit *max, QSpinBox *divs, QCheckBox *autoDivs) { + if(type->currentIndex() == 0) { + // axis disabled + log->setEnabled(false); + linear->setEnabled(false); + CBauto->setEnabled(false); + } else { + // axis enabled + log->setEnabled(true); + linear->setEnabled(true); + CBauto->setEnabled(true); + if(CBauto->isChecked()) { + // autorange active, other settings disabled + min->setEnabled(false); + max->setEnabled(false); + divs->setEnabled(false); + autoDivs->setEnabled(false); + } else { + min->setEnabled(true); + max->setEnabled(true); + if(log->isChecked()) { + divs->setEnabled(false); + autoDivs->setEnabled(false); + } else { + autoDivs->setEnabled(true); + divs->setEnabled(!autoDivs->isChecked()); + } + } + } + auto t = (YAxis::Type) type->currentIndex(); + QString unit = YAxis::Unit(t); + QString prefixes = YAxis::Prefixes(t); + min->setUnit(unit); + min->setPrefixes(prefixes); + max->setUnit(unit); + max->setPrefixes(prefixes); + }; + + connect(ui->Y1type, qOverload(&QComboBox::currentIndexChanged), [this, updateYenableState](int) { + updateYenableState(ui->Y1type, ui->Y1linear, ui->Y1log, ui->Y1auto, ui->Y1min, ui->Y1max, ui->Y1Divs, ui->Y1autoDivs); }); - connect(ui->Y1auto, &QCheckBox::toggled, [this](bool checked) { - ui->Y1min->setEnabled(!checked); - ui->Y1max->setEnabled(!checked); - ui->Y1divs->setEnabled(!checked && !ui->Y1log->isChecked()); + connect(ui->Y1auto, &QCheckBox::toggled, [this, updateYenableState](bool) { + updateYenableState(ui->Y1type, ui->Y1linear, ui->Y1log, ui->Y1auto, ui->Y1min, ui->Y1max, ui->Y1Divs, ui->Y1autoDivs); }); - connect(ui->Y1log, &QCheckBox::toggled, [this](bool checked) { - ui->Y1divs->setEnabled(!checked && !ui->Y1auto->isChecked()); + connect(ui->Y1log, &QCheckBox::toggled, [this, updateYenableState](bool) { + updateYenableState(ui->Y1type, ui->Y1linear, ui->Y1log, ui->Y1auto, ui->Y1min, ui->Y1max, ui->Y1Divs, ui->Y1autoDivs); + }); + connect(ui->Y1autoDivs, &QCheckBox::toggled, [this, updateYenableState](bool) { + updateYenableState(ui->Y1type, ui->Y1linear, ui->Y1log, ui->Y1auto, ui->Y1min, ui->Y1max, ui->Y1Divs, ui->Y1autoDivs); }); - connect(ui->Y2type, qOverload(&QComboBox::currentIndexChanged), [=](int index) { - ui->Y2log->setEnabled(index != 0); - ui->Y2linear->setEnabled(index != 0); - ui->Y2auto->setEnabled(index != 0); - bool autoRange = ui->Y2auto->isChecked(); - ui->Y2min->setEnabled(index != 0 && !autoRange); - ui->Y2max->setEnabled(index != 0 && !autoRange); - ui->Y2divs->setEnabled(index != 0 && !autoRange); - auto type = (YAxis::Type) index; - QString unit = YAxis::Unit(type); - QString prefixes = YAxis::Prefixes(type); - ui->Y2min->setUnit(unit); - ui->Y2min->setPrefixes(prefixes); - ui->Y2max->setUnit(unit); - ui->Y2max->setPrefixes(prefixes); - ui->Y2divs->setUnit(unit); - ui->Y2divs->setPrefixes(prefixes); + connect(ui->Y2type, qOverload(&QComboBox::currentIndexChanged), [this, updateYenableState](int) { + updateYenableState(ui->Y2type, ui->Y2linear, ui->Y2log, ui->Y2auto, ui->Y2min, ui->Y2max, ui->Y2Divs, ui->Y2autoDivs); + }); + connect(ui->Y2auto, &QCheckBox::toggled, [this, updateYenableState](bool) { + updateYenableState(ui->Y2type, ui->Y2linear, ui->Y2log, ui->Y2auto, ui->Y2min, ui->Y2max, ui->Y2Divs, ui->Y2autoDivs); + }); + connect(ui->Y2log, &QCheckBox::toggled, [this, updateYenableState](bool) { + updateYenableState(ui->Y2type, ui->Y2linear, ui->Y2log, ui->Y2auto, ui->Y2min, ui->Y2max, ui->Y2Divs, ui->Y2autoDivs); + }); + connect(ui->Y2autoDivs, &QCheckBox::toggled, [this, updateYenableState](bool) { + updateYenableState(ui->Y2type, ui->Y2linear, ui->Y2log, ui->Y2auto, ui->Y2min, ui->Y2max, ui->Y2Divs, ui->Y2autoDivs); }); - connect(ui->Y2auto, &QCheckBox::toggled, [this](bool checked) { - ui->Y2min->setEnabled(!checked); - ui->Y2max->setEnabled(!checked); - ui->Y2divs->setEnabled(!checked && !ui->Y1log->isChecked()); - }); - connect(ui->Y2log, &QCheckBox::toggled, [this](bool checked) { - ui->Y2divs->setEnabled(!checked && !ui->Y2auto->isChecked()); - }); + auto updateXenableState = [](QRadioButton *linear, QRadioButton *log, QCheckBox *CBauto, SIUnitEdit *min, SIUnitEdit *max, QSpinBox *divs, QCheckBox *autoDivs) { + log->setEnabled(true); + linear->setEnabled(true); + CBauto->setEnabled(true); + if(CBauto->isChecked()) { + // autorange active, other settings disabled + min->setEnabled(false); + max->setEnabled(false); + divs->setEnabled(false); + autoDivs->setEnabled(false); + } else { + min->setEnabled(true); + max->setEnabled(true); + if(log->isChecked()) { + divs->setEnabled(false); + autoDivs->setEnabled(false); + } else { + autoDivs->setEnabled(true); + divs->setEnabled(!autoDivs->isChecked()); + } + } + }; - connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) { - ui->Xmin->setEnabled(!checked); - ui->Xmax->setEnabled(!checked); - ui->Xdivs->setEnabled(!checked && ui->Xlinear->isChecked()); - ui->Xautomode->setEnabled(checked); + connect(ui->Xauto, &QCheckBox::toggled, [this, updateXenableState](bool checked) { + updateXenableState(ui->Xlinear, ui->Xlog, ui->Xauto, ui->Xmin, ui->Xmax, ui->XDivs, ui->XautoDivs); + }); + connect(ui->XautoDivs, &QCheckBox::toggled, [this, updateXenableState](bool checked) { + updateXenableState(ui->Xlinear, ui->Xlog, ui->Xauto, ui->Xmin, ui->Xmax, ui->XDivs, ui->XautoDivs); }); ui->XType->setCurrentIndex((int) plot->xAxis.getType()); ui->Xmin->setPrefixes("pnum kMG"); ui->Xmax->setPrefixes("pnum kMG"); - ui->Xdivs->setPrefixes("pnum kMG"); ui->Y1min->setPrefixes("pnum kMG"); ui->Y1max->setPrefixes("pnum kMG"); - ui->Y1divs->setPrefixes("pnum kMG"); ui->Y2min->setPrefixes("pnum kMG"); ui->Y2max->setPrefixes("pnum kMG"); - ui->Y2divs->setPrefixes("pnum kMG"); XAxisTypeChanged((int) plot->xAxis.getType()); connect(ui->XType, qOverload(&QComboBox::currentIndexChanged), this, &XYplotAxisDialog::XAxisTypeChanged); connect(ui->Xlog, &QCheckBox::toggled, [=](bool checked){ - ui->Xdivs->setEnabled(!checked && !ui->Xauto->isChecked()); + updateXenableState(ui->Xlinear, ui->Xlog, ui->Xauto, ui->Xmin, ui->Xmax, ui->XDivs, ui->XautoDivs); }); // Fill initial values @@ -140,7 +169,8 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : ui->Y1auto->setChecked(plot->yAxis[0].getAutorange()); ui->Y1min->setValueQuiet(plot->yAxis[0].getRangeMin()); ui->Y1max->setValueQuiet(plot->yAxis[0].getRangeMax()); - ui->Y1divs->setValueQuiet(plot->yAxis[0].getRangeDiv()); + ui->Y1Divs->setValue(plot->yAxis[0].getDivs()); + ui->Y1autoDivs->setChecked(plot->yAxis[0].getAutoDivs()); ui->Y2type->setCurrentIndex((int) plot->yAxis[1].getType()); if(plot->yAxis[1].getLog()) { @@ -151,7 +181,8 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : ui->Y2auto->setChecked(plot->yAxis[1].getAutorange()); ui->Y2min->setValueQuiet(plot->yAxis[1].getRangeMin()); ui->Y2max->setValueQuiet(plot->yAxis[1].getRangeMax()); - ui->Y2divs->setValueQuiet(plot->yAxis[1].getRangeDiv()); + ui->Y2Divs->setValue(plot->yAxis[1].getDivs()); + ui->Y2autoDivs->setChecked(plot->yAxis[1].getAutoDivs()); if(plot->xAxis.getLog()) { ui->Xlog->setChecked(true); @@ -166,7 +197,8 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : } ui->Xmin->setValueQuiet(plot->xAxis.getRangeMin()); ui->Xmax->setValueQuiet(plot->xAxis.getRangeMax()); - ui->Xdivs->setValueQuiet(plot->xAxis.getRangeDiv()); + ui->XDivs->setValue(plot->xAxis.getDivs()); + ui->XautoDivs->setChecked(plot->xAxis.getAutoDivs()); // Constant line list handling auto editLine = [&](XYPlotConstantLine *line) { @@ -277,8 +309,8 @@ XYplotAxisDialog::~XYplotAxisDialog() void XYplotAxisDialog::on_buttonBox_accepted() { // set plot values to the ones selected in the dialog - plot->setYAxis(0, (YAxis::Type) ui->Y1type->currentIndex(), ui->Y1log->isChecked(), ui->Y1auto->isChecked(), ui->Y1min->value(), ui->Y1max->value(), ui->Y1divs->value()); - plot->setYAxis(1, (YAxis::Type) ui->Y2type->currentIndex(), ui->Y2log->isChecked(), ui->Y2auto->isChecked(), ui->Y2min->value(), ui->Y2max->value(), ui->Y2divs->value()); + plot->setYAxis(0, (YAxis::Type) ui->Y1type->currentIndex(), ui->Y1log->isChecked(), ui->Y1auto->isChecked(), ui->Y1min->value(), ui->Y1max->value(), ui->Y1Divs->value(), ui->Y1autoDivs->isChecked()); + plot->setYAxis(1, (YAxis::Type) ui->Y2type->currentIndex(), ui->Y2log->isChecked(), ui->Y2auto->isChecked(), ui->Y2min->value(), ui->Y2max->value(), ui->Y2Divs->value(), ui->Y2autoDivs->isChecked()); TraceXYPlot::XAxisMode mode; if(ui->Xauto->isChecked()) { if(ui->Xautomode->currentIndex() == 0) { @@ -289,7 +321,7 @@ void XYplotAxisDialog::on_buttonBox_accepted() } else { mode = TraceXYPlot::XAxisMode::Manual; } - plot->setXAxis((XAxis::Type) ui->XType->currentIndex(), mode, ui->Xlog->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value()); + plot->setXAxis((XAxis::Type) ui->XType->currentIndex(), mode, ui->Xlog->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->XDivs->value(), ui->XautoDivs->isChecked()); } void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex) @@ -330,7 +362,6 @@ void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex) QString unit = XAxis::Unit(type); ui->Xmin->setUnit(unit); ui->Xmax->setUnit(unit); - ui->Xdivs->setUnit(unit); } std::set XYplotAxisDialog::supportedYAxis(XAxis::Type type) diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.ui b/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.ui index b8e2ba7..4ccc242 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.ui +++ b/Software/PC_Application/LibreVNA-GUI/Traces/xyplotaxisdialog.ui @@ -9,8 +9,8 @@ 0 0 - 814 - 458 + 810 + 461 @@ -21,9 +21,9 @@ - + - + @@ -133,12 +133,27 @@ - Divisions: + # Divisions: - + + + + + 2 + + + + + + + Auto + + + + @@ -262,12 +277,27 @@ - Divisions: + # Divisions: - + + + + + 2 + + + + + + + Auto + + + + @@ -281,7 +311,7 @@ - + @@ -418,12 +448,27 @@ - Divisions: + # Divisions: - + + + + + 2 + + + + + + + Auto + + + + @@ -592,8 +637,8 @@ - + diff --git a/Software/PC_Application/LibreVNA-GUI/preferences.cpp b/Software/PC_Application/LibreVNA-GUI/preferences.cpp index 8a4c790..4308b81 100644 --- a/Software/PC_Application/LibreVNA-GUI/preferences.cpp +++ b/Software/PC_Application/LibreVNA-GUI/preferences.cpp @@ -262,6 +262,7 @@ void PreferencesDialog::setInitialGUIState() ui->AcquisitionFullSpanCalibrated->setChecked(p->Acquisition.fullSpanCalibratedRange); ui->AcquisitionLimitTDRCheckbox->setChecked(p->Acquisition.limitDFT); ui->AcquisitionDFTLimitValue->setValue(p->Acquisition.maxDFTrate); + ui->AcquisitionGroupDelaySamples->setValue(p->Acquisition.groupDelaySamples); ui->GraphsDefaultTransmission->setCurrentText(p->Graphs.defaultGraphs.transmission); ui->GraphsDefaultReflection->setCurrentText(p->Graphs.defaultGraphs.reflection); @@ -299,6 +300,7 @@ void PreferencesDialog::setInitialGUIState() ui->MarkerShowCapacitance->setChecked(p->Marker.defaultBehavior.showCapacitance); ui->MarkerShowInductance->setChecked(p->Marker.defaultBehavior.showInductance); ui->MarkerShowQualityFactor->setChecked(p->Marker.defaultBehavior.showQualityFactor); + ui->MarkerShowGroupDelay->setChecked(p->Marker.defaultBehavior.showGroupDelay); ui->MarkerShowNoise->setChecked(p->Marker.defaultBehavior.showNoise); ui->MarkerShowPhasenoise->setChecked(p->Marker.defaultBehavior.showPhasenoise); ui->MarkerShowCenterBandwidth->setChecked(p->Marker.defaultBehavior.showCenterBandwidth); @@ -361,6 +363,7 @@ void PreferencesDialog::updateFromGUI() p->Acquisition.fullSpanCalibratedRange = ui->AcquisitionFullSpanCalibrated->isChecked(); p->Acquisition.limitDFT = ui->AcquisitionLimitTDRCheckbox->isChecked(); p->Acquisition.maxDFTrate = ui->AcquisitionDFTLimitValue->value(); + p->Acquisition.groupDelaySamples = ui->AcquisitionGroupDelaySamples->value(); p->Graphs.defaultGraphs.transmission = ui->GraphsDefaultTransmission->currentText(); p->Graphs.defaultGraphs.reflection = ui->GraphsDefaultReflection->currentText(); @@ -397,6 +400,7 @@ void PreferencesDialog::updateFromGUI() p->Marker.defaultBehavior.showCapacitance = ui->MarkerShowCapacitance->isChecked(); p->Marker.defaultBehavior.showInductance = ui->MarkerShowInductance->isChecked(); p->Marker.defaultBehavior.showQualityFactor = ui->MarkerShowQualityFactor->isChecked(); + p->Marker.defaultBehavior.showGroupDelay = ui->MarkerShowGroupDelay->isChecked(); p->Marker.defaultBehavior.showNoise = ui->MarkerShowNoise->isChecked(); p->Marker.defaultBehavior.showPhasenoise = ui->MarkerShowPhasenoise->isChecked(); p->Marker.defaultBehavior.showCenterBandwidth = ui->MarkerShowCenterBandwidth->isChecked(); diff --git a/Software/PC_Application/LibreVNA-GUI/preferences.h b/Software/PC_Application/LibreVNA-GUI/preferences.h index ce3b3d3..c17166c 100644 --- a/Software/PC_Application/LibreVNA-GUI/preferences.h +++ b/Software/PC_Application/LibreVNA-GUI/preferences.h @@ -108,6 +108,7 @@ public: // Math settings bool limitDFT; double maxDFTrate; + int groupDelaySamples; } Acquisition; struct { bool showUnits; @@ -150,7 +151,7 @@ public: struct { struct { bool showDataOnGraphs; - bool showdB, showdBm, showdBuV, showdBAngle, showRealImag, showImpedance, showVSWR, showResistance, showCapacitance, showInductance, showQualityFactor; + bool showdB, showdBm, showdBuV, showdBAngle, showRealImag, showImpedance, showVSWR, showResistance, showCapacitance, showInductance, showQualityFactor, showGroupDelay; bool showNoise, showPhasenoise, showCenterBandwidth, showCutoff, showInsertionLoss, showTOI, showAvgTone, showAvgModulation, showP1dB, showFlatness, showMaxDeltaNeg, showMaxDeltaPos; } defaultBehavior; bool interpolatePoints; @@ -214,6 +215,7 @@ private: {&Acquisition.fullSpanCalibratedRange, "Acquisition.fullSpanCalibratedRange", false}, {&Acquisition.limitDFT, "Acquisition.limitDFT", true}, {&Acquisition.maxDFTrate, "Acquisition.maxDFTrate", 1.0}, + {&Acquisition.groupDelaySamples, "Acquisition.groupDelaySamples", 5}, {&Graphs.showUnits, "Graphs.showUnits", true}, {&Graphs.Color.background, "Graphs.Color.background", QColor(Qt::black)}, {&Graphs.Color.axis, "Graphs.Color.axis", QColor(Qt::white)}, @@ -248,6 +250,7 @@ private: {&Marker.defaultBehavior.showCapacitance, "Marker.defaultBehavior.showCapacitance", true}, {&Marker.defaultBehavior.showInductance, "Marker.defaultBehavior.showInductance", true}, {&Marker.defaultBehavior.showQualityFactor, "Marker.defaultBehavior.showQualityFactor", true}, + {&Marker.defaultBehavior.showGroupDelay, "Marker.defaultBehavior.showGroupDelay", true}, {&Marker.defaultBehavior.showNoise, "Marker.defaultBehavior.showNoise", true}, {&Marker.defaultBehavior.showPhasenoise, "Marker.defaultBehavior.showPhasenoise", true}, {&Marker.defaultBehavior.showCenterBandwidth, "Marker.defaultBehavior.showCenterBandwidth", true}, diff --git a/Software/PC_Application/LibreVNA-GUI/preferencesdialog.ui b/Software/PC_Application/LibreVNA-GUI/preferencesdialog.ui index 5f6977e..fd3a7a8 100644 --- a/Software/PC_Application/LibreVNA-GUI/preferencesdialog.ui +++ b/Software/PC_Application/LibreVNA-GUI/preferencesdialog.ui @@ -93,7 +93,7 @@ - 1 + 3 @@ -107,8 +107,8 @@ 0 0 - 683 - 897 + 522 + 914 @@ -829,46 +829,81 @@ Math operations - + - - - Limit TDR/DFT to - - + + + + + Limit TDR/DFT to + + + + + + + false + + + 0.100000000000000 + + + 100.000000000000000 + + + + + + + updates per second + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - false - - - 0.100000000000000 - - - 100.000000000000000 - - - - - - - updates per second - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - + + + + + Number of samples used for group delay calculation: + + + + + + + 2 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -1503,6 +1538,13 @@ + + + + Group Delay + + + @@ -1737,8 +1779,8 @@ 0 0 - 697 - 563 + 168 + 127 @@ -1827,8 +1869,8 @@ 0 0 - 697 - 563 + 215 + 168