Updated graphs to use new math system

This commit is contained in:
Jan Käberich 2020-11-28 22:34:40 +01:00
parent a7ff3d60fb
commit 49f9b5442d
16 changed files with 263 additions and 518 deletions

View File

@ -23,7 +23,7 @@ TraceMath::DataType TDRLowpass::outputType(TraceMath::DataType inputType)
QString TDRLowpass::description()
{
return "TDR (bandpass mode)";
return "TDR (lowpass mode)";
}
void TDRLowpass::edit()
@ -85,11 +85,13 @@ void TDRLowpass::inputSamplesChanged(unsigned int begin, unsigned int end)
data[i].x = fs * i;
data[i].y = frequencyDomain[i] / (double) fft_bins;
}
updateStepResponse(true);
emit outputSamplesChanged(0, data.size());
success();
} else {
// not enough input data
data.clear();
updateStepResponse(false);
emit outputSamplesChanged(0, 0);
warning("Not enough input samples");
}

View File

@ -52,6 +52,15 @@ TraceMath::Data TraceMath::getSample(unsigned int index)
return data.at(index);
}
double TraceMath::getStepResponse(unsigned int index)
{
if(stepResponse.size() > index) {
return stepResponse[index];
} else {
return std::numeric_limits<double>::quiet_NaN();
}
}
TraceMath::Data TraceMath::getInterpolatedSample(double x)
{
Data ret;
@ -120,6 +129,7 @@ void TraceMath::inputTypeChanged(TraceMath::DataType type)
emit outputTypeChanged(dataType);
if(dataType == DataType::Invalid) {
error("Invalid input data");
updateStepResponse(false);
}
}
}
@ -146,6 +156,20 @@ void TraceMath::success()
}
}
void TraceMath::updateStepResponse(bool valid)
{
if(valid) {
stepResponse.resize(data.size());
double accumulate = 0.0;
for(unsigned int i=0;i<data.size();i++) {
accumulate += data[i].y.real();
stepResponse[i] = accumulate;
}
} else {
stepResponse.clear();
}
}
QString TraceMath::getStatusDescription() const
{
return statusString;

View File

@ -81,6 +81,7 @@ public:
static TypeInfo getInfo(Type type);
Data getSample(unsigned int index);
double getStepResponse(unsigned int index);
Data getInterpolatedSample(double x);
unsigned int numSamples();
@ -115,6 +116,11 @@ protected:
void error(QString err);
void success();
std::vector<Data> data;
// buffer for time domain step response data. This makes it possible to access an arbitrary sample of the step response without having to
// integrate the impulse response every time. Call updateStepResponse in your derived class, if step response data is valid after updating
// data.
std::vector<double> stepResponse;
void updateStepResponse(bool valid);
TraceMath *input;
DataType dataType;

View File

@ -5,8 +5,7 @@
using namespace std;
Trace::Trace(QString name, QColor color, LiveParameter live)
: tdr_users(0),
_name(name),
: _name(name),
_color(color),
_liveType(LivedataType::Overwrite),
_liveParam(live),
@ -75,12 +74,6 @@ void Trace::addData(const Trace::Data& d) {
}
success();
emit outputSamplesChanged(lower - data.begin(), lower - data.begin() + 1);
if(lower == data.begin()) {
// received the first point, which means the last sweep just finished
if(tdr_users) {
updateTimeDomainData();
}
}
}
void Trace::addData(const Trace::Data &d, const Protocol::SweepSettings &s)
@ -168,78 +161,6 @@ void Trace::removeMarker(TraceMarker *m)
markers.erase(m);
emit markerRemoved(m);
}
//#include <iostream>
//#include <chrono>
void Trace::updateTimeDomainData()
{
if(data.size() < 2) {
// can't compute anything
timeDomain.clear();
return;
}
// using namespace std::chrono;
// auto starttime = duration_cast< milliseconds >(
// system_clock::now().time_since_epoch()
// ).count();
auto steps = size();
auto firstStep = minFreq();
if(firstStep == 0) {
// zero as first step would result in infinite number of points, skip and start with second
firstStep = lastMath->rData()[1].x;
steps--;
}
if(firstStep * steps != maxFreq()) {
// data is not available with correct frequency spacing, calculate required steps
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(firstStep * 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 / (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;
if(abs(t.stepResponse) < 1.0) {
t.impedance = 50.0 * (1+t.stepResponse) / (1-t.stepResponse);
} else {
t.impedance = numeric_limits<double>::quiet_NaN();
}
last_step += t.impulseResponse;
timeDomain[i] = t;
}
// auto duration = duration_cast< milliseconds >(
// system_clock::now().time_since_epoch()
// ).count() - starttime;
// cout << "TDR: " << this << " (took " << duration << "ms)" <<endl;
}
const std::vector<Trace::MathInfo>& Trace::getMathOperations() const
{
@ -263,6 +184,7 @@ void Trace::updateLastMath(vector<MathInfo>::reverse_iterator start)
lastMath = newLast;
// relay signals of end of math chain
connect(lastMath, &TraceMath::outputSamplesChanged, this, &Trace::dataChanged);
emit typeChanged(this);
emit outputSamplesChanged(0, data.size());
}
}
@ -272,77 +194,6 @@ 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++;
if(tdr_users == 1) {
emit changedTDRstate(true);
}
}
void Trace::removeTDRinterest()
{
if(tdr_users > 0) {
tdr_users--;
if(tdr_users == 0) {
emit changedTDRstate(false);
}
}
}
Trace::TimedomainData Trace::getTDR(double position)
{
TimedomainData ret = {};
if(!TDRactive() || position < 0) {
return ret;
}
int index = 0;
bool exact = false;
double alpha = 0.0;
if(position <= timeDomain.back().time) {
auto lower = lower_bound(timeDomain.begin(), timeDomain.end(), position, [](const TimedomainData &lhs, const double pos) -> bool {
return lhs.time < pos;
});
index = lower - timeDomain.begin();
if(timeDomain.at(index).time == position) {
exact = true;
} else {
alpha = (position - timeDomain.at(index-1).time) / (timeDomain.at(index).time - timeDomain.at(index-1).time);
}
} else {
if(position > timeDomain.back().distance) {
// too high, invalid position
return ret;
}
auto lower = lower_bound(timeDomain.begin(), timeDomain.end(), position, [](const TimedomainData &lhs, const double pos) -> bool {
return lhs.distance < pos;
});
index = lower - timeDomain.begin();
if(timeDomain.at(index).distance == position) {
exact = true;
} else {
alpha = (position - timeDomain.at(index-1).distance) / (timeDomain.at(index).distance - timeDomain.at(index-1).distance);
}
}
if(exact) {
return timeDomain.at(index);
} else {
// need to interpolate
auto low = timeDomain.at(index-1);
auto high = timeDomain.at(index);
ret.time = low.time * (1.0 - alpha) + high.time * alpha;
ret.distance = low.distance * (1.0 - alpha) + high.distance * alpha;
ret.stepResponse = low.stepResponse * (1.0 - alpha) + high.stepResponse * alpha;
ret.impulseResponse = low.impulseResponse * (1.0 - alpha) + high.impulseResponse * alpha;
ret.impedance = low.impedance * (1.0 - alpha) + high.impedance * alpha;
return ret;
}
}
QString Trace::description()
{
return name() + ": measured data";
@ -502,26 +353,30 @@ unsigned int Trace::size()
return lastMath->numSamples();
}
double Trace::minFreq()
double Trace::minX()
{
if(size() > 0) {
return data.front().x;
if(lastMath->numSamples() > 0) {
return lastMath->rData().front().x;
} else {
return 0.0;
return numeric_limits<double>::quiet_NaN();
}
}
double Trace::maxFreq()
double Trace::maxX()
{
if(size() > 0) {
return data.back().x;
if(lastMath->numSamples() > 0) {
return lastMath->rData().back().x;
} else {
return 0.0;
return numeric_limits<double>::quiet_NaN();
}
}
double Trace::findExtremumFreq(bool max)
{
if(lastMath->getDataType() != DataType::Frequency) {
// not in frequency domain
return numeric_limits<double>::quiet_NaN();
}
double compare = max ? numeric_limits<double>::min() : numeric_limits<double>::max();
double freq = 0.0;
for(auto sample : lastMath->rData()) {
@ -537,6 +392,10 @@ double Trace::findExtremumFreq(bool max)
std::vector<double> Trace::findPeakFrequencies(unsigned int maxPeaks, double minLevel, double minValley)
{
if(lastMath->getDataType() != DataType::Frequency) {
// not in frequency domain
return vector<double>();
}
using peakInfo = struct peakinfo {
double frequency;
double level_dbm;
@ -587,9 +446,14 @@ std::vector<double> Trace::findPeakFrequencies(unsigned int maxPeaks, double min
return frequencies;
}
Trace::Data Trace::sample(unsigned int index)
Trace::Data Trace::sample(unsigned int index, SampleType type)
{
return lastMath->getSample(index);
auto data = lastMath->getSample(index);
if(type == SampleType::TimeStep) {
// exchange impulse data with step data
data.y = lastMath->getStepResponse(index);
}
return data;
}
QString Trace::getTouchstoneFilename() const
@ -607,41 +471,23 @@ unsigned int Trace::getTouchstoneParameter() const
return touchstoneParameter;
}
std::complex<double> Trace::getData(double frequency)
{
if(lastMath->numSamples() == 0 || frequency < minFreq() || frequency > maxFreq()) {
return std::numeric_limits<std::complex<double>>::quiet_NaN();
}
auto i = index(frequency);
if(lastMath->getSample(i).x == frequency) {
return lastMath->getSample(i).y;
} else {
// no exact frequency match, needs to interpolate
auto high = lastMath->getSample(i);
auto low = lastMath->getSample(i - 1);
double alpha = (frequency - low.x) / (high.x - low.x);
return low.y * (1 - alpha) + high.y * alpha;
}
}
double Trace::getNoise(double frequency)
{
if(!isLive() || !settings.valid || (_liveParam != LiveParameter::Port1 && _liveParam != LiveParameter::Port2)) {
if(!isLive() || !settings.valid || (_liveParam != LiveParameter::Port1 && _liveParam != LiveParameter::Port2) || lastMath->getDataType() != DataType::Frequency) {
// data not suitable for noise calculation
return std::numeric_limits<double>::quiet_NaN();
}
// convert to dbm
auto dbm = 20*log10(abs(getData(frequency)));
auto dbm = 20*log10(abs(lastMath->getInterpolatedSample(frequency).y));
// convert to 1Hz bandwidth
dbm -= 10*log10(settings.SA.RBW);
return dbm;
}
int Trace::index(double frequency)
int Trace::index(double x)
{
auto lower = lower_bound(lastMath->rData().begin(), lastMath->rData().end(), frequency, [](const Data &lhs, const double freq) -> bool {
return lhs.x < freq;
auto lower = lower_bound(lastMath->rData().begin(), lastMath->rData().end(), x, [](const Data &lhs, const double x) -> bool {
return lhs.x < x;
});
return lower - lastMath->rData().begin();
}

View File

@ -19,15 +19,6 @@ public:
using Data = TraceMath::Data;
class TimedomainData {
public:
double time;
double distance;
double impulseResponse;
double stepResponse;
double impedance;
};
enum class LiveParameter {
S11,
S12,
@ -66,9 +57,10 @@ public:
bool isReflection();
LiveParameter liveParameter() { return _liveParam; }
LivedataType liveType() { return _liveType; }
TraceMath::DataType outputType() { return lastMath->getDataType(); };
unsigned int size();
double minFreq();
double maxFreq();
double minX();
double maxX();
double findExtremumFreq(bool max);
/* Searches for peaks in the trace data and returns the peak frequencies in ascending order.
* Up to maxPeaks will be returned, with higher level peaks taking priority over lower level peaks.
@ -76,30 +68,22 @@ public:
* To detect the next peak, the signal first has to drop at least minValley below the peak level.
*/
std::vector<double> findPeakFrequencies(unsigned int maxPeaks = 100, double minLevel = -100.0, double minValley = 3.0);
Data sample(unsigned int index);
enum class SampleType {
Frequency,
TimeImpulse,
TimeStep,
};
Data sample(unsigned int index, SampleType type = SampleType::Frequency);
QString getTouchstoneFilename() const;
unsigned int getTouchstoneParameter() const;
std::complex<double> getData(double frequency);
/* Returns the noise in dbm/Hz for spectrum analyzer measurements. May return NaN if calculation not possible */
double getNoise(double frequency);
int index(double frequency);
int index(double x);
std::set<TraceMarker *> getMarkers() const;
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();
bool TDRactive() { return tdr_users > 0;};
const std::vector<TimedomainData>& getTDR() { return timeDomain;}
// interpolates the TDR data
// position is assumed to be the delay if it is smaller than the maximum sampled delay, otherwise it is assumed to be the distance.
// Since the usual delay values are way smaller than the distance values this should work
TimedomainData getTDR(double position);
DataType outputType(DataType inputType) override { Q_UNUSED(inputType) return DataType::Frequency;};
QString description() override;
@ -128,7 +112,6 @@ public slots:
void addMarker(TraceMarker *m);
void removeMarker(TraceMarker *m);
private:
signals:
void cleared(Trace *t);
void typeChanged(Trace *t);
@ -139,14 +122,8 @@ signals:
void colorChanged(Trace *t);
void markerAdded(TraceMarker *m);
void markerRemoved(TraceMarker *m);
void changedTDRstate(bool enabled);
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

@ -145,8 +145,8 @@ void TraceExportDialog::selectionChanged(QComboBox *w)
points = t->size();
ui->points->setText(QString::number(points));
if(points > 0) {
lowerFreq = t->minFreq();
upperFreq = t->maxFreq();
lowerFreq = t->minX();
upperFreq = t->maxX();
ui->lowerFreq->setText(QString::number(lowerFreq));
ui->upperFreq->setText(QString::number(upperFreq));
}
@ -157,7 +157,7 @@ void TraceExportDialog::selectionChanged(QComboBox *w)
for(auto c : v1) {
for(int i=1;i<c->count();i++) {
Trace *t = qvariant_cast<Trace*>(c->itemData(i));
if(t->size() != points || (points > 0 && (t->minFreq() != lowerFreq || t->maxFreq() != upperFreq))) {
if(t->size() != points || (points > 0 && (t->minX() != lowerFreq || t->maxX() != upperFreq))) {
// this trace is not available anymore
c->removeItem(i);
// decrement to check the next index in the next loop iteration

View File

@ -35,35 +35,6 @@ TraceMarker::~TraceMarker()
emit deleted(this);
}
void TraceMarker::setTimeDomain(bool timeDomain)
{
if(timeDomain != this->timeDomain) {
// setting changed, check if actually available
if(timeDomain && !parentTrace->TDRactive()) {
qWarning() << "Attempted to enable TDR marker on trace without active TDR";
return;
}
this->timeDomain = timeDomain;
if(timeDomain) {
// need to delete this marker if the TDR data of the trace is no longer available
connect(parentTrace, &Trace::changedTDRstate, [=](bool tdr_available){
if(!tdr_available) {
delete this;
}
});
// check if current type still supported
if(!getSupportedTypes().count(type)) {
// unsupported type, change to manual
setType(Type::Manual);
}
} else {
disconnect(parentTrace, &Trace::changedTDRstate, nullptr, nullptr);
}
emit timeDomainChanged();
}
}
void TraceMarker::assignTrace(Trace *t)
{
if(parentTrace) {
@ -73,7 +44,6 @@ void TraceMarker::assignTrace(Trace *t)
disconnect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged);
disconnect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
}
setTimeDomain(false);
parentTrace = t;
if(!getSupportedTypes().count(type)) {
// new trace does not support the current type
@ -104,44 +74,45 @@ QString TraceMarker::readableData()
case Type::Manual:
case Type::Maximum:
case Type::Minimum:
if(isTimeDomain()) {
QString ret;
ret += "Impulse:"+Unit::ToString(timeData.impulseResponse, "", "m ", 3)+" Step:"+Unit::ToString(timeData.stepResponse, "", "m ", 3)+" Impedance:";
if(isnan(timeData.impedance)) {
ret += "Invalid";
} else {
ret += Unit::ToString(timeData.impedance, "Ω", "m k", 3);
}
return ret;
} else {
// if(isTimeDomain()) {
// QString ret;
// ret += "Impulse:"+Unit::ToString(timeData.impulseResponse, "", "m ", 3)+" Step:"+Unit::ToString(timeData.stepResponse, "", "m ", 3)+" Impedance:";
// if(isnan(timeData.impedance)) {
// ret += "Invalid";
// } else {
// ret += Unit::ToString(timeData.impedance, "Ω", "m k", 3);
// }
// return ret;
// } else
{
auto phase = arg(data);
return QString::number(toDecibel(), 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
}
case Type::Delta:
if(!delta || delta->isTimeDomain() != isTimeDomain()) {
if(!delta /*|| delta->isTimeDomain() != isTimeDomain()*/) {
return "Invalid delta marker";
} else {
if(isTimeDomain()) {
// calculate difference between markers
auto impulse = timeData.impulseResponse - delta->timeData.impulseResponse;
auto step = timeData.stepResponse - delta->timeData.stepResponse;
auto impedance = timeData.impedance - delta->timeData.impedance;
QString ret;
ret += "ΔImpulse:"+Unit::ToString(impulse, "", "m ", 3)+" ΔStep:"+Unit::ToString(step, "", "m ", 3)+" ΔImpedance:";
if(isnan(timeData.impedance)) {
ret += "Invalid";
} else {
ret += Unit::ToString(impedance, "Ω", "m k", 3);
}
return ret;
} else {
// if(isTimeDomain()) {
// // calculate difference between markers
// auto impulse = timeData.impulseResponse - delta->timeData.impulseResponse;
// auto step = timeData.stepResponse - delta->timeData.stepResponse;
// auto impedance = timeData.impedance - delta->timeData.impedance;
// QString ret;
// ret += "ΔImpulse:"+Unit::ToString(impulse, "", "m ", 3)+" ΔStep:"+Unit::ToString(step, "", "m ", 3)+" ΔImpedance:";
// if(isnan(timeData.impedance)) {
// ret += "Invalid";
// } else {
// ret += Unit::ToString(impedance, "Ω", "m k", 3);
// }
// return ret;
// } else {
// calculate difference between markers
auto freqDiff = position - delta->position;
auto valueDiff = data / delta->data;
auto phase = arg(valueDiff);
auto db = 20*log10(abs(valueDiff));
return Unit::ToString(freqDiff, "Hz", " kMG") + " / " + QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
}
// }
}
break;
case Type::Noise:
@ -205,22 +176,22 @@ QString TraceMarker::readableData()
QString TraceMarker::readableSettings()
{
if(timeDomain) {
switch(type) {
case Type::Manual:
case Type::Delta: {
QString unit;
if(position <= parentTrace->getTDR().back().time) {
unit = "s";
} else {
unit = "m";
}
return Unit::ToString(position, unit, "fpnum k", 4);
}
default:
return "Unhandled case";
}
} else {
// if(timeDomain) {
// switch(type) {
// case Type::Manual:
// case Type::Delta: {
// QString unit;
// if(position <= parentTrace->getTDR().back().time) {
// unit = "s";
// } else {
// unit = "m";
// }
// return Unit::ToString(position, unit, "fpnum k", 4);
// }
// default:
// return "Unhandled case";
// }
// } else {
switch(type) {
case Type::Manual:
case Type::Maximum:
@ -241,7 +212,7 @@ QString TraceMarker::readableSettings()
default:
return "Unhandled case";
}
}
// }
}
QString TraceMarker::readableType()
@ -270,12 +241,12 @@ void TraceMarker::traceDataChanged()
{
// some data of the parent trace changed, check if marker data also changed
complex<double> newdata;
if (timeDomain) {
timeData = parentTrace->getTDR(position);
newdata = complex<double>(timeData.stepResponse, timeData.impulseResponse);
} else {
newdata = parentTrace->getData(position);
}
// if (timeDomain) {
// timeData = parentTrace->getTDR(position);
// newdata = complex<double>(timeData.stepResponse, timeData.impulseResponse);
// } else {
newdata = parentTrace->sample(parentTrace->index(position)).y;
// }
if (newdata != data) {
data = newdata;
update();
@ -309,11 +280,11 @@ std::set<TraceMarker::Type> TraceMarker::getSupportedTypes()
{
set<TraceMarker::Type> supported;
if(parentTrace) {
if(timeDomain) {
// only basic markers in time domain
supported.insert(Type::Manual);
supported.insert(Type::Delta);
} else {
// if(timeDomain) {
// // only basic markers in time domain
// supported.insert(Type::Manual);
// supported.insert(Type::Delta);
// } else {
// all traces support some basic markers
supported.insert(Type::Manual);
supported.insert(Type::Maximum);
@ -340,7 +311,7 @@ std::set<TraceMarker::Type> TraceMarker::getSupportedTypes()
break;
}
}
}
// }
}
return supported;
}
@ -348,21 +319,21 @@ std::set<TraceMarker::Type> TraceMarker::getSupportedTypes()
void TraceMarker::constrainPosition()
{
if(parentTrace) {
if(timeDomain) {
if(position < 0) {
position = 0;
} else if(position > parentTrace->getTDR().back().distance) {
position = parentTrace->getTDR().back().distance;
}
} else {
// if(timeDomain) {
// if(position < 0) {
// position = 0;
// } else if(position > parentTrace->getTDR().back().distance) {
// position = parentTrace->getTDR().back().distance;
// }
// } else {
if(parentTrace->size() > 0) {
if(position > parentTrace->maxFreq()) {
position = parentTrace->maxFreq();
} else if(position < parentTrace->minFreq()) {
position = parentTrace->minFreq();
if(position > parentTrace->maxX()) {
position = parentTrace->maxX();
} else if(position < parentTrace->minX()) {
position = parentTrace->minX();
}
}
}
// }
traceDataChanged();
}
}
@ -410,10 +381,10 @@ void TraceMarker::setType(TraceMarker::Type t)
// invalid delta marker assigned, attempt to find a matching marker
for(int pass = 0;pass < 3;pass++) {
for(auto m : model->getMarkers()) {
if(m->isTimeDomain() != isTimeDomain()) {
// markers are not on the same domain
continue;
}
// if(m->isTimeDomain() != isTimeDomain()) {
// // markers are not on the same domain
// continue;
// }
if(pass == 0 && m->parentTrace != parentTrace) {
// ignore markers on different traces in first pass
continue;
@ -484,11 +455,6 @@ QString TraceMarker::getSuffix() const
return suffix;
}
bool TraceMarker::isTimeDomain() const
{
return timeDomain;
}
const std::vector<TraceMarker *> &TraceMarker::getHelperMarkers() const
{
return helperMarkers;
@ -594,15 +560,15 @@ void TraceMarker::updateTypeFromEditor(QWidget *w)
SIUnitEdit *TraceMarker::getSettingsEditor()
{
if(timeDomain) {
switch(type) {
case Type::Manual:
case Type::Delta:
return new SIUnitEdit("", "fpnum k", 6);
default:
return nullptr;
}
} else {
// if(timeDomain) {
// switch(type) {
// case Type::Manual:
// case Type::Delta:
// return new SIUnitEdit("", "fpnum k", 6);
// default:
// return nullptr;
// }
// } else {
switch(type) {
case Type::Manual:
case Type::Maximum:
@ -619,7 +585,7 @@ SIUnitEdit *TraceMarker::getSettingsEditor()
case Type::TOI:
return nullptr;
}
}
// }
}
void TraceMarker::adjustSettings(double value)
@ -798,15 +764,6 @@ std::complex<double> TraceMarker::getData() const
return data;
}
Trace::TimedomainData TraceMarker::getTimeData() const
{
Trace::TimedomainData ret = {};
if(timeDomain) {
ret = timeData;
}
return ret;
}
bool TraceMarker::isMovable()
{
if(parent) {

View File

@ -15,7 +15,6 @@ class TraceMarker : public QObject
public:
TraceMarker(TraceMarkerModel *model, int number = 1, TraceMarker *parent = nullptr, QString descr = QString());
~TraceMarker();
void setTimeDomain(bool timeDomain);
void assignTrace(Trace *t);
Trace* trace();
QString readableData();
@ -24,7 +23,6 @@ public:
double getPosition() const;
std::complex<double> getData() const;
Trace::TimedomainData getTimeData() const;
bool isMovable();
QPixmap& getSymbol();
@ -48,8 +46,6 @@ public:
TraceMarker *helperMarker(unsigned int i);
QString getSuffix() const;
bool isTimeDomain() const;
public slots:
void setPosition(double freq);
signals:
@ -114,7 +110,6 @@ private:
// Frequency domain: S parameter
// Time domain: imag part is impulse response, real part is step response
std::complex<double> data;
Trace::TimedomainData timeData;
QPixmap symbol;
Type type;
QString suffix;
@ -128,8 +123,6 @@ private:
double peakThreshold;
double offset;
};
bool timeDomain;
};
#endif // TRACEMARKER_H

View File

@ -200,10 +200,7 @@ bool TraceMarkerModel::setData(const QModelIndex &index, const QVariant &value,
break;
case ColIndexTrace: {
auto info = qvariant_cast<MarkerWidgetTraceInfo>(value);
// always disable timedomain before switching trace
m->setTimeDomain(false);
m->assignTrace(info.trace);
m->setTimeDomain(info.isTimeDomain);
}
break;
case ColIndexSettings: {
@ -288,12 +285,7 @@ QWidget *MarkerTraceDelegate::createEditor(QWidget *parent, const QStyleOptionVi
for(auto t : traces) {
MarkerWidgetTraceInfo info;
info.trace = t;
info.isTimeDomain = false;
c->addItem(t->name(), QVariant::fromValue(info));
if(t->TDRactive()) {
info.isTimeDomain = true;
c->addItem(t->name() + " Time Domain", QVariant::fromValue(info));
}
}
return c;
}
@ -304,7 +296,6 @@ void MarkerTraceDelegate::setEditorData(QWidget *editor, const QModelIndex &inde
auto c = (QComboBox*) editor;
MarkerWidgetTraceInfo markerInfo;
markerInfo.trace = marker->trace();
markerInfo.isTimeDomain = marker->isTimeDomain();
for(int i=0;i<c->count();i++) {
auto info = qvariant_cast<MarkerWidgetTraceInfo>(c->itemData(i));
if(info == markerInfo) {

View File

@ -19,13 +19,11 @@ class MarkerTraceDelegate : public QStyledItemDelegate
class MarkerWidgetTraceInfo {
public:
Trace *trace;
bool isTimeDomain;
};
inline bool operator==(const MarkerWidgetTraceInfo& lhs, const MarkerWidgetTraceInfo& rhs)
{
return lhs.trace == rhs.trace && lhs.isTimeDomain == rhs.isTimeDomain;
return lhs.trace == rhs.trace;
}
Q_DECLARE_METATYPE(MarkerWidgetTraceInfo)

View File

@ -23,9 +23,6 @@ void TraceModel::addTrace(Trace *t)
connect(t, &Trace::nameChanged, [=]() {
emit traceNameChanged(t);
});
connect(t, &Trace::changedTDRstate, [=](bool enabled) {
emit traceTDRstateChanged(t, enabled);
});
traces.push_back(t);
endInsertRows();
emit traceAdded(t);

View File

@ -53,12 +53,14 @@ void TracePlot::enableTrace(Trace *t, bool enabled)
connect(t, &Trace::visibilityChanged, this, &TracePlot::triggerReplot);
connect(t, &Trace::markerAdded, this, &TracePlot::markerAdded);
connect(t, &Trace::markerRemoved, this, &TracePlot::markerRemoved);
connect(t, &Trace::typeChanged, this, &TracePlot::checkIfStillSupported);
} else {
// disconnect from notifications
disconnect(t, &Trace::dataChanged, this, &TracePlot::triggerReplot);
disconnect(t, &Trace::visibilityChanged, this, &TracePlot::triggerReplot);
disconnect(t, &Trace::markerAdded, this, &TracePlot::markerAdded);
disconnect(t, &Trace::markerRemoved, this, &TracePlot::markerRemoved);
disconnect(t, &Trace::typeChanged, this, &TracePlot::checkIfStillSupported);
}
updateContextMenu();
replot();
@ -271,6 +273,14 @@ void TracePlot::triggerReplot()
}
}
void TracePlot::checkIfStillSupported(Trace *t)
{
if(!supported(t)) {
// something with this trace changed and it can no longer be displayed on this graph
enableTrace(t, false);
}
}
void TracePlot::markerAdded(TraceMarker *m)
{
connect(m, &TraceMarker::dataChanged, this, &TracePlot::triggerReplot);

View File

@ -59,6 +59,7 @@ protected slots:
void newTraceAvailable(Trace *t);
void traceDeleted(Trace *t);
void triggerReplot();
void checkIfStillSupported(Trace *t);
virtual void markerAdded(TraceMarker *m);
virtual void markerRemoved(TraceMarker *m);
protected:

View File

@ -56,12 +56,12 @@ std::complex<double> TraceSmithChart::pixelToData(QPoint p)
QPoint TraceSmithChart::markerToPixel(TraceMarker *m)
{
QPoint ret = QPoint();
if(!m->isTimeDomain()) {
// if(!m->isTimeDomain()) {
if(m->getPosition() >= sweep_fmin && m->getPosition() <= sweep_fmax) {
auto d = m->getData();
ret = transform.map(QPoint(d.real() * smithCoordMax, -d.imag() * smithCoordMax));
}
}
// }
return ret;
}
@ -160,9 +160,9 @@ void TraceSmithChart::draw(QPainter &p) {
// only draw markers if the trace has at least one point
auto markers = t.first->getMarkers();
for(auto m : markers) {
if (m->isTimeDomain()) {
continue;
}
// if (m->isTimeDomain()) {
// continue;
// }
if (limitToSpan && (m->getPosition() < sweep_fmin || m->getPosition() > sweep_fmax)) {
continue;
}
@ -268,7 +268,7 @@ void TraceSmithChart::updateContextMenu()
bool TraceSmithChart::supported(Trace *t)
{
if(t->isReflection()) {
if(t->outputType() == Trace::DataType::Frequency && t->isReflection()) {
return true;
} else {
return false;

View File

@ -67,27 +67,14 @@ void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool
}
}
} while(erased);
auto oldType = YAxis[axis].type;
if(isTDRtype(YAxis[axis].type) && !isTDRtype(type)) {
// not TDR axis anymore
for(auto t : tracesAxis[axis]) {
t->removeTDRinterest();
}
}
YAxis[axis].type = type;
if(isTDRtype(type) && !isTDRtype(oldType)) {
// now a TDR axis
for(auto t : tracesAxis[axis]) {
t->addTDRinterest();
}
}
}
YAxis[axis].log = log;
YAxis[axis].autorange = autorange;
YAxis[axis].rangeMin = min;
YAxis[axis].rangeMax = max;
YAxis[axis].rangeDiv = div;
removeUnsupportedTraces();
updateAxisTicks();
updateContextMenu();
replot();
@ -100,15 +87,14 @@ void TraceXYPlot::setXAxis(XAxisType type, XAxisMode mode, double min, double ma
XAxis.rangeMin = min;
XAxis.rangeMax = max;
XAxis.rangeDiv = div;
removeUnsupportedTraces();
updateAxisTicks();
}
void TraceXYPlot::enableTrace(Trace *t, bool enabled)
{
for(int axis = 0;axis < 2;axis++) {
if(supported(t, YAxis[axis].type)) {
enableTraceAxis(t, axis, enabled);
}
enableTraceAxis(t, axis, enabled && supported(t, YAxis[axis].type));
}
}
@ -184,10 +170,10 @@ void TraceXYPlot::updateContextMenu()
});
}
bool TraceXYPlot::supported(Trace *)
bool TraceXYPlot::supported(Trace *t)
{
// potentially possible to add every kind of trace (depends on axis)
if(YAxis[0].type != YAxisType::Disabled || YAxis[1].type != YAxisType::Disabled) {
if(supported(t, YAxis[0].type) || supported(t, YAxis[1].type)) {
return true;
} else {
// no axis
@ -353,7 +339,7 @@ void TraceXYPlot::draw(QPainter &p)
pen.setStyle(Qt::SolidLine);
}
p.setPen(pen);
auto nPoints = numTraceSamples(t);
auto nPoints = t->size();
for(unsigned int j=1;j<nPoints;j++) {
auto last = traceToCoordinate(t, j-1, YAxis[i].type);
auto now = traceToCoordinate(t, j, YAxis[i].type);
@ -376,24 +362,25 @@ void TraceXYPlot::draw(QPainter &p)
// only draw markers on primary YAxis and if the trace has at least one point
auto markers = t->getMarkers();
for(auto m : markers) {
if(m->isTimeDomain() != (XAxis.type != XAxisType::Frequency)) {
// wrong domain, skip this marker
continue;
}
// if(m->isTimeDomain() != (XAxis.type != XAxisType::Frequency)) {
// // wrong domain, skip this marker
// continue;
// }
double xPosition;
if(m->isTimeDomain()) {
if(XAxis.type == XAxisType::Distance) {
xPosition = m->getTimeData().distance;
} else {
xPosition = m->getTimeData().time;
}
} else {
// if(m->isTimeDomain()) {
// if(XAxis.type == XAxisType::Distance) {
// xPosition = m->getTimeData().distance;
// } else {
// xPosition = m->getTimeData().time;
// }
// } else {
xPosition = m->getPosition();
}
// }
if (xPosition < XAxis.rangeMin || xPosition > XAxis.rangeMax) {
continue;
}
QPointF markerPoint = QPointF(xPosition, traceToCoordinate(m->getData(), YAxis[i].type));
auto t = m->getTrace();
QPointF markerPoint = traceToCoordinate(t, t->index(xPosition), YAxis[i].type);
auto point = plotValueToPixel(markerPoint, i);
if(!plotRect.contains(point)) {
// out of screen
@ -412,7 +399,8 @@ void TraceXYPlot::draw(QPainter &p)
p.setOpacity(0.5);
p.setBrush(Qt::white);
p.setPen(Qt::white);
if(YAxis[0].type == YAxisType::Disabled || YAxis[1].type == YAxisType::Disabled) {
if((YAxis[0].type == YAxisType::Disabled || !supported(dropTrace, YAxis[0].type))
|| (YAxis[1].type == YAxisType::Disabled || !supported(dropTrace, YAxis[1].type))) {
// only one axis enabled, show drop area over whole plot
p.drawRect(plotRect);
auto font = p.font();
@ -500,27 +488,13 @@ void TraceXYPlot::updateAxisTicks()
|| tracesAxis[1].find(t.first) != tracesAxis[1].end());
auto trace = t.first;
if(enabled && trace->isVisible()) {
if(!numTraceSamples(trace)) {
if(!trace->size()) {
// empty trace, do not use for automatic axis calculation
continue;
}
// this trace is currently displayed
double trace_min = std::numeric_limits<double>::max();
double trace_max = std::numeric_limits<double>::lowest();
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;
}
double trace_min = trace->minX();
double trace_max = trace->maxX();
if(trace_min < min) {
min = trace_min;
}
@ -546,7 +520,7 @@ void TraceXYPlot::updateAxisTicks()
double max = std::numeric_limits<double>::lowest();
double min = std::numeric_limits<double>::max();
for(auto t : tracesAxis[i]) {
unsigned int samples = numTraceSamples(t);
unsigned int samples = t->size();
for(unsigned int j=0;j<samples;j++) {
auto point = traceToCoordinate(t, j, YAxis[i].type);
@ -557,7 +531,8 @@ void TraceXYPlot::updateAxisTicks()
if(point.y() > max) {
max = point.y();
} else if(point.y() < min) {
}
if(point.y() < min) {
min = point.y();
}
}
@ -608,14 +583,7 @@ void TraceXYPlot::enableTraceAxis(Trace *t, int axis, bool enabled)
if(alreadyEnabled != enabled) {
if(enabled) {
tracesAxis[axis].insert(t);
// connect signals
if(isTDRtype(YAxis[axis].type)) {
t->addTDRinterest();
}
} else {
if(isTDRtype(YAxis[axis].type)) {
t->removeTDRinterest();
}
tracesAxis[axis].erase(t);
if(axis == 0) {
disconnect(t, &Trace::markerAdded, this, &TraceXYPlot::markerAdded);
@ -634,6 +602,22 @@ void TraceXYPlot::enableTraceAxis(Trace *t, int axis, bool enabled)
bool TraceXYPlot::supported(Trace *t, TraceXYPlot::YAxisType type)
{
switch(XAxis.type) {
case XAxisType::Frequency:
if(t->outputType() != Trace::DataType::Frequency) {
return false;
}
break;
case XAxisType::Distance:
case XAxisType::Time:
if(t->outputType() != Trace::DataType::Time) {
return false;
}
break;
default:
break;
}
switch(type) {
case YAxisType::Disabled:
return false;
@ -648,66 +632,45 @@ bool TraceXYPlot::supported(Trace *t, TraceXYPlot::YAxisType type)
return true;
}
double TraceXYPlot::traceToCoordinate(std::complex<double> data, TraceXYPlot::YAxisType type)
void TraceXYPlot::removeUnsupportedTraces()
{
switch(type) {
case YAxisType::Magnitude:
return 20*log10(abs(data));
case YAxisType::Phase:
return arg(data) * 180.0 / M_PI;
case YAxisType::VSWR:
if(abs(data) < 1.0) {
return (1+abs(data)) / (1-abs(data));
for(unsigned int i=0;i<2;i++) {
auto set_copy = tracesAxis[i];
for(auto t : set_copy) {
if(!supported(t, YAxis[i].type)) {
enableTraceAxis(t, i, false);
}
}
break;
case YAxisType::Step:
return data.real();
case YAxisType::Impulse:
return data.imag();
case YAxisType::Impedance:
if(abs(data.real()) < 1.0) {
return 50 * (1+data.real()) / (1-data.real());
}
default:
break;
}
return numeric_limits<double>::quiet_NaN();
}
QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlot::YAxisType type)
{
QPointF ret = QPointF(numeric_limits<double>::quiet_NaN(), numeric_limits<double>::quiet_NaN());
auto data = t->sample(sample);
ret.setX(data.x);
switch(type) {
case YAxisType::Magnitude:
ret.setY(20*log10(abs(data.y)));
break;
case YAxisType::Phase:
case YAxisType::VSWR: {
auto d = t->sample(sample);
ret.setY(traceToCoordinate(d.y, type));
ret.setX(d.x);
}
ret.setY(arg(data.y) * 180.0 / M_PI);
break;
case YAxisType::VSWR:
if(abs(data.y) < 1.0) {
ret.setY((1+abs(data.y)) / (1-abs(data.y)));
}
break;
case YAxisType::Impulse:
ret.setY(t->getTDR()[sample].impulseResponse);
if(XAxis.type == XAxisType::Distance) {
ret.setX(t->getTDR()[sample].distance);
} else {
ret.setX(t->getTDR()[sample].time);
}
ret.setY(real(data.y));
break;
case YAxisType::Step:
ret.setY(t->getTDR()[sample].stepResponse);
if(XAxis.type == XAxisType::Distance) {
ret.setX(t->getTDR()[sample].distance);
} else {
ret.setX(t->getTDR()[sample].time);
}
ret.setY(t->sample(sample, Trace::SampleType::TimeStep).y.real());
break;
case YAxisType::Impedance: {
ret.setY(t->getTDR()[sample].impedance);
if(XAxis.type == XAxisType::Distance) {
ret.setX(t->getTDR()[sample].distance);
} else {
ret.setX(t->getTDR()[sample].time);
double step = t->sample(sample, Trace::SampleType::TimeStep).y.real();
if(abs(step) < 1.0) {
ret.setY(50 * (1.0+step) / (1.0-step));
}
}
break;
@ -719,26 +682,17 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo
return ret;
}
unsigned int TraceXYPlot::numTraceSamples(Trace *t)
{
if(XAxis.type == XAxisType::Frequency) {
return t->size();
} else {
return t->getTDR().size();
}
}
QPoint TraceXYPlot::dataToPixel(Trace::Data d)
{
if(d.x < XAxis.rangeMin || d.x > XAxis.rangeMax) {
return QPoint();
}
auto y = traceToCoordinate(d.y, YAxis[0].type);
QPoint p;
p.setX(Util::Scale<double>(d.x, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth));
p.setY(Util::Scale<double>(y, YAxis[0].rangeMin, YAxis[0].rangeMax, plotAreaBottom, 0));
return p;
}
//QPoint TraceXYPlot::dataToPixel(Trace::Data d)
//{
// if(d.x < XAxis.rangeMin || d.x > XAxis.rangeMax) {
// return QPoint();
// }
// auto y = traceToCoordinate(d.y, YAxis[0].type);
// QPoint p;
// p.setX(Util::Scale<double>(d.x, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth));
// p.setY(Util::Scale<double>(y, YAxis[0].rangeMin, YAxis[0].rangeMax, plotAreaBottom, 0));
// return p;
//}
QPoint TraceXYPlot::plotValueToPixel(QPointF plotValue, int Yaxis)
{
@ -759,22 +713,12 @@ QPointF TraceXYPlot::pixelToPlotValue(QPoint pixel, int Yaxis)
QPoint TraceXYPlot::markerToPixel(TraceMarker *m)
{
QPoint ret = QPoint();
if(m->isTimeDomain() != (XAxis.type != XAxisType::Frequency)) {
// invalid domain
return ret;
}
QPointF plotPoint;
plotPoint.setY(traceToCoordinate(m->getData(), YAxis[0].type));
if(m->isTimeDomain()) {
auto timedata = m->getTimeData();
if(XAxis.type == XAxisType::Distance) {
plotPoint.setX(timedata.distance);
} else {
plotPoint.setX(timedata.time);
}
} else {
plotPoint.setX(m->getPosition());
}
// if(m->isTimeDomain() != (XAxis.type != XAxisType::Frequency)) {
// // invalid domain
// return ret;
// }
auto t = m->getTrace();
QPointF plotPoint = traceToCoordinate(t, t->index(m->getPosition()), YAxis[0].type);
return plotValueToPixel(plotPoint, 0);
}
@ -786,7 +730,7 @@ double TraceXYPlot::nearestTracePoint(Trace *t, QPoint pixel)
}
double closestDistance = numeric_limits<double>::max();
double closestXpos = 0;
auto samples = numTraceSamples(t);
auto samples = t->size();
for(unsigned int i=0;i<samples;i++) {
auto point = traceToCoordinate(t, i, YAxis[0].type);
if(isnan(point.x()) || isnan(point.y())) {

View File

@ -58,10 +58,9 @@ private:
QString AxisTypeToName(YAxisType type);
void enableTraceAxis(Trace *t, int axis, bool enabled);
bool supported(Trace *t, YAxisType type);
double traceToCoordinate(std::complex<double> data, YAxisType type);
void removeUnsupportedTraces();
QPointF traceToCoordinate(Trace *t, unsigned int sample, YAxisType type);
unsigned int numTraceSamples(Trace *t);
QPoint dataToPixel(Trace::Data d);
// QPoint dataToPixel(Trace::Data d);
QPoint plotValueToPixel(QPointF plotValue, int Yaxis);
QPointF pixelToPlotValue(QPoint pixel, int YAxis);
QPoint markerToPixel(TraceMarker *m) override;