Autoscale improvements

This commit is contained in:
Jan Käberich 2020-11-01 22:56:31 +01:00
parent 3976db8f9d
commit 32270dc747
12 changed files with 640 additions and 513 deletions

View File

@ -130,7 +130,9 @@ void TileWidget::on_bSmithchart_clicked()
void TileWidget::on_bXYplot_clicked()
{
setContent(new TraceXYPlot(model));
auto plot = new TraceXYPlot(model);
setContent(plot);
plot->axisSetupDialog();
}
void TileWidget::traceDeleted(TracePlot *)

View File

@ -18,6 +18,7 @@ MarkerWidget::MarkerWidget(TraceMarkerModel &model, QWidget *parent) :
connect(&model.getModel(), &TraceModel::traceAdded, this, &MarkerWidget::updatePersistentEditors);
connect(&model.getModel(), &TraceModel::traceRemoved, this, &MarkerWidget::updatePersistentEditors);
connect(&model.getModel(), &TraceModel::traceNameChanged, this, &MarkerWidget::updatePersistentEditors);
}
MarkerWidget::~MarkerWidget()

View File

@ -156,16 +156,22 @@ void Trace::updateTimeDomainData()
// system_clock::now().time_since_epoch()
// ).count();
auto steps = size();
if(minFreq() * size() != maxFreq()) {
auto firstStep = minFreq();
if(firstStep == 0) {
// zero as first step would result in infinite number of points, skip and start with second
firstStep = _data[1].frequency;
steps--;
}
if(firstStep * steps != maxFreq()) {
// data is not available with correct frequency spacing, calculate required steps
steps = maxFreq() / minFreq();
steps = maxFreq() / firstStep;
}
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);
auto S = getData(firstStep * i);
constexpr double alpha0 = 0.54;
auto hamming = alpha0 - (1.0 - alpha0) * -cos(PI * i / steps);
S *= hamming;
@ -180,13 +186,18 @@ void Trace::updateTimeDomainData()
auto fft_bins = frequencyDomain.size();
timeDomain.clear();
timeDomain.resize(fft_bins);
const double fs = 1.0 / (minFreq() * fft_bins);
const double fs = 1.0 / (firstStep * fft_bins);
double last_step = 0.0;
Fft::transform(frequencyDomain, true);
constexpr double c = 299792458;
for(unsigned int i = 0;i<fft_bins;i++) {
TimedomainData t;
t.time = fs * i;
t.distance = t.time * c * 0.66; // TODO user settable velocity factor
if(isReflection()) {
t.distance /= 2;
}
t.impulseResponse = real(frequencyDomain[i]) / fft_bins;
t.stepResponse = last_step;
last_step += t.impulseResponse;

View File

@ -24,6 +24,7 @@ public:
class TimedomainData {
public:
double time;
double distance;
double impulseResponse;
double stepResponse;
};

View File

@ -20,6 +20,9 @@ TraceModel::~TraceModel()
void TraceModel::addTrace(Trace *t)
{
beginInsertRows(QModelIndex(), traces.size(), traces.size());
connect(t, &Trace::nameChanged, [=]() {
emit traceNameChanged(t);
});
traces.push_back(t);
endInsertRows();
emit traceAdded(t);

View File

@ -32,6 +32,7 @@ signals:
void traceAdded(Trace *t);
void traceRemoved(Trace *t);
void requiredExcitation(bool excitePort1, bool excitePort2);
void traceNameChanged(Trace *t);
public slots:
void clearVNAData();

View File

@ -121,8 +121,8 @@ void TracePlot::triggerReplot()
{
auto now = QTime::currentTime();
if (lastUpdate.msecsTo(now) >= MinUpdateInterval) {
replot();
lastUpdate = now;
replot();
}
}

View File

@ -16,7 +16,7 @@ public:
virtual void enableTrace(Trace *t, bool enabled);
void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void setXAxis(double min, double max){Q_UNUSED(min);Q_UNUSED(max)};
virtual void updateSpan(double min, double max){Q_UNUSED(min);Q_UNUSED(max)};
static std::set<TracePlot *> getPlots();

View File

@ -91,8 +91,11 @@ public:
case TraceXYPlot::YAxisType::Impedance: {
auto sample = t.getTDR()[i];
QPointF p;
// TODO set distance
if(Xtype == TraceXYPlot::XAxisType::Time) {
p.setX(sample.time);
} else {
p.setX(sample.distance);
}
p.setY(TimeAxisTransformation(Ytype, &t, i));
return p;
}
@ -116,23 +119,23 @@ TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent)
selectedMarker(nullptr)
{
YAxis[0].log = false;
YAxis[0].Ytype = YAxisType::Disabled;
YAxis[0].type = YAxisType::Disabled;
YAxis[0].rangeDiv = 1;
YAxis[0].rangeMax = 10;
YAxis[0].rangeMin = 0;
YAxis[0].autorange = false;
YAxis[1].log = false;
YAxis[1].Ytype = YAxisType::Disabled;
YAxis[1].type = YAxisType::Disabled;
YAxis[1].rangeDiv = 1;
YAxis[1].rangeMax = 10;
YAxis[1].rangeMin = 0;
YAxis[1].autorange = false;
XAxis.Xtype = XAxisType::Frequency;
XAxis.type = XAxisType::Frequency;
XAxis.log = false;
XAxis.rangeDiv = 1;
XAxis.rangeMax = 10;
XAxis.rangeMin = 0;
XAxis.autorange = true;
XAxis.mode = XAxisMode::UseSpan;
plot = new QwtPlot(this);
@ -168,10 +171,10 @@ TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent)
setYAxis(0, YAxisType::Magnitude, false, false, -120, 20, 10);
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(XAxisType::Frequency, true, 0, 6000000000, 600000000);
updateSpan(0, 6000000000);
setXAxis(XAxisType::Frequency, XAxisMode::UseSpan, 0, 6000000000, 600000000);
// get notified when the span changes
connect(&model, &TraceModel::SpanChanged, this, qOverload<double, double>(&TraceXYPlot::setXAxis));
connect(&model, &TraceModel::SpanChanged, this, qOverload<double, double>(&TraceXYPlot::updateSpan));
allPlots.insert(this);
}
@ -187,16 +190,15 @@ TraceXYPlot::~TraceXYPlot()
allPlots.erase(this);
}
void TraceXYPlot::setXAxis(double min, double max)
void TraceXYPlot::updateSpan(double min, double max)
{
sweep_fmin = min;
sweep_fmax = max;
updateXAxis();
}
void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool autorange, double min, double max, double div)
{
if(YAxis[axis].Ytype != type) {
if(YAxis[axis].type != type) {
// remove traces that are active but not supported with the new axis type
bool erased = false;
do {
@ -210,12 +212,12 @@ void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool
}
} while(erased);
if(isTDRtype(YAxis[axis].Ytype)) {
if(isTDRtype(YAxis[axis].type)) {
for(auto t : tracesAxis[axis]) {
t->removeTDRinterest();
}
}
YAxis[axis].Ytype = type;
YAxis[axis].type = type;
for(auto t : tracesAxis[axis]) {
// supported but needs an adjusted QwtSeriesData
@ -252,20 +254,19 @@ void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool
replot();
}
void TraceXYPlot::setXAxis(XAxisType type, bool autorange, double min, double max, double div)
void TraceXYPlot::setXAxis(XAxisType type, XAxisMode mode, double min, double max, double div)
{
XAxis.Xtype = type;
XAxis.autorange = autorange;
XAxis.type = type;
XAxis.mode = mode;
XAxis.rangeMin = min;
XAxis.rangeMax = max;
XAxis.rangeDiv = div;
updateXAxis();
}
void TraceXYPlot::enableTrace(Trace *t, bool enabled)
{
for(int axis = 0;axis < 2;axis++) {
if(supported(t, YAxis[axis].Ytype)) {
if(supported(t, YAxis[axis].type)) {
enableTraceAxis(t, axis, enabled);
}
}
@ -290,17 +291,20 @@ bool TraceXYPlot::isTDRtype(TraceXYPlot::YAxisType type)
}
}
void TraceXYPlot::axisSetupDialog()
{
auto setup = new XYplotAxisDialog(this);
setup->show();
}
void TraceXYPlot::updateContextMenu()
{
contextmenu->clear();
auto setup = new QAction("Axis setup...", contextmenu);
connect(setup, &QAction::triggered, [this]() {
auto setup = new XYplotAxisDialog(this);
setup->show();
});
connect(setup, &QAction::triggered, this, &TraceXYPlot::axisSetupDialog);
contextmenu->addAction(setup);
for(int axis = 0;axis < 2;axis++) {
if(YAxis[axis].Ytype == YAxisType::Disabled) {
if(YAxis[axis].type == YAxisType::Disabled) {
continue;
}
if(axis == 0) {
@ -310,7 +314,7 @@ void TraceXYPlot::updateContextMenu()
}
for(auto t : traces) {
// Skip traces that are not applicable for the selected axis type
if(!supported(t.first, YAxis[axis].Ytype)) {
if(!supported(t.first, YAxis[axis].type)) {
continue;
}
@ -341,6 +345,7 @@ bool TraceXYPlot::supported(Trace *)
void TraceXYPlot::replot()
{
updateXAxis();
plot->replot();
}
@ -381,12 +386,12 @@ void TraceXYPlot::enableTraceAxis(Trace *t, int axis, bool enabled)
markerAdded(m);
}
}
if(isTDRtype(YAxis[axis].Ytype)) {
if(isTDRtype(YAxis[axis].type)) {
t->addTDRinterest();
}
traceColorChanged(t);
} else {
if(isTDRtype(YAxis[axis].Ytype)) {
if(isTDRtype(YAxis[axis].type)) {
t->removeTDRinterest();
}
tracesAxis[axis].erase(t);
@ -436,22 +441,78 @@ bool TraceXYPlot::supported(Trace *t, TraceXYPlot::YAxisType type)
void TraceXYPlot::updateXAxis()
{
if(XAxis.autorange && sweep_fmax-sweep_fmin > 0) {
if(XAxis.mode == XAxisMode::Manual) {
plot->setAxisScale(QwtPlot::xBottom, XAxis.rangeMin, XAxis.rangeMax, XAxis.rangeDiv);
} else {
// automatic mode, figure out limits
double max = std::numeric_limits<double>::lowest();
double min = std::numeric_limits<double>::max();
if(XAxis.mode == XAxisMode::UseSpan) {
min = sweep_fmin;
max = sweep_fmax;
} else if(XAxis.mode == XAxisMode::FitTraces) {
for(auto t : traces) {
bool enabled = (tracesAxis[0].find(t.first) != tracesAxis[0].end()
|| tracesAxis[1].find(t.first) != tracesAxis[1].end());
auto trace = t.first;
if(enabled && trace->isVisible()) {
// this trace is currently displayed
double trace_min, trace_max;
switch(XAxis.type) {
case XAxisType::Frequency:
trace_min = trace->minFreq();
trace_max = trace->maxFreq();
break;
case XAxisType::Time:
trace_min = trace->getTDR().front().time;
trace_max = trace->getTDR().back().time;
break;
case XAxisType::Distance:
trace_min = trace->getTDR().front().distance;
trace_max = trace->getTDR().back().distance;
break;
}
if(trace_min < min) {
min = trace_min;
}
if(trace_max > max) {
max = trace_max;
}
}
}
}
if(min >= max) {
// still at initial values, no traces are active, leave axis unchanged
return;
}
constexpr int minDivisions = 8;
double max_div_step = (max - min) / minDivisions;
int zeros = floor(log10(max_div_step));
double decimals_shift = pow(10, zeros);
max_div_step /= decimals_shift;
if(max_div_step >= 5) {
max_div_step = 5;
} else if(max_div_step >= 2) {
max_div_step = 2;
} else {
max_div_step = 1;
}
auto div_step = max_div_step * decimals_shift;
// round min up to next multiple of div_step
min = ceil(min / div_step) * div_step;
QList<double> tickList;
for(double tick = sweep_fmin;tick <= sweep_fmax;tick+= (sweep_fmax-sweep_fmin)/10) {
for(double tick = min;tick <= max;tick += div_step) {
tickList.append(tick);
}
QwtScaleDiv scalediv(sweep_fmin, sweep_fmax, QList<double>(), QList<double>(), tickList);
QwtScaleDiv scalediv(min, max, QList<double>(), QList<double>(), tickList);
plot->setAxisScaleDiv(QwtPlot::xBottom, scalediv);
} else {
plot->setAxisScale(QwtPlot::xBottom, XAxis.rangeMin, XAxis.rangeMax, XAxis.rangeDiv);
}
triggerReplot();
}
QwtSeriesData<QPointF> *TraceXYPlot::createQwtSeriesData(Trace &t, int axis)
{
return new QwtTraceSeries(t, YAxis[axis].Ytype, XAxis.Xtype);
return new QwtTraceSeries(t, YAxis[axis].type, XAxis.type);
}
void TraceXYPlot::traceColorChanged(Trace *t)
@ -513,7 +574,7 @@ void TraceXYPlot::markerDataChanged(TraceMarker *m)
{
auto qwtMarker = markers[m];
qwtMarker->setXValue(m->getFrequency());
qwtMarker->setYValue(FrequencyAxisTransformation(YAxis[0].Ytype, m->getData()));
qwtMarker->setYValue(FrequencyAxisTransformation(YAxis[0].type, m->getData()));
triggerReplot();
}

View File

@ -50,17 +50,24 @@ public:
Time,
Distance,
};
enum class XAxisMode {
UseSpan,
FitTraces,
Manual,
};
virtual void setXAxis(double min, double max) override;
virtual void updateSpan(double min, double max) override;
void setYAxis(int axis, YAxisType type, bool log, bool autorange, double min, double max, double div);
void setXAxis(XAxisType type, bool autorange, double min, double max, double div);
void setXAxis(XAxisType type, XAxisMode mode, double min, double max, double div);
void enableTrace(Trace *t, bool enabled) override;
// Applies potentially changed colors to all XY-plots
static void updateGraphColors();
bool isTDRtype(YAxisType type);
public slots:
void axisSetupDialog();
protected:
virtual void updateContextMenu() override;
virtual bool supported(Trace *t) override;
@ -85,20 +92,27 @@ private:
std::set<Trace*> tracesAxis[2];
class Axis {
class YAxis {
public:
union {
YAxisType Ytype;
XAxisType Xtype;
};
bool log;
YAxisType type;
bool log; // not used yet
bool autorange;
double rangeMin;
double rangeMax;
double rangeDiv;
};
Axis YAxis[2];
Axis XAxis;
class XAxis {
public:
XAxisType type;
XAxisMode mode;
bool log; // not used yet
double rangeMin;
double rangeMax;
double rangeDiv;
};
YAxis YAxis[2];
XAxis XAxis;
double sweep_fmin, sweep_fmax;
using CurveData = struct {

View File

@ -59,16 +59,17 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
ui->Xmin->setEnabled(!checked);
ui->Xmax->setEnabled(!checked);
ui->Xdivs->setEnabled(!checked);
ui->Xautomode->setEnabled(checked);
});
ui->XType->setCurrentIndex((int) plot->XAxis.Xtype);
ui->XType->setCurrentIndex((int) plot->XAxis.type);
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);
ui->Y1type->setCurrentIndex((int) plot->YAxis[0].type);
if(plot->YAxis[0].log) {
ui->Y1log->setChecked(true);
} else {
@ -79,7 +80,7 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
ui->Y1max->setValueQuiet(plot->YAxis[0].rangeMax);
ui->Y1divs->setValueQuiet(plot->YAxis[0].rangeDiv);
ui->Y2type->setCurrentIndex((int) plot->YAxis[1].Ytype);
ui->Y2type->setCurrentIndex((int) plot->YAxis[1].type);
if(plot->YAxis[1].log) {
ui->Y2log->setChecked(true);
} else {
@ -90,7 +91,12 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax);
ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv);
ui->Xauto->setChecked(plot->XAxis.autorange);
ui->Xauto->setChecked(plot->XAxis.mode != TraceXYPlot::XAxisMode::Manual);
if(plot->XAxis.mode == TraceXYPlot::XAxisMode::UseSpan) {
ui->Xautomode->setCurrentIndex(0);
} else {
ui->Xautomode->setCurrentIndex(1);
}
ui->Xmin->setValueQuiet(plot->XAxis.rangeMin);
ui->Xmax->setValueQuiet(plot->XAxis.rangeMax);
ui->Xdivs->setValueQuiet(plot->XAxis.rangeDiv);
@ -106,7 +112,24 @@ 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());
TraceXYPlot::XAxisMode mode;
if(ui->Xauto->isChecked()) {
if(ui->Xautomode->currentIndex() == 0) {
mode = TraceXYPlot::XAxisMode::UseSpan;
} else {
mode = TraceXYPlot::XAxisMode::FitTraces;
}
} else {
mode = TraceXYPlot::XAxisMode::Manual;
}
plot->setXAxis((TraceXYPlot::XAxisType) ui->XType->currentIndex(), mode, ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value());
}
static void enableComboBoxItem(QComboBox *cb, int itemNum, bool enable) {
auto *model = qobject_cast<QStandardItemModel *>(cb->model());
auto item = model->item(itemNum);
item->setFlags(enable ? item->flags() | Qt::ItemIsEnabled
: item->flags() & ~Qt::ItemIsEnabled);
}
void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex)
@ -116,14 +139,8 @@ void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex)
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);
enableComboBoxItem(ui->Y1type, index, enable);
enableComboBoxItem(ui->Y2type, index, enable);
}
// Disable Yaxis if previously selected type is not supported
if(!supported.count((TraceXYPlot::YAxisType)ui->Y1type->currentIndex())) {
@ -133,6 +150,16 @@ void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex)
ui->Y2type->setCurrentIndex(0);
}
if(type == TraceXYPlot::XAxisType::Frequency) {
enableComboBoxItem(ui->Xautomode, 0, true);
} else {
// auto mode using span not supported in time mode
if(ui->Xautomode->currentIndex() == 0) {
ui->Xautomode->setCurrentIndex(1);
enableComboBoxItem(ui->Xautomode, 0, false);
}
}
QString unit;
switch(type) {
case TraceXYPlot::XAxisType::Frequency: unit = "Hz"; break;

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>715</width>
<width>689</width>
<height>282</height>
</rect>
</property>
@ -16,32 +16,9 @@
<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="layoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>701</width>
<height>233</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
@ -455,12 +432,30 @@
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="Xauto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="Xautomode">
<item>
<property name="text">
<string>Use Span</string>
</property>
</item>
<item>
<property name="text">
<string>Fit Traces</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
@ -496,7 +491,18 @@
</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>