#include "traceaxis.h" #include "Util/util.h" #include using namespace std; static void createEvenlySpacedTicks(vector& ticks, double start, double stop, double step) { ticks.clear(); if(start > stop) { swap(start, stop); } step = abs(step); constexpr unsigned int maxTicks = 100; for(double tick = start; tick - stop < numeric_limits::epsilon() && ticks.size() <= maxTicks;tick+= step) { ticks.push_back(tick); } } static double createAutomaticTicks(vector& ticks, double start, double stop, int minDivisions) { Q_ASSERT(stop > start); ticks.clear(); double max_div_step = (stop - start) / 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 auto start_div = ceil(start / div_step) * div_step; for(double tick = start_div;tick <= stop;tick += div_step) { ticks.push_back(tick); } return div_step; } static void createLogarithmicTicks(vector& ticks, double start, double stop, int minDivisions) { // enforce usable log settings if(start <= 0) { start = 1.0; } if(stop <= start) { stop = start + 1.0; } ticks.clear(); auto decades = log10(stop) - log10(start); double max_div_decade = minDivisions / decades; int zeros = floor(log10(max_div_decade)); double decimals_shift = pow(10, zeros); max_div_decade /= decimals_shift; if(max_div_decade < 2) { max_div_decade = 2; } else if(max_div_decade < 5) { max_div_decade = 5; } else { max_div_decade = 10; } auto step = pow(10, floor(log10(start))+1) / (max_div_decade * decimals_shift); // round min up to next multiple of div_step auto div = ceil(start / step) * step; if(floor(log10(div)) != floor(log10(start))) { // first div is already at the next decade step *= 10; } do { ticks.push_back(div); if(ticks.size() > 1 && div != step && floor(log10(div)) != floor(log10(div - step))) { // reached a new decade with this switch step *= 10; div = step; } else { div += step; } } while(div <= stop); } YAxis::YAxis() { type = Type::Magnitude; } double YAxis::sampleToCoordinate(Trace::Data data, Trace *t, unsigned int sample) { switch(type) { case YAxis::Type::Magnitude: return Util::SparamTodB(data.y); case YAxis::Type::MagnitudedBuV: return Util::dBmTodBuV(Util::SparamTodB(data.y)); case YAxis::Type::MagnitudeLinear: return abs(data.y); case YAxis::Type::Phase: return Util::SparamToDegree(data.y); case YAxis::Type::UnwrappedPhase: if(!t) { return 0.0; } return t->getUnwrappedPhase(sample) * 180.0 / M_PI; case YAxis::Type::VSWR: return Util::SparamToVSWR(data.y); case YAxis::Type::Real: return data.y.real(); case YAxis::Type::Imaginary: return data.y.imag(); case YAxis::Type::SeriesR: return Util::SparamToResistance(data.y, t->getReferenceImpedance()); case YAxis::Type::Reactance: return Util::SparamToImpedance(data.y, t->getReferenceImpedance()).imag(); case YAxis::Type::Capacitance: return Util::SparamToCapacitance(data.y, data.x, t->getReferenceImpedance()); case YAxis::Type::Inductance: return Util::SparamToInductance(data.y, data.x); case YAxis::Type::QualityFactor: return Util::SparamToQualityFactor(data.y); case YAxis::Type::GroupDelay: { constexpr int requiredSamples = 5; if(!t || t->size() < requiredSamples) { // unable to calculate return 0.0; } // needs at least some samples before/after current sample for calculating the derivative. // For samples too far at either end of the trace, return group delay of "inner" trace sample instead if(sample < requiredSamples / 2) { return sampleToCoordinate(data, t, requiredSamples / 2); } else if(sample >= t->size() - requiredSamples / 2) { return sampleToCoordinate(data, t, t->size() - requiredSamples / 2 - 1); } else { // got enough samples at either end to calculate derivative. // acquire phases of the required samples std::vector phases; phases.reserve(requiredSamples); for(unsigned int index = sample - requiredSamples / 2;index <= sample + requiredSamples / 2;index++) { phases.push_back(arg(t->sample(index).y)); } // make sure there are no phase jumps Util::unwrapPhase(phases); // calculate linearRegression to get derivative double B_0, B_1; Util::linearRegression(phases, B_0, B_1); // B_1 now contains the derived phase vs. the sample. Scale by frequency to get group delay double freq_step = t->sample(sample).x - t->sample(sample - 1).x; return -B_1 / (2.0*M_PI * freq_step); } } case YAxis::Type::ImpulseReal: return real(data.y); case YAxis::Type::ImpulseMag: return Util::SparamTodB(data.y); case YAxis::Type::Step: if(!t) { return 0.0; } return t->sample(sample, true).y.real(); case YAxis::Type::Impedance: { if(!t) { return 0.0; } double step = t->sample(sample, true).y.real(); if(abs(step) < 1.0) { return Util::SparamToImpedance(step, t->getReferenceImpedance()).real(); } } break; case YAxis::Type::Disabled: case YAxis::Type::Last: // no valid axis break; } return 0.0; } void YAxis::set(Type type, bool log, bool autorange, double min, double max, double div) { this->type = type; this->log = log; this->autorange = autorange; this->rangeMin = min; this->rangeMax = max; this->rangeDiv = div; if(type != Type::Disabled) { updateTicks(); } } QString YAxis::TypeToName(Type type) { switch(type) { case Type::Disabled: return "Disabled"; case Type::Magnitude: return "Magnitude"; case Type::MagnitudedBuV: return "Magnitude (dBuV)"; case Type::MagnitudeLinear: return "Magnitude (linear)"; case Type::Phase: return "Phase"; case Type::UnwrappedPhase: return "Unwrapped Phase"; case Type::VSWR: return "VSWR"; case Type::Real: return "Real"; case Type::Imaginary: return "Imaginary"; case Type::SeriesR: return "Resistance"; case Type::Reactance: return "Reactance"; case Type::Capacitance: return "Capacitance"; case Type::Inductance: return "Inductance"; case Type::QualityFactor: return "Quality Factor"; case Type::GroupDelay: return "Group delay"; case Type::ImpulseReal: return "Impulse Response (Real)"; case Type::ImpulseMag: return "Impulse Response (Magnitude)"; case Type::Step: return "Step Response"; case Type::Impedance: return "Impedance"; case Type::Last: return "Unknown"; } return "Missing case"; } YAxis::Type YAxis::TypeFromName(QString name) { for(unsigned int i=0;i<(int) Type::Last;i++) { if(TypeToName((Type) i) == name) { return (Type) i; } } // not found, use default return Type::Magnitude; } QString YAxis::Unit(Type type, TraceModel::DataSource source) { if(source == TraceModel::DataSource::VNA) { switch(type) { case Type::Magnitude: return "dB"; case Type::MagnitudeLinear: return ""; case Type::Phase: return "°"; case Type::UnwrappedPhase: return "°"; case Type::VSWR: return ""; case Type::ImpulseReal: return ""; case Type::ImpulseMag: return "dB"; case Type::Step: return ""; case Type::Impedance: return "Ω"; case Type::GroupDelay: return "s"; case Type::Disabled: case Type::Real: case Type::Imaginary: case Type::QualityFactor: return ""; case Type::SeriesR: return "Ω"; case Type::Reactance: return "Ω"; case Type::Capacitance: return "F"; case Type::Inductance: return "H"; default: return ""; } } else if(source == TraceModel::DataSource::SA) { switch(type) { case Type::Magnitude: return "dBm"; case Type::MagnitudedBuV: return "dBuV"; default: return ""; } } return ""; } QString YAxis::Prefixes(Type type, TraceModel::DataSource source) { if(source == TraceModel::DataSource::VNA) { switch(type) { case Type::Magnitude: return " "; case Type::MagnitudeLinear: return "num "; case Type::Phase: return " "; case Type::UnwrappedPhase: return " "; case Type::VSWR: return " "; case Type::ImpulseReal: return "pnum kMG"; case Type::ImpulseMag: return " "; case Type::Step: return "pnum kMG"; case Type::Impedance: return "m kM"; case Type::GroupDelay: return "pnum "; case Type::Disabled: return " "; case Type::Real: return "pnum "; case Type::Imaginary: return "pnum "; case Type::QualityFactor: return " "; case Type::SeriesR: return "m kM"; case Type::Reactance: return "m kM"; case Type::Capacitance: return "pnum "; case Type::Inductance: return "pnum "; default: return " "; } } else if(source == TraceModel::DataSource::SA) { switch(type) { case Type::Magnitude: return " "; case Type::MagnitudedBuV: return " "; default: return " "; } } return " "; } QString YAxis::TypeToName() { return TypeToName(type); } QString YAxis::Unit(TraceModel::DataSource source) { return Unit(type, source); } QString YAxis::Prefixes(TraceModel::DataSource source) { return Prefixes(type, source); } YAxis::Type YAxis::getType() const { return type; } std::set YAxis::getSupported(XAxis::Type type, TraceModel::DataSource source) { std::set ret = {YAxis::Type::Disabled}; if(source == TraceModel::DataSource::VNA) { switch(type) { case XAxis::Type::Frequency: case XAxis::Type::Power: case XAxis::Type::TimeZeroSpan: ret.insert(YAxis::Type::Magnitude); ret.insert(YAxis::Type::MagnitudeLinear); ret.insert(YAxis::Type::Phase); ret.insert(YAxis::Type::UnwrappedPhase); ret.insert(YAxis::Type::VSWR); ret.insert(YAxis::Type::Real); ret.insert(YAxis::Type::Imaginary); ret.insert(YAxis::Type::SeriesR); ret.insert(YAxis::Type::Reactance); ret.insert(YAxis::Type::Capacitance); ret.insert(YAxis::Type::Inductance); ret.insert(YAxis::Type::QualityFactor); ret.insert(YAxis::Type::GroupDelay); break; case XAxis::Type::Time: case XAxis::Type::Distance: ret.insert(YAxis::Type::ImpulseReal); ret.insert(YAxis::Type::ImpulseMag); ret.insert(YAxis::Type::Step); ret.insert(YAxis::Type::Impedance); break; default: break; } } else if(source == TraceModel::DataSource::SA) { switch(type) { case XAxis::Type::Frequency: ret.insert(YAxis::Type::Magnitude); ret.insert(YAxis::Type::MagnitudedBuV); break; default: break; } } return ret; } std::complex YAxis::reconstructValueFromYAxisType(std::map yaxistypes) { std::complex ret = std::numeric_limits>::quiet_NaN(); if(yaxistypes.count(Type::Real)) { ret.real(yaxistypes[Type::Real]); if(yaxistypes.count(Type::Imaginary)) { ret.imag(yaxistypes[Type::Imaginary]); } else { ret.imag(0.0); } } else if(yaxistypes.count(Type::Magnitude) || yaxistypes.count(Type::MagnitudedBuV) || yaxistypes.count(Type::MagnitudeLinear)) { double maglin, phase; if(yaxistypes.count(Type::MagnitudeLinear)) { maglin = yaxistypes[Type::MagnitudeLinear]; } else if(yaxistypes.count(Type::Magnitude)) { maglin = Util::dBToMagnitude(yaxistypes[Type::Magnitude]); } else { auto dBm = Util::dBuVTodBm(yaxistypes[Type::MagnitudedBuV]); maglin = Util::dBToMagnitude(dBm); } if(yaxistypes.count(Type::Phase)) { phase = yaxistypes[Type::Phase]; } else { phase = 0.0; } ret = polar(maglin, phase / 180.0 * M_PI); } return ret; } bool XAxis::isSupported(XAxis::Type type, TraceModel::DataSource source) { if(source == TraceModel::DataSource::VNA) { // all X axis types are supported return true; } else if(source == TraceModel::DataSource::SA) { if (type == XAxis::Type::Frequency) { return true; } else { return false; } } return false; } void Axis::updateTicks() { if(log) { createLogarithmicTicks(ticks, rangeMin, rangeMax, 20); } else if(autorange) { if(rangeMin >= rangeMax) { // problem, min must be less than max rangeMin -= 1.0; rangeMax += 1.0; } rangeDiv = createAutomaticTicks(ticks, rangeMin, rangeMax, 8); } else { createEvenlySpacedTicks(ticks, rangeMin, rangeMax, rangeDiv); } } const std::vector &Axis::getTicks() const { return ticks; } double Axis::getRangeDiv() const { return rangeDiv; } double Axis::getRangeMax() const { return rangeMax; } double Axis::getRangeMin() const { return rangeMin; } bool Axis::getAutorange() const { return autorange; } bool Axis::getLog() const { return log; } XAxis::XAxis() { type = Type::Frequency; } double XAxis::sampleToCoordinate(Trace::Data data, Trace *t, unsigned int sample) { Q_UNUSED(sample) switch(type) { case Type::Distance: if(!t) { return 0.0; } return t->timeToDistance(data.x); default: return data.x; } } void XAxis::set(Type type, bool log, bool autorange, double min, double max, double div) { this->type = type; this->log = log; this->autorange = autorange; this->rangeMin = min; this->rangeMax = max; this->rangeDiv = div; updateTicks(); } QString XAxis::TypeToName(Type type) { switch(type) { case Type::Frequency: return "Frequency"; case Type::Time: return "Time"; case Type::Distance: return "Distance"; case Type::Power: return "Power"; case Type::TimeZeroSpan: return "Time (Zero Span)"; default: return "Unknown"; } } XAxis::Type XAxis::TypeFromName(QString name) { for(unsigned int i=0;i<(int) Type::Last;i++) { if(TypeToName((Type) i) == name) { return (Type) i; } } // not found, use default return Type::Frequency; } QString XAxis::Unit(Type type) { switch(type) { case Type::Frequency: return "Hz"; case Type::Time: return "s"; case Type::Distance: return "m"; case Type::Power: return "dBm"; case Type::TimeZeroSpan: return "s"; default: return ""; } } QString XAxis::TypeToName() { return TypeToName(type); } QString XAxis::Unit() { return Unit(type); } XAxis::Type XAxis::getType() const { return type; } Axis::Axis() { log = false; autorange = true; rangeMin = -1.0; rangeMax = 1.0; rangeDiv = 1.0; ticks.clear(); } double Axis::transform(double value, double to_low, double to_high) { return Util::Scale(value, rangeMin, rangeMax, to_low, to_high, log); } double Axis::inverseTransform(double value, double to_low, double to_high) { return Util::Scale(value, to_low, to_high, rangeMin, rangeMax, false, log); }