#include "tracexyplot.h" #include #include "trace.h" #include #include #include "tracemarker.h" #include "xyplotaxisdialog.h" #include #include #include "Util/util.h" #include "unit.h" #include using namespace std; const set 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(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; default: 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(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 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, traceToCoordinate(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& 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); } }; auto createAutomaticTicks = [](vector& ticks, double start, double stop, int minDivisions) -> double { 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; }; 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::lowest(); double min = std::numeric_limits::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()) { if(!numTraceSamples(trace)) { // empty trace, do not use for automatic axis calculation continue; } // this trace is currently displayed double trace_min = std::numeric_limits::max(); double trace_max = std::numeric_limits::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) { // found min/max values 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::lowest(); double min = std::numeric_limits::max(); for(auto t : tracesAxis[i]) { unsigned int samples = numTraceSamples(t); for(unsigned int j=0;j XAxis.rangeMax) { // this point is not in the displayed X range, skip for auto Y range calculation continue; } if(point.y() > max) { max = point.y(); } else if(point.y() < min) { min = point.y(); } } } if(max >= min) { auto range = max - min; if(range == 0.0) { // this could happen if all values in a trace are identical (e.g. imported ideal touchstone files) if(max == 0.0) { // simply use +/-1 range max = 1.0; min = -1.0; } else { // +/-5% around value max += abs(max * 0.05); min -= abs(max * 0.05); } } else { // add 5% of range at both ends 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); } } } } 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::traceToCoordinate(std::complex 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::quiet_NaN(); } QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlot::YAxisType type) { QPointF ret = QPointF(numeric_limits::quiet_NaN(), numeric_limits::quiet_NaN()); switch(type) { case YAxisType::Magnitude: case YAxisType::Phase: case YAxisType::VSWR: { auto d = t->sample(sample); ret.setY(traceToCoordinate(d.y, type)); ret.setX(d.x); } 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.x < XAxis.rangeMin || d.x > XAxis.rangeMax) { return QPoint(); } auto y = traceToCoordinate(d.y, YAxis[0].type); QPoint p; p.setX(Util::Scale(d.x, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth)); p.setY(Util::Scale(y, YAxis[0].rangeMin, YAxis[0].rangeMax, plotAreaBottom, 0)); return p; } QPoint TraceXYPlot::plotValueToPixel(QPointF plotValue, int Yaxis) { QPoint p; p.setX(Util::Scale(plotValue.x(), XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth)); p.setY(Util::Scale(plotValue.y(), YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax, plotAreaBottom, 0)); return p; } QPointF TraceXYPlot::pixelToPlotValue(QPoint pixel, int Yaxis) { QPointF p; p.setX(Util::Scale(pixel.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth, XAxis.rangeMin, XAxis.rangeMax)); p.setY(Util::Scale(pixel.y(), plotAreaBottom, 0, YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax)); 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(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()); } 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::max(); double closestXpos = 0; auto samples = numTraceSamples(t); for(unsigned int i=0;i(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); } } QString TraceXYPlot::mouseText(QPoint pos) { QString ret; if(QRect(plotAreaLeft, 0, plotAreaWidth + 1, plotAreaBottom).contains(pos)) { // cursor within plot area QPointF coords[2]; coords[0] = pixelToPlotValue(pos, 0); coords[1] = pixelToPlotValue(pos, 1); int significantDigits = floor(log10(XAxis.rangeMax)) - floor(log10((XAxis.rangeMax - XAxis.rangeMin) / 1000.0)) + 1; ret += Unit::ToString(coords[0].x(), AxisUnit(XAxis.type), "fpnum kMG", significantDigits) + "\n"; for(int i=0;i<2;i++) { if(YAxis[i].type != YAxisType::Disabled) { auto max = qMax(abs(YAxis[i].rangeMax), abs(YAxis[i].rangeMin)); auto step = abs(YAxis[i].rangeMax - YAxis[i].rangeMin) / 1000.0; significantDigits = floor(log10(max)) - floor(log10(step)) + 1; ret += Unit::ToString(coords[i].y(), AxisUnit(YAxis[i].type), "fpnum kMG", significantDigits) + "\n"; } } } return ret; } QString TraceXYPlot::AxisUnit(TraceXYPlot::YAxisType type) { switch(type) { case TraceXYPlot::YAxisType::Magnitude: return "db"; break; case TraceXYPlot::YAxisType::Phase: return "°"; break; case TraceXYPlot::YAxisType::VSWR: return ""; break; case TraceXYPlot::YAxisType::Impulse: return ""; break; case TraceXYPlot::YAxisType::Step: return ""; break; case TraceXYPlot::YAxisType::Impedance: return "Ohm"; break; default: return ""; break; } } QString TraceXYPlot::AxisUnit(TraceXYPlot::XAxisType type) { switch(type) { case XAxisType::Frequency: return "Hz"; break; case XAxisType::Time: return "s"; break; case XAxisType::Distance: return "m"; break; default: return ""; break; } }