794 lines
28 KiB
C++
794 lines
28 KiB
C++
#include "tracexyplot.h"
|
|
#include <QGridLayout>
|
|
#include "trace.h"
|
|
#include <cmath>
|
|
#include <QFrame>
|
|
#include "tracemarker.h"
|
|
#include "xyplotaxisdialog.h"
|
|
#include <preferences.h>
|
|
#include <QPainter>
|
|
#include "Util/util.h"
|
|
#include "unit.h"
|
|
#include <QDebug>
|
|
|
|
using namespace std;
|
|
|
|
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};
|
|
|
|
TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent)
|
|
: TracePlot(model, parent)
|
|
{
|
|
YAxis[0].log = false;
|
|
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].type = YAxisType::Disabled;
|
|
YAxis[1].rangeDiv = 1;
|
|
YAxis[1].rangeMax = 10;
|
|
YAxis[1].rangeMin = 0;
|
|
YAxis[1].autorange = false;
|
|
XAxis.type = XAxisType::Frequency;
|
|
XAxis.log = false;
|
|
XAxis.rangeDiv = 1;
|
|
XAxis.rangeMax = 10;
|
|
XAxis.rangeMin = 0;
|
|
XAxis.mode = XAxisMode::UseSpan;
|
|
|
|
// Setup default axis
|
|
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)
|
|
updateSpan(0, 6000000000);
|
|
setXAxis(XAxisType::Frequency, XAxisMode::UseSpan, 0, 6000000000, 600000000);
|
|
initializeTraceInfo();
|
|
}
|
|
|
|
void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool autorange, double min, double max, double div)
|
|
{
|
|
if(YAxis[axis].type != type) {
|
|
// remove traces that are active but not supported with the new axis type
|
|
bool erased = false;
|
|
do {
|
|
erased = false;
|
|
for(auto t : tracesAxis[axis]) {
|
|
if(!supported(t, type)) {
|
|
enableTraceAxis(t, axis, false);
|
|
erased = true;
|
|
break;
|
|
}
|
|
}
|
|
} 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;
|
|
updateAxisTicks();
|
|
updateContextMenu();
|
|
replot();
|
|
}
|
|
|
|
void TraceXYPlot::setXAxis(XAxisType type, XAxisMode mode, double min, double max, double div)
|
|
{
|
|
XAxis.type = type;
|
|
XAxis.mode = mode;
|
|
XAxis.rangeMin = min;
|
|
XAxis.rangeMax = max;
|
|
XAxis.rangeDiv = div;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TraceXYPlot::updateSpan(double min, double max)
|
|
{
|
|
TracePlot::updateSpan(min, max);
|
|
updateAxisTicks();
|
|
}
|
|
|
|
void TraceXYPlot::replot()
|
|
{
|
|
if(XAxis.mode != XAxisMode::Manual || YAxis[0].autorange || YAxis[1].autorange) {
|
|
updateAxisTicks();
|
|
}
|
|
TracePlot::replot();
|
|
}
|
|
|
|
bool TraceXYPlot::isTDRtype(TraceXYPlot::YAxisType type)
|
|
{
|
|
switch(type) {
|
|
case YAxisType::Impulse:
|
|
case YAxisType::Step:
|
|
case YAxisType::Impedance:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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, &TraceXYPlot::axisSetupDialog);
|
|
contextmenu->addAction(setup);
|
|
for(int axis = 0;axis < 2;axis++) {
|
|
if(YAxis[axis].type == YAxisType::Disabled) {
|
|
continue;
|
|
}
|
|
if(axis == 0) {
|
|
contextmenu->addSection("Primary Traces");
|
|
} else {
|
|
contextmenu->addSection("Secondary Traces");
|
|
}
|
|
for(auto t : traces) {
|
|
// Skip traces that are not applicable for the selected axis type
|
|
if(!supported(t.first, YAxis[axis].type)) {
|
|
continue;
|
|
}
|
|
|
|
auto action = new QAction(t.first->name(), contextmenu);
|
|
action->setCheckable(true);
|
|
if(tracesAxis[axis].find(t.first) != tracesAxis[axis].end()) {
|
|
action->setChecked(true);
|
|
}
|
|
connect(action, &QAction::toggled, [=](bool active) {
|
|
enableTraceAxis(t.first, axis, active);
|
|
});
|
|
contextmenu->addAction(action);
|
|
}
|
|
}
|
|
contextmenu->addSeparator();
|
|
auto close = new QAction("Close", contextmenu);
|
|
contextmenu->addAction(close);
|
|
connect(close, &QAction::triggered, [=]() {
|
|
markedForDeletion = true;
|
|
});
|
|
}
|
|
|
|
bool TraceXYPlot::supported(Trace *)
|
|
{
|
|
// potentially possible to add every kind of trace (depends on axis)
|
|
if(YAxis[0].type != YAxisType::Disabled || YAxis[1].type != YAxisType::Disabled) {
|
|
return true;
|
|
} else {
|
|
// no axis
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void TraceXYPlot::draw(QPainter &p)
|
|
{
|
|
auto pref = Preferences::getInstance();
|
|
|
|
constexpr int yAxisSpace = 50;
|
|
constexpr int yAxisDisabledSpace = 10;
|
|
constexpr int xAxisSpace = 30;
|
|
auto w = p.window();
|
|
auto pen = QPen(pref.General.graphColors.axis, 0);
|
|
pen.setCosmetic(true);
|
|
p.setPen(pen);
|
|
plotAreaLeft = YAxis[0].type == YAxisType::Disabled ? yAxisDisabledSpace : yAxisSpace;
|
|
plotAreaWidth = w.width();
|
|
plotAreaBottom = w.height() - xAxisSpace;
|
|
if(YAxis[0].type != YAxisType::Disabled) {
|
|
plotAreaWidth -= yAxisSpace;
|
|
} else {
|
|
plotAreaWidth -= yAxisDisabledSpace;
|
|
}
|
|
if(YAxis[1].type != YAxisType::Disabled) {
|
|
plotAreaWidth -= yAxisSpace;
|
|
} else {
|
|
plotAreaWidth -= yAxisDisabledSpace;
|
|
}
|
|
|
|
auto plotRect = QRect(plotAreaLeft, 0, plotAreaWidth + 1, plotAreaBottom);
|
|
p.drawRect(plotRect);
|
|
|
|
// draw axis types
|
|
QString labelX;
|
|
switch(XAxis.type) {
|
|
case XAxisType::Frequency: labelX = "Frequency"; break;
|
|
case XAxisType::Time: labelX = "Time"; break;
|
|
case XAxisType::Distance: labelX = "Distance"; break;
|
|
}
|
|
auto font = p.font();
|
|
font.setPixelSize(AxisLabelSize);
|
|
p.setFont(font);
|
|
p.drawText(QRect(0, w.height()-AxisLabelSize*1.5, w.width(), AxisLabelSize*1.5), Qt::AlignHCenter, labelX);
|
|
// draw X ticks
|
|
// this only works for evenly distributed ticks:
|
|
auto max = qMax(abs(XAxis.ticks.front()), abs(XAxis.ticks.back()));
|
|
auto step = abs(XAxis.ticks[0] - XAxis.ticks[1]);
|
|
int significantDigits = floor(log10(max)) - floor(log10(step)) + 1;
|
|
bool displayFullFreq = significantDigits <= 5;
|
|
constexpr int displayLastDigits = 4;
|
|
QString prefixes = "fpnum kMG";
|
|
QString commonPrefix = QString();
|
|
if(!displayFullFreq) {
|
|
auto fullFreq = Unit::ToString(XAxis.ticks.front(), "", prefixes, significantDigits);
|
|
commonPrefix = fullFreq.at(fullFreq.size() - 1);
|
|
auto front = fullFreq;
|
|
front.truncate(fullFreq.size() - displayLastDigits);
|
|
auto back = fullFreq;
|
|
back.remove(0, front.size());
|
|
back.append("..");
|
|
p.setPen(QPen(QColor("orange")));
|
|
QRect bounding;
|
|
p.drawText(QRect(2, plotAreaBottom + AxisLabelSize + 5, w.width(), AxisLabelSize), 0, front, &bounding);
|
|
p.setPen(pref.General.graphColors.axis);
|
|
p.drawText(QRect(bounding.x() + bounding.width(), plotAreaBottom + AxisLabelSize + 5, w.width(), AxisLabelSize), 0, back);
|
|
}
|
|
|
|
for(auto t : XAxis.ticks) {
|
|
auto xCoord = Util::Scale<double>(t, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth);
|
|
auto tickValue = Unit::ToString(t, "", prefixes, significantDigits);
|
|
p.setPen(QPen(pref.General.graphColors.axis, 1));
|
|
if(displayFullFreq) {
|
|
p.drawText(QRect(xCoord - 40, plotAreaBottom + 5, 80, AxisLabelSize), Qt::AlignHCenter, tickValue);
|
|
} else {
|
|
// check if the same prefix was used as in the fullFreq string
|
|
if(tickValue.at(tickValue.size() - 1) != commonPrefix) {
|
|
// prefix changed, we reached the next order of magnitude. Force same prefix as in fullFreq and add extra digit
|
|
tickValue = Unit::ToString(t, "", commonPrefix, significantDigits + 1);
|
|
}
|
|
|
|
tickValue.remove(0, tickValue.size() - displayLastDigits);
|
|
QRect bounding;
|
|
p.drawText(QRect(xCoord - 40, plotAreaBottom + 5, 80, AxisLabelSize), Qt::AlignHCenter, tickValue, &bounding);
|
|
p.setPen(QPen(QColor("orange")));
|
|
p.drawText(QRect(0, plotAreaBottom + 5, bounding.x() - 1, AxisLabelSize), Qt::AlignRight, "..");
|
|
p.setPen(QPen(pref.General.graphColors.axis, 1));
|
|
}
|
|
p.drawLine(xCoord, plotAreaBottom, xCoord, plotAreaBottom + 2);
|
|
if(xCoord != plotAreaLeft && xCoord != plotAreaLeft + plotAreaWidth) {
|
|
p.setPen(QPen(pref.General.graphColors.divisions, 0.5, Qt::DashLine));
|
|
p.drawLine(xCoord, 0, xCoord, plotAreaBottom);
|
|
}
|
|
}
|
|
|
|
for(int i=0;i<2;i++) {
|
|
if(YAxis[i].type == YAxisType::Disabled) {
|
|
continue;
|
|
}
|
|
QString labelY;
|
|
p.setPen(QPen(pref.General.graphColors.axis, 1));
|
|
switch(YAxis[i].type) {
|
|
case YAxisType::Magnitude: labelY = "Magnitude"; break;
|
|
case YAxisType::Phase: labelY = "Phase"; break;
|
|
case YAxisType::VSWR: labelY = "VSWR"; break;
|
|
case YAxisType::Impulse: labelY = "Impulse Response"; break;
|
|
case YAxisType::Step: labelY = "Step Response"; break;
|
|
case YAxisType::Impedance: labelY = "Impedance"; break;
|
|
}
|
|
auto xStart = i == 0 ? 0 : w.width() - AxisLabelSize * 1.5;
|
|
p.save();
|
|
p.translate(xStart, w.height()-xAxisSpace);
|
|
p.rotate(-90);
|
|
p.drawText(QRect(0, 0, w.height()-xAxisSpace, AxisLabelSize*1.5), Qt::AlignHCenter, labelY);
|
|
p.restore();
|
|
// draw ticks
|
|
if(YAxis[0].type != YAxisType::Disabled) {
|
|
// this only works for evenly distributed ticks:
|
|
auto max = qMax(abs(YAxis[i].ticks.front()), abs(YAxis[i].ticks.back()));
|
|
auto step = abs(YAxis[i].ticks[0] - YAxis[i].ticks[1]);
|
|
int significantDigits = floor(log10(max)) - floor(log10(step)) + 1;
|
|
for(auto t : YAxis[i].ticks) {
|
|
auto yCoord = Util::Scale<double>(t, YAxis[i].rangeMax, YAxis[i].rangeMin, 0, w.height() - xAxisSpace);
|
|
p.setPen(QPen(pref.General.graphColors.axis, 1));
|
|
// draw tickmark on axis
|
|
auto tickStart = i == 0 ? plotAreaLeft : plotAreaLeft + plotAreaWidth;
|
|
auto tickLen = i == 0 ? -2 : 2;
|
|
p.drawLine(tickStart, yCoord, tickStart + tickLen, yCoord);
|
|
auto tickValue = Unit::ToString(t, "", "fpnum kMG", significantDigits);
|
|
if(i == 0) {
|
|
p.drawText(QRectF(0, yCoord - AxisLabelSize/2 - 2, tickStart + 2 * tickLen, AxisLabelSize), Qt::AlignRight, tickValue);
|
|
} else {
|
|
p.drawText(QRectF(tickStart + 2 * tickLen + 2, yCoord - AxisLabelSize/2 - 2, yAxisSpace, AxisLabelSize), Qt::AlignLeft, tickValue);
|
|
}
|
|
|
|
// tick lines
|
|
if(yCoord == 0 || yCoord == w.height() - xAxisSpace) {
|
|
// skip tick lines right on the plot borders
|
|
continue;
|
|
}
|
|
if(i == 0) {
|
|
// only draw tick lines for primary axis
|
|
p.setPen(QPen(pref.General.graphColors.divisions, 0.5, Qt::DashLine));
|
|
p.drawLine(plotAreaLeft, yCoord, plotAreaLeft + plotAreaWidth, yCoord);
|
|
}
|
|
}
|
|
}
|
|
|
|
// plot traces
|
|
p.setClipRect(QRect(plotRect.x()+1, plotRect.y()+1, plotRect.width()-2, plotRect.height()-2));
|
|
for(auto t : tracesAxis[i]) {
|
|
if(!t->isVisible()) {
|
|
continue;
|
|
}
|
|
pen = QPen(t->color(), 1);
|
|
pen.setCosmetic(true);
|
|
if(i == 1) {
|
|
pen.setStyle(Qt::DotLine);
|
|
} else {
|
|
pen.setStyle(Qt::SolidLine);
|
|
}
|
|
p.setPen(pen);
|
|
auto nPoints = numTraceSamples(t);
|
|
for(unsigned int j=1;j<nPoints;j++) {
|
|
auto last = transformY(t, j-1, YAxis[i].type);
|
|
auto now = transformY(t, j, YAxis[i].type);
|
|
|
|
if(isnan(last.y()) || isnan(now.y()) || isinf(last.y()) || isinf(now.y())) {
|
|
continue;
|
|
}
|
|
|
|
// scale to plot coordinates
|
|
auto p1 = plotValueToPixel(last, i);
|
|
auto p2 = plotValueToPixel(now, i);
|
|
if(!plotRect.contains(p1) && !plotRect.contains(p2)) {
|
|
// completely out of frame
|
|
continue;
|
|
}
|
|
// draw line
|
|
p.drawLine(p1, p2);
|
|
}
|
|
if(i == 0 && nPoints > 0) {
|
|
// 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;
|
|
}
|
|
double xPosition;
|
|
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, transformY(m->getData(), YAxis[i].type));
|
|
auto point = plotValueToPixel(markerPoint, i);
|
|
if(!plotRect.contains(point)) {
|
|
// out of screen
|
|
continue;
|
|
}
|
|
auto symbol = m->getSymbol();
|
|
point += QPoint(-symbol.width()/2, -symbol.height());
|
|
p.drawPixmap(point, symbol);
|
|
}
|
|
}
|
|
}
|
|
p.setClipping(false);
|
|
}
|
|
|
|
if(dropPending) {
|
|
p.setOpacity(0.5);
|
|
p.setBrush(Qt::white);
|
|
p.setPen(Qt::white);
|
|
if(YAxis[0].type == YAxisType::Disabled || YAxis[1].type == YAxisType::Disabled) {
|
|
// only one axis enabled, show drop area over whole plot
|
|
p.drawRect(plotRect);
|
|
auto font = p.font();
|
|
font.setPixelSize(20);
|
|
p.setFont(font);
|
|
p.setOpacity(1.0);
|
|
p.setPen(Qt::white);
|
|
auto text = "Drop here to add\n" + dropTrace->name() + "\nto XY-plot";
|
|
p.drawText(plotRect, Qt::AlignCenter, text);
|
|
} else {
|
|
// both axis enabled, show regions
|
|
auto leftRect = plotRect;
|
|
leftRect.setWidth(plotRect.width() * 0.3);
|
|
auto centerRect = plotRect;
|
|
centerRect.setX(centerRect.x() + plotRect.width() * 0.35);
|
|
centerRect.setWidth(plotRect.width() * 0.3);
|
|
auto rightRect = plotRect;
|
|
rightRect.setX(rightRect.x() + plotRect.width() * 0.7);
|
|
rightRect.setWidth(plotRect.width() * 0.3);
|
|
p.drawRect(leftRect);
|
|
p.drawRect(centerRect);
|
|
p.drawRect(rightRect);
|
|
p.setOpacity(1.0);
|
|
p.setPen(Qt::white);
|
|
auto font = p.font();
|
|
font.setPixelSize(20);
|
|
p.setFont(font);
|
|
p.drawText(leftRect, Qt::AlignCenter, "Drop here to add\nto primary axis");
|
|
p.drawText(centerRect, Qt::AlignCenter, "Drop here to add\nto boths axes");
|
|
p.drawText(rightRect, Qt::AlignCenter, "Drop here to add\nto secondary axis");
|
|
}
|
|
}
|
|
}
|
|
|
|
void TraceXYPlot::updateAxisTicks()
|
|
{
|
|
auto createEvenlySpacedTicks = [](vector<double>& ticks, double start, double stop, double step) {
|
|
ticks.clear();
|
|
for(double tick = start; tick - stop < numeric_limits<double>::epsilon() ;tick+= step) {
|
|
ticks.push_back(tick);
|
|
}
|
|
};
|
|
|
|
auto createAutomaticTicks = [](vector<double>& ticks, double start, double stop, int minDivisions) -> double {
|
|
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;
|
|
};
|
|
|
|
if(XAxis.mode == XAxisMode::Manual) {
|
|
createEvenlySpacedTicks(XAxis.ticks, XAxis.rangeMin, XAxis.rangeMax, XAxis.rangeDiv);
|
|
} else {
|
|
XAxis.ticks.clear();
|
|
// 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 = 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;
|
|
}
|
|
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;
|
|
}
|
|
XAxis.rangeMin = min;
|
|
XAxis.rangeMax = max;
|
|
XAxis.rangeDiv = createAutomaticTicks(XAxis.ticks, min, max, 8);
|
|
}
|
|
|
|
for(int i=0;i<2;i++) {
|
|
if(!YAxis[i].autorange) {
|
|
createEvenlySpacedTicks(YAxis[i].ticks, YAxis[i].rangeMin, YAxis[i].rangeMax, YAxis[i].rangeDiv);
|
|
} else {
|
|
// automatic mode, figure out limits
|
|
double max = std::numeric_limits<double>::lowest();
|
|
double min = std::numeric_limits<double>::max();
|
|
for(auto t : tracesAxis[i]) {
|
|
unsigned int samples = numTraceSamples(t);
|
|
for(unsigned int j=0;j<samples;j++) {
|
|
auto point = transformY(t, j, YAxis[i].type);
|
|
|
|
if(point.y() > max) {
|
|
max = point.y();
|
|
} else if(point.y() < min) {
|
|
min = point.y();
|
|
}
|
|
}
|
|
}
|
|
// add 5% overrange
|
|
auto range = max - min;
|
|
min -= range * 0.05;
|
|
max += range * 0.05;
|
|
YAxis[i].rangeMin = min;
|
|
YAxis[i].rangeMax = max;
|
|
YAxis[i].rangeDiv = createAutomaticTicks(YAxis[i].ticks, min, max, 8);
|
|
}
|
|
}
|
|
triggerReplot();
|
|
}
|
|
|
|
QString TraceXYPlot::AxisTypeToName(TraceXYPlot::YAxisType type)
|
|
{
|
|
switch(type) {
|
|
case YAxisType::Disabled: return "Disabled"; break;
|
|
case YAxisType::Magnitude: return "Magnitude"; break;
|
|
case YAxisType::Phase: return "Phase"; break;
|
|
case YAxisType::VSWR: return "VSWR"; break;
|
|
default: return "Unknown"; break;
|
|
}
|
|
}
|
|
|
|
void TraceXYPlot::enableTraceAxis(Trace *t, int axis, bool enabled)
|
|
{
|
|
if(axis == 0) {
|
|
TracePlot::enableTrace(t, enabled);
|
|
}
|
|
bool alreadyEnabled = tracesAxis[axis].find(t) != tracesAxis[axis].end();
|
|
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);
|
|
disconnect(t, &Trace::markerRemoved, this, &TraceXYPlot::markerRemoved);
|
|
auto tracemarkers = t->getMarkers();
|
|
for(auto m : tracemarkers) {
|
|
markerRemoved(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
updateContextMenu();
|
|
replot();
|
|
}
|
|
}
|
|
|
|
bool TraceXYPlot::supported(Trace *t, TraceXYPlot::YAxisType type)
|
|
{
|
|
switch(type) {
|
|
case YAxisType::Disabled:
|
|
return false;
|
|
case YAxisType::VSWR:
|
|
if(!t->isReflection()) {
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
double TraceXYPlot::transformY(std::complex<double> data, TraceXYPlot::YAxisType type)
|
|
{
|
|
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));
|
|
}
|
|
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::transformY(Trace *t, unsigned int sample, TraceXYPlot::YAxisType type)
|
|
{
|
|
QPointF ret = QPointF(numeric_limits<double>::quiet_NaN(), numeric_limits<double>::quiet_NaN());
|
|
switch(type) {
|
|
case YAxisType::Magnitude:
|
|
case YAxisType::Phase:
|
|
case YAxisType::VSWR: {
|
|
auto d = t->sample(sample);
|
|
ret.setY(transformY(d.S, type));
|
|
ret.setX(d.frequency);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
break;
|
|
case YAxisType::Disabled:
|
|
case YAxisType::Last:
|
|
// no valid axis
|
|
break;
|
|
}
|
|
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.frequency < XAxis.rangeMin || d.frequency > XAxis.rangeMax) {
|
|
return QPoint();
|
|
}
|
|
auto y = transformY(d.S, YAxis[0].type);
|
|
QPoint p;
|
|
p.setX(Util::Scale<double>(d.frequency, 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)
|
|
{
|
|
QPoint p;
|
|
p.setX(Util::Scale<double>(plotValue.x(), XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth));
|
|
p.setY(Util::Scale<double>(plotValue.y(), YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax, plotAreaBottom, 0));
|
|
return p;
|
|
}
|
|
|
|
QPoint TraceXYPlot::markerToPixel(TraceMarker *m)
|
|
{
|
|
QPoint ret = QPoint();
|
|
if(m->isTimeDomain() != (XAxis.type != XAxisType::Frequency)) {
|
|
// invalid domain
|
|
return ret;
|
|
}
|
|
QPointF plotPoint;
|
|
plotPoint.setY(transformY(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());
|
|
}
|
|
return plotValueToPixel(plotPoint, 0);
|
|
}
|
|
|
|
double TraceXYPlot::nearestTracePoint(Trace *t, QPoint pixel)
|
|
{
|
|
if(!tracesAxis[0].count(t)) {
|
|
// trace not enabled
|
|
return 0;
|
|
}
|
|
double closestDistance = numeric_limits<double>::max();
|
|
double closestXpos = 0;
|
|
auto samples = numTraceSamples(t);
|
|
for(unsigned int i=0;i<samples;i++) {
|
|
auto point = transformY(t, i, YAxis[0].type);
|
|
if(isnan(point.x()) || isnan(point.y())) {
|
|
continue;
|
|
}
|
|
auto plotPoint = plotValueToPixel(point, 0);
|
|
auto diff = plotPoint - pixel;
|
|
unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y();
|
|
if(distance < closestDistance) {
|
|
closestDistance = distance;
|
|
closestXpos = point.x();
|
|
}
|
|
}
|
|
return closestXpos;
|
|
}
|
|
|
|
void TraceXYPlot::traceDropped(Trace *t, QPoint position)
|
|
{
|
|
if(YAxis[0].type == YAxisType::Disabled && YAxis[1].type == YAxisType::Disabled) {
|
|
// no Y axis enabled, unable to drop
|
|
return;
|
|
}
|
|
if(YAxis[0].type == YAxisType::Disabled) {
|
|
// only axis 1 enabled
|
|
enableTraceAxis(t, 1, true);
|
|
return;
|
|
}
|
|
if(YAxis[1].type == YAxisType::Disabled) {
|
|
// only axis 0 enabled
|
|
enableTraceAxis(t, 0, true);
|
|
return;
|
|
}
|
|
// both axis enabled, check drop position
|
|
auto drop = Util::Scale<double>(position.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth, 0.0, 1.0);
|
|
if(drop < 0.66) {
|
|
enableTraceAxis(t, 0, true);
|
|
}
|
|
if(drop > 0.33) {
|
|
enableTraceAxis(t, 1, true);
|
|
}
|
|
}
|