Proof-of-concept TDR measurements

This commit is contained in:
Jan Käberich 2020-10-27 22:07:14 +01:00
parent d313988bfd
commit e68a9abffe
18 changed files with 1276 additions and 718 deletions

View File

@ -19,10 +19,9 @@ HEADERS += \
SpectrumAnalyzer/spectrumanalyzer.h \
Tools/eseries.h \
Tools/impedancematchdialog.h \
Traces/bodeplotaxisdialog.h \
Traces/fftcomplex.h \
Traces/markerwidget.h \
Traces/trace.h \
Traces/tracebodeplot.h \
Traces/traceeditdialog.h \
Traces/traceexportdialog.h \
Traces/traceimportdialog.h \
@ -32,6 +31,8 @@ HEADERS += \
Traces/traceplot.h \
Traces/tracesmithchart.h \
Traces/tracewidget.h \
Traces/tracexyplot.h \
Traces/xyplotaxisdialog.h \
VNA/vna.h \
appwindow.h \
averaging.h \
@ -63,10 +64,9 @@ SOURCES += \
SpectrumAnalyzer/spectrumanalyzer.cpp \
Tools/eseries.cpp \
Tools/impedancematchdialog.cpp \
Traces/bodeplotaxisdialog.cpp \
Traces/fftcomplex.cpp \
Traces/markerwidget.cpp \
Traces/trace.cpp \
Traces/tracebodeplot.cpp \
Traces/traceeditdialog.cpp \
Traces/traceexportdialog.cpp \
Traces/traceimportdialog.cpp \
@ -76,6 +76,8 @@ SOURCES += \
Traces/traceplot.cpp \
Traces/tracesmithchart.cpp \
Traces/tracewidget.cpp \
Traces/tracexyplot.cpp \
Traces/xyplotaxisdialog.cpp \
VNA/vna.cpp \
appwindow.cpp \
averaging.cpp \
@ -103,12 +105,12 @@ FORMS += \
Device/manualcontroldialog.ui \
Generator/signalgenwidget.ui \
Tools/impedancematchdialog.ui \
Traces/bodeplotaxisdialog.ui \
Traces/markerwidget.ui \
Traces/traceeditdialog.ui \
Traces/traceexportdialog.ui \
Traces/traceimportdialog.ui \
Traces/tracewidget.ui \
Traces/xyplotaxisdialog.ui \
main.ui \
preferencesdialog.ui

View File

@ -1,7 +1,7 @@
#include "tilewidget.h"
#include "ui_tilewidget.h"
#include <QDebug>
#include "Traces/tracebodeplot.h"
#include "Traces/tracexyplot.h"
#include "Traces/tracesmithchart.h"
TileWidget::TileWidget(TraceModel &model, QWidget *parent) :
@ -130,7 +130,7 @@ void TileWidget::on_bSmithchart_clicked()
void TileWidget::on_bBodeplot_clicked()
{
setContent(new TraceBodePlot(model));
setContent(new TraceXYPlot(model));
}
void TileWidget::traceDeleted(TracePlot *)

View File

@ -81,7 +81,7 @@
<item>
<widget class="QPushButton" name="bBodeplot">
<property name="text">
<string>Bodeplot</string>
<string>XY-plot</string>
</property>
</widget>
</item>

View File

@ -25,7 +25,7 @@
#include "Traces/tracemodel.h"
#include "Traces/tracewidget.h"
#include "Traces/tracesmithchart.h"
#include "Traces/tracebodeplot.h"
#include "Traces/tracexyplot.h"
#include "Traces/traceimportdialog.h"
#include "CustomWidgets/tilewidget.h"
#include "CustomWidgets/siunitedit.h"
@ -55,11 +55,11 @@ SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window)
tPort2->fromLivedata(Trace::LivedataType::Overwrite, Trace::LiveParameter::Port2);
traceModel.addTrace(tPort2);
auto tracebode = new TraceBodePlot(traceModel);
auto tracebode = new TraceXYPlot(traceModel);
tracebode->enableTrace(tPort1, true);
tracebode->enableTrace(tPort2, true);
tracebode->setYAxis(0, TraceBodePlot::YAxisType::Magnitude, false, false, -120,0,10);
tracebode->setYAxis(1, TraceBodePlot::YAxisType::Disabled, false, true, 0,0,1);
tracebode->setYAxis(0, TraceXYPlot::YAxisType::Magnitude, false, false, -120,0,10);
tracebode->setYAxis(1, TraceXYPlot::YAxisType::Disabled, false, true, 0,0,1);
central->setPlot(tracebode);

View File

@ -1,118 +0,0 @@
#include "bodeplotaxisdialog.h"
#include "ui_bodeplotaxisdialog.h"
BodeplotAxisDialog::BodeplotAxisDialog(TraceBodePlot *plot) :
QDialog(nullptr),
ui(new Ui::BodeplotAxisDialog),
plot(plot)
{
ui->setupUi(this);
// Setup GUI connections
connect(ui->Y1type, qOverload<int>(&QComboBox::currentIndexChanged), [this](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 = (TraceBodePlot::YAxisType) index;
QString unit;
switch(type) {
case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break;
case TraceBodePlot::YAxisType::Phase: unit = "°"; break;
case TraceBodePlot::YAxisType::VSWR: unit = ""; break;
default: unit = ""; break;
}
ui->Y1min->setUnit(unit);
ui->Y1max->setUnit(unit);
ui->Y1divs->setUnit(unit);
});
connect(ui->Y1auto, &QCheckBox::toggled, [this](bool checked) {
ui->Y1min->setEnabled(!checked);
ui->Y1max->setEnabled(!checked);
ui->Y1divs->setEnabled(!checked);
});
connect(ui->Y2type, qOverload<int>(&QComboBox::currentIndexChanged), [this](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 = (TraceBodePlot::YAxisType) index;
QString unit;
switch(type) {
case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break;
case TraceBodePlot::YAxisType::Phase: unit = "°"; break;
case TraceBodePlot::YAxisType::VSWR: unit = ""; break;
default: unit = ""; break;
}
ui->Y2min->setUnit(unit);
ui->Y2max->setUnit(unit);
ui->Y2divs->setUnit(unit);
});
connect(ui->Y2auto, &QCheckBox::toggled, [this](bool checked) {
ui->Y2min->setEnabled(!checked);
ui->Y2max->setEnabled(!checked);
ui->Y2divs->setEnabled(!checked);
});
connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) {
ui->Xmin->setEnabled(!checked);
ui->Xmax->setEnabled(!checked);
ui->Xdivs->setEnabled(!checked);
});
ui->Xmin->setUnit("Hz");
ui->Xmax->setUnit("Hz");
ui->Xdivs->setUnit("Hz");
ui->Xmin->setPrefixes(" kMG");
ui->Xmax->setPrefixes(" kMG");
ui->Xdivs->setPrefixes(" kMG");
// Fill initial values
// assume same order in YAxisType enum as in ComboBox items
ui->Y1type->setCurrentIndex((int) plot->YAxis[0].type);
if(plot->YAxis[0].log) {
ui->Y1log->setChecked(true);
} else {
ui->Y1linear->setChecked(true);
}
ui->Y1auto->setChecked(plot->YAxis[0].autorange);
ui->Y1min->setValueQuiet(plot->YAxis[0].rangeMin);
ui->Y1max->setValueQuiet(plot->YAxis[0].rangeMax);
ui->Y1divs->setValueQuiet(plot->YAxis[0].rangeDiv);
ui->Y2type->setCurrentIndex((int) plot->YAxis[1].type);
if(plot->YAxis[1].log) {
ui->Y2log->setChecked(true);
} else {
ui->Y2linear->setChecked(true);
}
ui->Y2auto->setChecked(plot->YAxis[1].autorange);
ui->Y2min->setValueQuiet(plot->YAxis[1].rangeMin);
ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax);
ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv);
ui->Xauto->setChecked(plot->XAxis.autorange);
ui->Xmin->setValueQuiet(plot->XAxis.rangeMin);
ui->Xmax->setValueQuiet(plot->XAxis.rangeMax);
ui->Xdivs->setValueQuiet(plot->XAxis.rangeDiv);
}
BodeplotAxisDialog::~BodeplotAxisDialog()
{
delete ui;
}
void BodeplotAxisDialog::on_buttonBox_accepted()
{
// set plot values to the ones selected in the dialog
plot->setYAxis(0, (TraceBodePlot::YAxisType) ui->Y1type->currentIndex(), ui->Y1log->isChecked(), ui->Y1auto->isChecked(), ui->Y1min->value(), ui->Y1max->value(), ui->Y1divs->value());
plot->setYAxis(1, (TraceBodePlot::YAxisType) ui->Y2type->currentIndex(), ui->Y2log->isChecked(), ui->Y2auto->isChecked(), ui->Y2min->value(), ui->Y2max->value(), ui->Y2divs->value());
plot->setXAxis(ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value());
}

View File

@ -1,27 +0,0 @@
#ifndef BODEPLOTAXISDIALOG_H
#define BODEPLOTAXISDIALOG_H
#include <QDialog>
#include "tracebodeplot.h"
namespace Ui {
class BodeplotAxisDialog;
}
class BodeplotAxisDialog : public QDialog
{
Q_OBJECT
public:
explicit BodeplotAxisDialog(TraceBodePlot *plot);
~BodeplotAxisDialog();
private slots:
void on_buttonBox_accepted();
private:
Ui::BodeplotAxisDialog *ui;
TraceBodePlot *plot;
};
#endif // BODEPLOTAXISDIALOG_H

View File

@ -1,462 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BodeplotAxisDialog</class>
<widget class="QDialog" name="BodeplotAxisDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>715</width>
<height>282</height>
</rect>
</property>
<property name="windowTitle">
<string>Axis Setup</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Primary Y axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="Y1type">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude</string>
</property>
</item>
<item>
<property name="text">
<string>Phase</string>
</property>
</item>
<item>
<property name="text">
<string>VSWR</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="Y1linear">
<property name="text">
<string>Linear</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y1group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Y1log">
<property name="text">
<string>Log</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y1group</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Y1auto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Y1max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Y1min"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Y1divs"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_7">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Secondary Y axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="Y2type">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude</string>
</property>
</item>
<item>
<property name="text">
<string>Phase</string>
</property>
</item>
<item>
<property name="text">
<string>VSWR</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="Y2linear">
<property name="text">
<string>Linear</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y2group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Y2log">
<property name="text">
<string>Log</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y2group</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Y2auto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Y2max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Y2min"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Y2divs"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_13">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>X axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QFormLayout" name="formLayout_6">
<item row="0" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Xauto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Xmax"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Xmin"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Xdivs"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SIUnitEdit</class>
<extends>QLineEdit</extends>
<header>CustomWidgets/siunitedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>BodeplotAxisDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>BodeplotAxisDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="Y1group"/>
<buttongroup name="Y2group"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,152 @@
/*
* Free FFT and convolution (C++)
*
* Copyright (c) 2020 Project Nayuki. (MIT License)
* https://www.nayuki.io/page/free-small-fft-in-multiple-languages
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <utility>
#include "fftcomplex.h"
using std::complex;
using std::size_t;
using std::uintmax_t;
using std::vector;
// Private function prototypes
static size_t reverseBits(size_t val, int width);
void Fft::transform(vector<complex<double> > &vec, bool inverse) {
size_t n = vec.size();
if (n == 0)
return;
else if ((n & (n - 1)) == 0) // Is power of 2
transformRadix2(vec, inverse);
else // More complicated algorithm for arbitrary sizes
transformBluestein(vec, inverse);
}
void Fft::transformRadix2(vector<complex<double> > &vec, bool inverse) {
// Length variables
size_t n = vec.size();
int levels = 0; // Compute levels = floor(log2(n))
for (size_t temp = n; temp > 1U; temp >>= 1)
levels++;
if (static_cast<size_t>(1U) << levels != n)
throw std::domain_error("Length is not a power of 2");
// Trigonometric table
vector<complex<double> > expTable(n / 2);
for (size_t i = 0; i < n / 2; i++)
expTable[i] = std::polar(1.0, (inverse ? 2 : -2) * M_PI * i / n);
// Bit-reversed addressing permutation
for (size_t i = 0; i < n; i++) {
size_t j = reverseBits(i, levels);
if (j > i)
std::swap(vec[i], vec[j]);
}
// Cooley-Tukey decimation-in-time radix-2 FFT
for (size_t size = 2; size <= n; size *= 2) {
size_t halfsize = size / 2;
size_t tablestep = n / size;
for (size_t i = 0; i < n; i += size) {
for (size_t j = i, k = 0; j < i + halfsize; j++, k += tablestep) {
complex<double> temp = vec[j + halfsize] * expTable[k];
vec[j + halfsize] = vec[j] - temp;
vec[j] += temp;
}
}
if (size == n) // Prevent overflow in 'size *= 2'
break;
}
}
void Fft::transformBluestein(vector<complex<double> > &vec, bool inverse) {
// Find a power-of-2 convolution length m such that m >= n * 2 + 1
size_t n = vec.size();
size_t m = 1;
while (m / 2 <= n) {
if (m > SIZE_MAX / 2)
throw std::length_error("Vector too large");
m *= 2;
}
// Trigonometric table
vector<complex<double> > expTable(n);
for (size_t i = 0; i < n; i++) {
uintmax_t temp = static_cast<uintmax_t>(i) * i;
temp %= static_cast<uintmax_t>(n) * 2;
double angle = (inverse ? M_PI : -M_PI) * temp / n;
expTable[i] = std::polar(1.0, angle);
}
// Temporary vectors and preprocessing
vector<complex<double> > avec(m);
for (size_t i = 0; i < n; i++)
avec[i] = vec[i] * expTable[i];
vector<complex<double> > bvec(m);
bvec[0] = expTable[0];
for (size_t i = 1; i < n; i++)
bvec[i] = bvec[m - i] = std::conj(expTable[i]);
// Convolution
vector<complex<double> > cvec(m);
convolve(avec, bvec, cvec);
// Postprocessing
for (size_t i = 0; i < n; i++)
vec[i] = cvec[i] * expTable[i];
}
void Fft::convolve(
const vector<complex<double> > &xvec,
const vector<complex<double> > &yvec,
vector<complex<double> > &outvec) {
size_t n = xvec.size();
if (n != yvec.size() || n != outvec.size())
throw std::domain_error("Mismatched lengths");
vector<complex<double> > xv = xvec;
vector<complex<double> > yv = yvec;
transform(xv, false);
transform(yv, false);
for (size_t i = 0; i < n; i++)
xv[i] *= yv[i];
transform(xv, true);
for (size_t i = 0; i < n; i++) // Scaling (because this FFT implementation omits it)
outvec[i] = xv[i] / static_cast<double>(n);
}
static size_t reverseBits(size_t val, int width) {
size_t result = 0;
for (int i = 0; i < width; i++, val >>= 1)
result = (result << 1) | (val & 1U);
return result;
}

View File

@ -0,0 +1,62 @@
/*
* Free FFT and convolution (C++)
*
* Copyright (c) 2020 Project Nayuki. (MIT License)
* https://www.nayuki.io/page/free-small-fft-in-multiple-languages
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
#pragma once
#include <complex>
#include <vector>
namespace Fft {
/*
* Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
* The vector can have any length. This is a wrapper function. The inverse transform does not perform scaling, so it is not a true inverse.
*/
void transform(std::vector<std::complex<double> > &vec, bool inverse);
/*
* Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
* The vector's length must be a power of 2. Uses the Cooley-Tukey decimation-in-time radix-2 algorithm.
*/
void transformRadix2(std::vector<std::complex<double> > &vec, bool inverse);
/*
* Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
* The vector can have any length. This requires the convolution function, which in turn requires the radix-2 FFT function.
* Uses Bluestein's chirp z-transform algorithm.
*/
void transformBluestein(std::vector<std::complex<double> > &vec, bool inverse);
/*
* Computes the circular convolution of the given complex vectors. Each vector's length must be the same.
*/
void convolve(
const std::vector<std::complex<double> > &xvec,
const std::vector<std::complex<double> > &yvec,
std::vector<std::complex<double> > &outvec);
}

View File

@ -1,9 +1,12 @@
#include "trace.h"
#include <math.h>
#include "fftcomplex.h"
using namespace std;
Trace::Trace(QString name, QColor color, LiveParameter live)
: _name(name),
: tdr_users(0),
_name(name),
_color(color),
_liveType(LivedataType::Overwrite),
_liveParam(live),
@ -64,6 +67,12 @@ void Trace::addData(Trace::Data d) {
}
emit dataAdded(this, d);
emit dataChanged();
if(lower == _data.begin()) {
// received the first point, which means the last sweep just finished
if(tdr_users) {
updateTimeDomainData();
}
}
}
void Trace::setName(QString name) {
@ -137,12 +146,79 @@ void Trace::removeMarker(TraceMarker *m)
markers.erase(m);
emit markerRemoved(m);
}
//#include <iostream>
//#include <chrono>
void Trace::updateTimeDomainData()
{
// using namespace std::chrono;
// auto starttime = duration_cast< milliseconds >(
// system_clock::now().time_since_epoch()
// ).count();
auto steps = size();
if(minFreq() * size() != maxFreq()) {
// data is not available with correct frequency spacing, calculate required steps
steps = maxFreq() / minFreq();
}
const double PI = 3.141592653589793238463;
// reserve vector for negative frequenies and DC as well
vector<complex<double>> frequencyDomain(2*steps + 1);
// copy frequencies, use the flipped conjugate for negative part
for(unsigned int i = 1;i<=steps;i++) {
auto S = getData(minFreq() * i);
constexpr double alpha0 = 0.54;
auto hamming = alpha0 - (1.0 - alpha0) * -cos(PI * i / steps);
S *= hamming;
frequencyDomain[2 * steps - i + 1] = conj(S);
frequencyDomain[i] = S;
}
// use simple extrapolation from lowest two points to extract DC value
auto abs_DC = 2.0 * abs(frequencyDomain[1]) - abs(frequencyDomain[2]);
auto phase_DC = 2.0 * arg(frequencyDomain[1]) - arg(frequencyDomain[2]);
frequencyDomain[0] = polar(abs_DC, phase_DC);
auto fft_bins = frequencyDomain.size();
timeDomain.clear();
timeDomain.resize(fft_bins);
const double fs = 1.0 / (minFreq() * fft_bins);
double last_step = 0.0;
Fft::transform(frequencyDomain, true);
for(unsigned int i = 0;i<fft_bins;i++) {
TimedomainData t;
t.time = fs * i;
t.impulseResponse = real(frequencyDomain[i]) / fft_bins;
t.stepResponse = last_step;
last_step += t.impulseResponse;
timeDomain.push_back(t);
}
// auto duration = duration_cast< milliseconds >(
// system_clock::now().time_since_epoch()
// ).count() - starttime;
// cout << "TDR: " << this << " (took " << duration << "ms)" <<endl;
}
void Trace::setReflection(bool value)
{
reflection = value;
}
void Trace::addTDRinterest()
{
if(tdr_users == 0) {
// no recent time domain data available, calculate now
updateTimeDomainData();
}
tdr_users++;
}
void Trace::removeTDRinterest()
{
if(tdr_users > 0) {
tdr_users--;
}
}
void Trace::setCalibration(bool value)
{
calibration = value;
@ -288,7 +364,16 @@ std::complex<double> Trace::getData(double frequency)
return std::numeric_limits<std::complex<double>>::quiet_NaN();
}
return sample(index(frequency)).S;
auto i = index(frequency);
if(_data.at(i).frequency == frequency) {
return _data[i].S;
} else {
// no exact frequency match, needs to interpolate
auto high = _data[i];
auto low = _data[i-1];
double alpha = (frequency - low.frequency) / (high.frequency - low.frequency);
return low.S * (1 - alpha) + high.S * alpha;
}
}
int Trace::index(double frequency)

View File

@ -21,6 +21,13 @@ public:
std::complex<double> S;
};
class TimedomainData {
public:
double time;
double impulseResponse;
double stepResponse;
};
enum class LiveParameter {
S11,
S12,
@ -76,6 +83,14 @@ public:
void setCalibration(bool value);
void setReflection(bool value);
// TDR calculation can be ressource intensive, only perform when some other module is interested.
// Each interested module should call addTDRinterest(), read the data with getTDR() and finally
// call removeTDRinterest() once TDR updates are no longer required.
// The data is only updated at the end of a sweep and upon the first addTDRinterest() call.
void addTDRinterest();
void removeTDRinterest();
const std::vector<TimedomainData>& getTDR() { return timeDomain;}
public slots:
void setTouchstoneParameter(int value);
void setTouchstoneFilename(const QString &value);
@ -98,7 +113,11 @@ signals:
void markerRemoved(TraceMarker *m);
private:
void updateTimeDomainData();
void printTimeDomain();
std::vector<Data> _data;
std::vector<TimedomainData> timeDomain;
unsigned int tdr_users;
QString _name;
QColor _color;
LivedataType _liveType;

View File

@ -1,4 +1,4 @@
#include "tracebodeplot.h"
#include "tracexyplot.h"
#include <QGridLayout>
#include "qwtplotpiecewisecurve.h"
#include "qwt_series_data.h"
@ -11,18 +11,26 @@
#include "tracemarker.h"
#include <qwt_symbol.h>
#include <qwt_picker_machine.h>
#include "bodeplotaxisdialog.h"
#include "xyplotaxisdialog.h"
#include <preferences.h>
using namespace std;
set<TraceBodePlot*> TraceBodePlot::allPlots;
set<TraceXYPlot*> TraceXYPlot::allPlots;
static double AxisTransformation(TraceBodePlot::YAxisType type, complex<double> data) {
const set<TraceXYPlot::YAxisType> TraceXYPlot::YAxisTypes = {TraceXYPlot::YAxisType::Disabled,
TraceXYPlot::YAxisType::Magnitude,
TraceXYPlot::YAxisType::Phase,
TraceXYPlot::YAxisType::VSWR,
TraceXYPlot::YAxisType::Impulse,
TraceXYPlot::YAxisType::Step,
TraceXYPlot::YAxisType::Impedance};
static double FrequencyAxisTransformation(TraceXYPlot::YAxisType type, complex<double> data) {
switch(type) {
case TraceBodePlot::YAxisType::Magnitude: return 20*log10(abs(data)); break;
case TraceBodePlot::YAxisType::Phase: return arg(data) * 180.0 / M_PI; break;
case TraceBodePlot::YAxisType::VSWR:
case TraceXYPlot::YAxisType::Magnitude: return 20*log10(abs(data)); break;
case TraceXYPlot::YAxisType::Phase: return arg(data) * 180.0 / M_PI; break;
case TraceXYPlot::YAxisType::VSWR:
if(abs(data) < 1.0) {
return (1+abs(data)) / (1-abs(data));
}
@ -31,31 +39,79 @@ static double AxisTransformation(TraceBodePlot::YAxisType type, complex<double>
}
return numeric_limits<double>::quiet_NaN();
}
static double TimeAxisTransformation(TraceXYPlot::YAxisType type, Trace *t, int index) {
auto timeData = t->getTDR()[index];
switch(type) {
case TraceXYPlot::YAxisType::Impulse: return timeData.impulseResponse; break;
case TraceXYPlot::YAxisType::Step: return timeData.stepResponse; break;
case TraceXYPlot::YAxisType::Impedance:
if(abs(timeData.stepResponse) < 1.0) {
return 50 * (1+timeData.stepResponse) / (1-timeData.stepResponse);
}
break;
default: break;
}
return numeric_limits<double>::quiet_NaN();
}
template<TraceBodePlot::YAxisType E> class QwtTraceSeries : public QwtSeriesData<QPointF> {
class QwtTraceSeries : public QwtSeriesData<QPointF> {
public:
QwtTraceSeries(Trace &t)
QwtTraceSeries(Trace &t, TraceXYPlot::YAxisType Ytype, TraceXYPlot::XAxisType Xtype)
: QwtSeriesData<QPointF>(),
t(t){};
Ytype(Ytype),
Xtype(Xtype),
t(t){}
size_t size() const override {
return t.size();
switch(Ytype) {
case TraceXYPlot::YAxisType::Magnitude:
case TraceXYPlot::YAxisType::Phase:
case TraceXYPlot::YAxisType::VSWR:
return t.size();
case TraceXYPlot::YAxisType::Impulse:
case TraceXYPlot::YAxisType::Step:
case TraceXYPlot::YAxisType::Impedance:
return t.getTDR().size();
default:
return 0;
}
}
QPointF sample(size_t i) const override {
Trace::Data d = t.sample(i);
QPointF p;
p.setX(d.frequency);
p.setY(AxisTransformation(E, d.S));
return p;
switch(Ytype) {
case TraceXYPlot::YAxisType::Magnitude:
case TraceXYPlot::YAxisType::Phase:
case TraceXYPlot::YAxisType::VSWR: {
Trace::Data d = t.sample(i);
QPointF p;
p.setX(d.frequency);
p.setY(FrequencyAxisTransformation(Ytype, d.S));
return p;
}
case TraceXYPlot::YAxisType::Impulse:
case TraceXYPlot::YAxisType::Step:
case TraceXYPlot::YAxisType::Impedance: {
auto sample = t.getTDR()[i];
QPointF p;
// TODO set distance
p.setX(sample.time);
p.setY(TimeAxisTransformation(Ytype, &t, i));
return p;
}
default:
return QPointF();
}
}
QRectF boundingRect() const override {
return qwtBoundingRect(*this);
}
private:
TraceXYPlot::YAxisType Ytype;
TraceXYPlot::XAxisType Xtype;
Trace &t;
};
TraceBodePlot::TraceBodePlot(TraceModel &model, QWidget *parent)
TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent)
: TracePlot(parent),
selectedMarker(nullptr)
{
@ -69,10 +125,10 @@ TraceBodePlot::TraceBodePlot(TraceModel &model, QWidget *parent)
grid->attach(plot);
setColorFromPreferences();
auto selectPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
auto selectPicker = new XYplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
selectPicker->setStateMachine(new QwtPickerClickPointMachine);
drawPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
drawPicker = new XYplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
drawPicker->setStateMachine(new QwtPickerDragPointMachine);
drawPicker->setTrackerPen(QPen(Qt::white));
@ -94,14 +150,14 @@ TraceBodePlot::TraceBodePlot(TraceModel &model, QWidget *parent)
setYAxis(1, YAxisType::Phase, false, false, -180, 180, 30);
// enable autoscaling and set for full span (no information about actual span available yet)
setXAxis(0, 6000000000);
setXAxis(true, 0, 6000000000, 600000000);
setXAxis(XAxisType::Frequency, true, 0, 6000000000, 600000000);
// get notified when the span changes
connect(&model, &TraceModel::SpanChanged, this, qOverload<double, double>(&TraceBodePlot::setXAxis));
connect(&model, &TraceModel::SpanChanged, this, qOverload<double, double>(&TraceXYPlot::setXAxis));
allPlots.insert(this);
}
TraceBodePlot::~TraceBodePlot()
TraceXYPlot::~TraceXYPlot()
{
for(int axis = 0;axis < 2;axis++) {
for(auto pd : curves[axis]) {
@ -112,17 +168,16 @@ TraceBodePlot::~TraceBodePlot()
allPlots.erase(this);
}
void TraceBodePlot::setXAxis(double min, double max)
void TraceXYPlot::setXAxis(double min, double max)
{
sweep_fmin = min;
sweep_fmax = max;
updateXAxis();
}
void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log, bool autorange, double min, double max, double div)
void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool autorange, double min, double max, double div)
{
if(YAxis[axis].type != type) {
YAxis[axis].type = type;
if(YAxis[axis].Ytype != type) {
// remove traces that are active but not supported with the new axis type
bool erased = false;
do {
@ -136,6 +191,13 @@ void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log,
}
} while(erased);
if(isTDRtype(YAxis[axis].Ytype)) {
for(auto t : tracesAxis[axis]) {
t->removeTDRinterest();
}
}
YAxis[axis].Ytype = type;
for(auto t : tracesAxis[axis]) {
// supported but needs an adjusted QwtSeriesData
auto td = curves[axis][t];
@ -149,6 +211,9 @@ void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log,
markerDataChanged(m);
}
}
if(isTDRtype(type)) {
t->addTDRinterest();
}
}
}
YAxis[axis].log = log;
@ -168,8 +233,9 @@ void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log,
replot();
}
void TraceBodePlot::setXAxis(bool autorange, double min, double max, double div)
void TraceXYPlot::setXAxis(XAxisType type, bool autorange, double min, double max, double div)
{
XAxis.Xtype = type;
XAxis.autorange = autorange;
XAxis.rangeMin = min;
XAxis.rangeMax = max;
@ -177,33 +243,45 @@ void TraceBodePlot::setXAxis(bool autorange, double min, double max, double div)
updateXAxis();
}
void TraceBodePlot::enableTrace(Trace *t, bool enabled)
void TraceXYPlot::enableTrace(Trace *t, bool enabled)
{
for(int axis = 0;axis < 2;axis++) {
if(supported(t, YAxis[axis].type)) {
if(supported(t, YAxis[axis].Ytype)) {
enableTraceAxis(t, axis, enabled);
}
}
}
void TraceBodePlot::updateGraphColors()
void TraceXYPlot::updateGraphColors()
{
for(auto p : allPlots) {
p->setColorFromPreferences();
}
}
void TraceBodePlot::updateContextMenu()
bool TraceXYPlot::isTDRtype(TraceXYPlot::YAxisType type)
{
switch(type) {
case YAxisType::Impulse:
case YAxisType::Step:
case YAxisType::Impedance:
return true;
default:
return false;
}
}
void TraceXYPlot::updateContextMenu()
{
contextmenu->clear();
auto setup = new QAction("Axis setup...");
connect(setup, &QAction::triggered, [this]() {
auto setup = new BodeplotAxisDialog(this);
auto setup = new XYplotAxisDialog(this);
setup->show();
});
contextmenu->addAction(setup);
for(int axis = 0;axis < 2;axis++) {
if(YAxis[axis].type == YAxisType::Disabled) {
if(YAxis[axis].Ytype == YAxisType::Disabled) {
continue;
}
if(axis == 0) {
@ -213,7 +291,7 @@ void TraceBodePlot::updateContextMenu()
}
for(auto t : traces) {
// Skip traces that are not applicable for the selected axis type
if(!supported(t.first, YAxis[axis].type)) {
if(!supported(t.first, YAxis[axis].Ytype)) {
continue;
}
@ -236,18 +314,18 @@ void TraceBodePlot::updateContextMenu()
});
}
bool TraceBodePlot::supported(Trace *)
bool TraceXYPlot::supported(Trace *)
{
// potentially possible to add every kind of trace (depends on axis)
return true;
}
void TraceBodePlot::replot()
void TraceXYPlot::replot()
{
plot->replot();
}
QString TraceBodePlot::AxisTypeToName(TraceBodePlot::YAxisType type)
QString TraceXYPlot::AxisTypeToName(TraceXYPlot::YAxisType type)
{
switch(type) {
case YAxisType::Disabled: return "Disabled"; break;
@ -258,7 +336,7 @@ QString TraceBodePlot::AxisTypeToName(TraceBodePlot::YAxisType type)
}
}
void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled)
void TraceXYPlot::enableTraceAxis(Trace *t, int axis, bool enabled)
{
bool alreadyEnabled = tracesAxis[axis].find(t) != tracesAxis[axis].end();
if(alreadyEnabled != enabled) {
@ -272,20 +350,26 @@ void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled)
cd.curve->setSamples(cd.data);
curves[axis][t] = cd;
// connect signals
connect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot);
connect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged);
connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged);
connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot);
connect(t, &Trace::dataChanged, this, &TraceXYPlot::triggerReplot);
connect(t, &Trace::colorChanged, this, &TraceXYPlot::traceColorChanged);
connect(t, &Trace::visibilityChanged, this, &TraceXYPlot::traceColorChanged);
connect(t, &Trace::visibilityChanged, this, &TraceXYPlot::triggerReplot);
if(axis == 0) {
connect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded);
connect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved);
connect(t, &Trace::markerAdded, this, &TraceXYPlot::markerAdded);
connect(t, &Trace::markerRemoved, this, &TraceXYPlot::markerRemoved);
auto tracemarkers = t->getMarkers();
for(auto m : tracemarkers) {
markerAdded(m);
}
}
if(isTDRtype(YAxis[axis].Ytype)) {
t->addTDRinterest();
}
traceColorChanged(t);
} else {
if(isTDRtype(YAxis[axis].Ytype)) {
t->removeTDRinterest();
}
tracesAxis[axis].erase(t);
// clean up and delete
if(curves[axis].find(t) != curves[axis].end()) {
@ -295,14 +379,14 @@ void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled)
int otherAxis = axis == 0 ? 1 : 0;
if(curves[otherAxis].find(t) == curves[otherAxis].end()) {
// this trace is not used anymore, disconnect from notifications
disconnect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot);
disconnect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged);
disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged);
disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot);
disconnect(t, &Trace::dataChanged, this, &TraceXYPlot::triggerReplot);
disconnect(t, &Trace::colorChanged, this, &TraceXYPlot::traceColorChanged);
disconnect(t, &Trace::visibilityChanged, this, &TraceXYPlot::traceColorChanged);
disconnect(t, &Trace::visibilityChanged, this, &TraceXYPlot::triggerReplot);
}
if(axis == 0) {
disconnect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded);
disconnect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved);
disconnect(t, &Trace::markerAdded, this, &TraceXYPlot::markerAdded);
disconnect(t, &Trace::markerRemoved, this, &TraceXYPlot::markerRemoved);
auto tracemarkers = t->getMarkers();
for(auto m : tracemarkers) {
markerRemoved(m);
@ -315,7 +399,7 @@ void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled)
}
}
bool TraceBodePlot::supported(Trace *t, TraceBodePlot::YAxisType type)
bool TraceXYPlot::supported(Trace *t, TraceXYPlot::YAxisType type)
{
switch(type) {
case YAxisType::Disabled:
@ -331,7 +415,7 @@ bool TraceBodePlot::supported(Trace *t, TraceBodePlot::YAxisType type)
return true;
}
void TraceBodePlot::updateXAxis()
void TraceXYPlot::updateXAxis()
{
if(XAxis.autorange && sweep_fmax-sweep_fmin > 0) {
QList<double> tickList;
@ -346,21 +430,12 @@ void TraceBodePlot::updateXAxis()
triggerReplot();
}
QwtSeriesData<QPointF> *TraceBodePlot::createQwtSeriesData(Trace &t, int axis)
QwtSeriesData<QPointF> *TraceXYPlot::createQwtSeriesData(Trace &t, int axis)
{
switch(YAxis[axis].type) {
case YAxisType::Magnitude:
return new QwtTraceSeries<YAxisType::Magnitude>(t);
case YAxisType::Phase:
return new QwtTraceSeries<YAxisType::Phase>(t);
case YAxisType::VSWR:
return new QwtTraceSeries<YAxisType::VSWR>(t);
default:
return nullptr;
}
return new QwtTraceSeries(t, YAxis[axis].Ytype, XAxis.Xtype);
}
void TraceBodePlot::traceColorChanged(Trace *t)
void TraceXYPlot::traceColorChanged(Trace *t)
{
for(int axis = 0;axis < 2;axis++) {
if(curves[axis].find(t) != curves[axis].end()) {
@ -388,7 +463,7 @@ void TraceBodePlot::traceColorChanged(Trace *t)
}
}
void TraceBodePlot::markerAdded(TraceMarker *m)
void TraceXYPlot::markerAdded(TraceMarker *m)
{
if(markers.count(m)) {
return;
@ -396,17 +471,17 @@ void TraceBodePlot::markerAdded(TraceMarker *m)
auto qwtMarker = new QwtPlotMarker;
markers[m] = qwtMarker;
markerSymbolChanged(m);
connect(m, &TraceMarker::symbolChanged, this, &TraceBodePlot::markerSymbolChanged);
connect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged);
connect(m, &TraceMarker::symbolChanged, this, &TraceXYPlot::markerSymbolChanged);
connect(m, &TraceMarker::dataChanged, this, &TraceXYPlot::markerDataChanged);
markerDataChanged(m);
qwtMarker->attach(plot);
triggerReplot();
}
void TraceBodePlot::markerRemoved(TraceMarker *m)
void TraceXYPlot::markerRemoved(TraceMarker *m)
{
disconnect(m, &TraceMarker::symbolChanged, this, &TraceBodePlot::markerSymbolChanged);
disconnect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged);
disconnect(m, &TraceMarker::symbolChanged, this, &TraceXYPlot::markerSymbolChanged);
disconnect(m, &TraceMarker::dataChanged, this, &TraceXYPlot::markerDataChanged);
if(markers.count(m)) {
markers[m]->detach();
delete markers[m];
@ -415,15 +490,15 @@ void TraceBodePlot::markerRemoved(TraceMarker *m)
triggerReplot();
}
void TraceBodePlot::markerDataChanged(TraceMarker *m)
void TraceXYPlot::markerDataChanged(TraceMarker *m)
{
auto qwtMarker = markers[m];
qwtMarker->setXValue(m->getFrequency());
qwtMarker->setYValue(AxisTransformation(YAxis[0].type, m->getData()));
qwtMarker->setYValue(FrequencyAxisTransformation(YAxis[0].Ytype, m->getData()));
triggerReplot();
}
void TraceBodePlot::markerSymbolChanged(TraceMarker *m)
void TraceXYPlot::markerSymbolChanged(TraceMarker *m)
{
auto qwtMarker = markers[m];
auto old_sym = qwtMarker->symbol();
@ -437,7 +512,7 @@ void TraceBodePlot::markerSymbolChanged(TraceMarker *m)
triggerReplot();
}
void TraceBodePlot::clicked(const QPointF pos)
void TraceXYPlot::clicked(const QPointF pos)
{
auto clickPoint = drawPicker->plotToPixel(pos);
unsigned int closestDistance = numeric_limits<unsigned int>::max();
@ -461,7 +536,7 @@ void TraceBodePlot::clicked(const QPointF pos)
}
}
void TraceBodePlot::moved(const QPointF pos)
void TraceXYPlot::moved(const QPointF pos)
{
if(!selectedMarker || !selectedCurve) {
return;
@ -469,7 +544,7 @@ void TraceBodePlot::moved(const QPointF pos)
selectedMarker->setFrequency(pos.x());
}
void TraceBodePlot::setColorFromPreferences()
void TraceXYPlot::setColorFromPreferences()
{
auto pref = Preferences::getInstance();
plot->setCanvasBackground(pref.General.graphColors.background);

View File

@ -1,5 +1,5 @@
#ifndef TRACEBODEPLOT_H
#define TRACEBODEPLOT_H
#ifndef TRACEXYPLOT_H
#define TRACEXYPLOT_H
#include "traceplot.h"
#include <set>
@ -11,10 +11,10 @@
#include <qwt_plot_picker.h>
// Derived plotpicker, exposing transformation functions
class BodeplotPicker : public QwtPlotPicker {
class XYplotPicker : public QwtPlotPicker {
Q_OBJECT
public:
BodeplotPicker(int xAxis, int yAxis, RubberBand rubberBand, DisplayMode trackerMode, QWidget *w)
XYplotPicker(int xAxis, int yAxis, RubberBand rubberBand, DisplayMode trackerMode, QWidget *w)
: QwtPlotPicker(xAxis, yAxis, rubberBand, trackerMode, w) {};
QPoint plotToPixel(const QPointF &pos) {
return transform(pos);
@ -24,33 +24,46 @@ public:
}
};
class TraceBodePlot : public TracePlot
class TraceXYPlot : public TracePlot
{
friend class BodeplotAxisDialog;
friend class XYplotAxisDialog;
Q_OBJECT
public:
TraceBodePlot(TraceModel &model, QWidget *parent = nullptr);
~TraceBodePlot();
TraceXYPlot(TraceModel &model, QWidget *parent = nullptr);
~TraceXYPlot();
enum class YAxisType {
Disabled = 0,
// S parameter options
Magnitude = 1,
Phase = 2,
VSWR = 3,
// TDR options
Impulse = 4,
Step = 5,
Impedance = 6,
Last,
};
static const std::set<YAxisType> YAxisTypes;
enum class XAxisType {
Frequency,
Time,
Distance,
};
virtual void setXAxis(double min, double max) override;
void setYAxis(int axis, YAxisType type, bool log, bool autorange, double min, double max, double div);
void setXAxis(bool autorange, double min, double max, double div);
void setXAxis(XAxisType type, bool autorange, double min, double max, double div);
void enableTrace(Trace *t, bool enabled) override;
// Applies potentially changed colors to all bodeplots
// Applies potentially changed colors to all XY-plots
static void updateGraphColors();
bool isTDRtype(YAxisType type);
protected:
virtual void updateContextMenu();
virtual bool supported(Trace *t);
virtual void updateContextMenu() override;
virtual bool supported(Trace *t) override;
void replot() override;
private slots:
@ -74,7 +87,10 @@ private:
class Axis {
public:
YAxisType type;
union {
YAxisType Ytype;
XAxisType Xtype;
};
bool log;
bool autorange;
double rangeMin;
@ -97,10 +113,10 @@ private:
TraceMarker *selectedMarker;
QwtPlotCurve *selectedCurve;
BodeplotPicker *drawPicker;
XYplotPicker *drawPicker;
// keep track of all created plots for changing colors
static std::set<TraceBodePlot*> allPlots;
static std::set<TraceXYPlot*> allPlots;
};
#endif // TRACEBODEPLOT_H
#endif // TRACEXYPLOT_H

View File

@ -0,0 +1,177 @@
#include "xyplotaxisdialog.h"
#include "ui_bodeplotaxisdialog.h"
#include <QStandardItemModel>
using namespace std;
XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
QDialog(nullptr),
ui(new Ui::XYplotAxisDialog),
plot(plot)
{
ui->setupUi(this);
// Setup GUI connections
connect(ui->Y1type, qOverload<int>(&QComboBox::currentIndexChanged), [this](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 = (TraceXYPlot::YAxisType) index;
QString unit = YAxisUnit(type);
ui->Y1min->setUnit(unit);
ui->Y1max->setUnit(unit);
ui->Y1divs->setUnit(unit);
});
connect(ui->Y1auto, &QCheckBox::toggled, [this](bool checked) {
ui->Y1min->setEnabled(!checked);
ui->Y1max->setEnabled(!checked);
ui->Y1divs->setEnabled(!checked);
});
connect(ui->Y2type, qOverload<int>(&QComboBox::currentIndexChanged), [this](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 = (TraceXYPlot::YAxisType) index;
QString unit = YAxisUnit(type);
ui->Y2min->setUnit(unit);
ui->Y2max->setUnit(unit);
ui->Y2divs->setUnit(unit);
});
connect(ui->XType, qOverload<int>(&QComboBox::currentIndexChanged), this, &XYplotAxisDialog::XAxisTypeChanged);
connect(ui->Y2auto, &QCheckBox::toggled, [this](bool checked) {
ui->Y2min->setEnabled(!checked);
ui->Y2max->setEnabled(!checked);
ui->Y2divs->setEnabled(!checked);
});
connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) {
ui->Xmin->setEnabled(!checked);
ui->Xmax->setEnabled(!checked);
ui->Xdivs->setEnabled(!checked);
});
ui->XType->setCurrentIndex((int) plot->XAxis.Xtype);
ui->Xmin->setPrefixes("pnum kMG");
ui->Xmax->setPrefixes("pnum kMG");
ui->Xdivs->setPrefixes("pnum kMG");
// Fill initial values
// assume same order in YAxisType enum as in ComboBox items
ui->Y1type->setCurrentIndex((int) plot->YAxis[0].Ytype);
if(plot->YAxis[0].log) {
ui->Y1log->setChecked(true);
} else {
ui->Y1linear->setChecked(true);
}
ui->Y1auto->setChecked(plot->YAxis[0].autorange);
ui->Y1min->setValueQuiet(plot->YAxis[0].rangeMin);
ui->Y1max->setValueQuiet(plot->YAxis[0].rangeMax);
ui->Y1divs->setValueQuiet(plot->YAxis[0].rangeDiv);
ui->Y2type->setCurrentIndex((int) plot->YAxis[1].Ytype);
if(plot->YAxis[1].log) {
ui->Y2log->setChecked(true);
} else {
ui->Y2linear->setChecked(true);
}
ui->Y2auto->setChecked(plot->YAxis[1].autorange);
ui->Y2min->setValueQuiet(plot->YAxis[1].rangeMin);
ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax);
ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv);
ui->Xauto->setChecked(plot->XAxis.autorange);
ui->Xmin->setValueQuiet(plot->XAxis.rangeMin);
ui->Xmax->setValueQuiet(plot->XAxis.rangeMax);
ui->Xdivs->setValueQuiet(plot->XAxis.rangeDiv);
}
XYplotAxisDialog::~XYplotAxisDialog()
{
delete ui;
}
void XYplotAxisDialog::on_buttonBox_accepted()
{
// set plot values to the ones selected in the dialog
plot->setYAxis(0, (TraceXYPlot::YAxisType) ui->Y1type->currentIndex(), ui->Y1log->isChecked(), ui->Y1auto->isChecked(), ui->Y1min->value(), ui->Y1max->value(), ui->Y1divs->value());
plot->setYAxis(1, (TraceXYPlot::YAxisType) ui->Y2type->currentIndex(), ui->Y2log->isChecked(), ui->Y2auto->isChecked(), ui->Y2min->value(), ui->Y2max->value(), ui->Y2divs->value());
plot->setXAxis((TraceXYPlot::XAxisType) ui->XType->currentIndex(), ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value());
}
void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex)
{
auto type = (TraceXYPlot::XAxisType) XAxisIndex;
auto supported = supportedYAxis(type);
for(auto t : TraceXYPlot::YAxisTypes) {
auto enable = supported.count(t) > 0;
auto index = (int) t;
auto *model = qobject_cast<QStandardItemModel *>(ui->Y1type->model());
auto item = model->item(index);
item->setFlags(enable ? item->flags() | Qt::ItemIsEnabled
: item->flags() & ~Qt::ItemIsEnabled);
model = qobject_cast<QStandardItemModel *>(ui->Y2type->model());
item = model->item(index);
item->setFlags(enable ? item->flags() | Qt::ItemIsEnabled
: item->flags() & ~Qt::ItemIsEnabled);
}
// Disable Yaxis if previously selected type is not supported
if(!supported.count((TraceXYPlot::YAxisType)ui->Y1type->currentIndex())) {
ui->Y1type->setCurrentIndex(0);
}
if(!supported.count((TraceXYPlot::YAxisType)ui->Y2type->currentIndex())) {
ui->Y2type->setCurrentIndex(0);
}
QString unit;
switch(type) {
case TraceXYPlot::XAxisType::Frequency: unit = "Hz"; break;
case TraceXYPlot::XAxisType::Time: unit = "s"; break;
case TraceXYPlot::XAxisType::Distance: unit = "m"; break;
}
ui->Xmin->setUnit(unit);
ui->Xmax->setUnit(unit);
ui->Xdivs->setUnit(unit);
}
QString XYplotAxisDialog::YAxisUnit(TraceXYPlot::YAxisType type)
{
switch(type) {
case TraceXYPlot::YAxisType::Magnitude: return "db"; break;
case TraceXYPlot::YAxisType::Phase: return "°"; break;
case TraceXYPlot::YAxisType::VSWR: return ""; break;
case TraceXYPlot::YAxisType::Impulse: return ""; break;
case TraceXYPlot::YAxisType::Step: return ""; break;
case TraceXYPlot::YAxisType::Impedance: return "Ohm"; break;
default: return ""; break;
}
}
std::set<TraceXYPlot::YAxisType> XYplotAxisDialog::supportedYAxis(TraceXYPlot::XAxisType type)
{
set<TraceXYPlot::YAxisType> ret = {TraceXYPlot::YAxisType::Disabled};
switch(type) {
case TraceXYPlot::XAxisType::Frequency:
ret.insert(TraceXYPlot::YAxisType::Magnitude);
ret.insert(TraceXYPlot::YAxisType::Phase);
ret.insert(TraceXYPlot::YAxisType::VSWR);
break;
case TraceXYPlot::XAxisType::Time:
case TraceXYPlot::XAxisType::Distance:
ret.insert(TraceXYPlot::YAxisType::Impulse);
ret.insert(TraceXYPlot::YAxisType::Step);
ret.insert(TraceXYPlot::YAxisType::Impedance);
break;
}
return ret;
}

View File

@ -0,0 +1,30 @@
#ifndef XYPLOTAXISDIALOG_H
#define XYPLOTAXISDIALOG_H
#include <QDialog>
#include "tracexyplot.h"
namespace Ui {
class XYplotAxisDialog;
}
class XYplotAxisDialog : public QDialog
{
Q_OBJECT
public:
explicit XYplotAxisDialog(TraceXYPlot *plot);
~XYplotAxisDialog();
private slots:
void on_buttonBox_accepted();
void XAxisTypeChanged(int XAxisIndex);
private:
QString YAxisUnit(TraceXYPlot::YAxisType type);
std::set<TraceXYPlot::YAxisType> supportedYAxis(TraceXYPlot::XAxisType type);
Ui::XYplotAxisDialog *ui;
TraceXYPlot *plot;
};
#endif // XYPLOTAXISDIALOG_H

View File

@ -0,0 +1,547 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BodeplotAxisDialog</class>
<widget class="QDialog" name="BodeplotAxisDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>715</width>
<height>282</height>
</rect>
</property>
<property name="windowTitle">
<string>Axis Setup</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>9</x>
<y>248</y>
<width>166</width>
<height>25</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QWidget" name="">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>701</width>
<height>233</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Primary Y axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="Y1type">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude</string>
</property>
</item>
<item>
<property name="text">
<string>Phase</string>
</property>
</item>
<item>
<property name="text">
<string>VSWR</string>
</property>
</item>
<item>
<property name="text">
<string>Impulse Response</string>
</property>
</item>
<item>
<property name="text">
<string>Step Response</string>
</property>
</item>
<item>
<property name="text">
<string>Impedance</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="Y1linear">
<property name="text">
<string>Linear</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y1group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Y1log">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Log</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y1group</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Y1auto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Y1max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Y1min"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Y1divs"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_7">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Secondary Y axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="Y2type">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude</string>
</property>
</item>
<item>
<property name="text">
<string>Phase</string>
</property>
</item>
<item>
<property name="text">
<string>VSWR</string>
</property>
</item>
<item>
<property name="text">
<string>Impulse Response</string>
</property>
</item>
<item>
<property name="text">
<string>Step Response</string>
</property>
</item>
<item>
<property name="text">
<string>Impedance</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="Y2linear">
<property name="text">
<string>Linear</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y2group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Y2log">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Log</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y2group</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Y2auto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Y2max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Y2min"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Y2divs"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_13">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>X axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="XType">
<item>
<property name="text">
<string>Frequency</string>
</property>
</item>
<item>
<property name="text">
<string>Time</string>
</property>
</item>
<item>
<property name="text">
<string>Distance</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QFormLayout" name="formLayout_6">
<item row="0" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Xauto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Xmax"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Xmin"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Xdivs"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>SIUnitEdit</class>
<extends>QLineEdit</extends>
<header>CustomWidgets/siunitedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>BodeplotAxisDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>BodeplotAxisDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="Y1group"/>
<buttongroup name="Y2group"/>
</buttongroups>
</ui>

View File

@ -26,7 +26,7 @@
#include "Traces/tracemodel.h"
#include "Traces/tracewidget.h"
#include "Traces/tracesmithchart.h"
#include "Traces/tracebodeplot.h"
#include "Traces/tracexyplot.h"
#include "Traces/traceimportdialog.h"
#include "CustomWidgets/tilewidget.h"
#include "CustomWidgets/siunitedit.h"
@ -70,9 +70,9 @@ VNA::VNA(AppWindow *window)
auto tracesmith2 = new TraceSmithChart(traceModel);
tracesmith2->enableTrace(tS22, true);
auto tracebode1 = new TraceBodePlot(traceModel);
auto tracebode1 = new TraceXYPlot(traceModel);
tracebode1->enableTrace(tS12, true);
auto tracebode2 = new TraceBodePlot(traceModel);
auto tracebode2 = new TraceXYPlot(traceModel);
tracebode2->enableTrace(tS21, true);
connect(&traceModel, &TraceModel::requiredExcitation, this, &VNA::ExcitationRequired);

View File

@ -25,7 +25,7 @@
#include "Traces/tracemodel.h"
#include "Traces/tracewidget.h"
#include "Traces/tracesmithchart.h"
#include "Traces/tracebodeplot.h"
#include "Traces/tracexyplot.h"
#include "Traces/traceimportdialog.h"
#include "CustomWidgets/tilewidget.h"
#include "CustomWidgets/siunitedit.h"
@ -110,7 +110,7 @@ AppWindow::AppWindow(QWidget *parent)
connect(ui->actionPreferences, &QAction::triggered, [=](){
Preferences::getInstance().edit();
// settings might have changed, update necessary stuff
TraceBodePlot::updateGraphColors();
TraceXYPlot::updateGraphColors();
});
setWindowTitle("VNA");