From 0379040a05a8fcb2891e0710c9f73d89007ce0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Wed, 5 Jan 2022 14:59:25 +0100 Subject: [PATCH] Log option for X axis in XY plot (frequency axis only) --- .../PC_Application/Traces/tracexyplot.cpp | 121 +++++++++++++----- Software/PC_Application/Traces/tracexyplot.h | 2 +- .../Traces/xyplotaxisdialog.cpp | 20 ++- .../PC_Application/Traces/xyplotaxisdialog.ui | 42 ++++-- Software/PC_Application/Util/util.h | 16 ++- 5 files changed, 151 insertions(+), 50 deletions(-) diff --git a/Software/PC_Application/Traces/tracexyplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp index 19cd2dd..4bd2c4e 100644 --- a/Software/PC_Application/Traces/tracexyplot.cpp +++ b/Software/PC_Application/Traces/tracexyplot.cpp @@ -44,7 +44,7 @@ TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent) 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); + setXAxis(XAxisType::Frequency, XAxisMode::UseSpan, false, 0, 6000000000, 600000000); initializeTraceInfo(); } @@ -76,10 +76,11 @@ void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool replot(); } -void TraceXYPlot::setXAxis(XAxisType type, XAxisMode mode, double min, double max, double div) +void TraceXYPlot::setXAxis(XAxisType type, XAxisMode mode, bool log, double min, double max, double div) { XAxis.type = type; XAxis.mode = mode; + XAxis.log = log; XAxis.rangeMin = min; XAxis.rangeMax = max; XAxis.rangeDiv = div; @@ -165,7 +166,8 @@ void TraceXYPlot::fromJSON(nlohmann::json j) auto xmin = jX.value("min", 0.0); auto xmax = jX.value("max", 6000000000.0); auto xdiv = jX.value("div", 600000000.0); - setXAxis(xtype, xmode, xmin, xmax, xdiv); + auto xlog = jX.value("log", false); + setXAxis(xtype, xmode, xlog, xmin, xmax, xdiv); nlohmann::json jY[2] = {j["YPrimary"], j["YSecondary"]}; for(unsigned int i=0;i<2;i++) { YAxisType ytype; @@ -220,17 +222,17 @@ bool TraceXYPlot::configureForTrace(Trace *t) { switch(t->outputType()) { case Trace::DataType::Frequency: - setXAxis(XAxisType::Frequency, XAxisMode::FitTraces, 0, 1, 0.1); + setXAxis(XAxisType::Frequency, XAxisMode::FitTraces, false, 0, 1, 0.1); setYAxis(0, YAxisType::Magnitude, false, true, 0, 1, 1.0); setYAxis(1, YAxisType::Phase, false, true, 0, 1, 1.0); break; case Trace::DataType::Time: - setXAxis(XAxisType::Time, XAxisMode::FitTraces, 0, 1, 0.1); + setXAxis(XAxisType::Time, XAxisMode::FitTraces, false, 0, 1, 0.1); setYAxis(0, YAxisType::ImpulseMag, false, true, 0, 1, 1.0); setYAxis(1, YAxisType::Disabled, false, true, 0, 1, 1.0); break; case Trace::DataType::Power: - setXAxis(XAxisType::Power, XAxisMode::FitTraces, 0, 1, 0.1); + setXAxis(XAxisType::Power, XAxisMode::FitTraces, false, 0, 1, 0.1); setYAxis(0, YAxisType::Magnitude, false, true, 0, 1, 1.0); setYAxis(1, YAxisType::Phase, false, true, 0, 1, 1.0); break; @@ -506,21 +508,24 @@ void TraceXYPlot::draw(QPainter &p) if(XAxis.ticks.size() >= 1) { // draw X ticks - // this only works for evenly distributed ticks: - auto max = qMax(abs(XAxis.ticks.front()), abs(XAxis.ticks.back())); - auto minLabel = qMin(abs(XAxis.ticks.front()), abs(XAxis.ticks.back())); - double step; - if(XAxis.ticks.size() >= 2) { - step = abs(XAxis.ticks[0] - XAxis.ticks[1]); + int significantDigits; + bool displayFullFreq; + if(XAxis.log) { + significantDigits = 5; + displayFullFreq = true; } else { - // only one tick, set arbitrary number of digits - step = max / 1000; + // this only works for evenly distributed ticks: + auto max = qMax(abs(XAxis.ticks.front()), abs(XAxis.ticks.back())); + double step; + if(XAxis.ticks.size() >= 2) { + step = abs(XAxis.ticks[0] - XAxis.ticks[1]); + } else { + // only one tick, set arbitrary number of digits + step = max / 1000; + } + significantDigits = floor(log10(max)) - floor(log10(step)) + 1; + displayFullFreq = significantDigits <= 5; } - if(minLabel > 0 && minLabel < step) { - step = minLabel; - } - int significantDigits = floor(log10(max)) - floor(log10(step)) + 1; - bool displayFullFreq = significantDigits <= 5; constexpr int displayLastDigits = 4; QString prefixes = "fpnum kMG"; QString unit = ""; @@ -543,12 +548,25 @@ void TraceXYPlot::draw(QPainter &p) p.drawText(QRect(bounding.x() + bounding.width(), plotAreaBottom + AxisLabelSize + 5, w.width(), AxisLabelSize), 0, back); } + int lastTickLabelEnd = 0; for(auto t : XAxis.ticks) { - auto xCoord = Util::Scale(t, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth); + auto xCoord = Util::Scale(t, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth, XAxis.log); + p.setPen(QPen(pref.Graphs.Color.axis, 1)); + p.drawLine(xCoord, plotAreaBottom, xCoord, plotAreaBottom + 2); + if(xCoord != plotAreaLeft && xCoord != plotAreaLeft + plotAreaWidth) { + p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine)); + p.drawLine(xCoord, plotAreaTop, xCoord, plotAreaBottom); + } + if(xCoord - 40 <= lastTickLabelEnd) { + // would overlap previous tick label, skip + continue; + } auto tickValue = Unit::ToString(t, unit, prefixes, significantDigits); p.setPen(QPen(pref.Graphs.Color.axis, 1)); if(displayFullFreq) { - p.drawText(QRect(xCoord - 40, plotAreaBottom + 5, 80, AxisLabelSize), Qt::AlignHCenter, tickValue); + QRect bounding; + p.drawText(QRect(xCoord - 40, plotAreaBottom + 5, 80, AxisLabelSize), Qt::AlignHCenter, tickValue, &bounding); + lastTickLabelEnd = bounding.x() + bounding.width(); } else { // check if the same prefix was used as in the fullFreq string if(tickValue.at(tickValue.size() - 1) != commonPrefix) { @@ -559,14 +577,9 @@ void TraceXYPlot::draw(QPainter &p) tickValue.remove(0, tickValue.size() - displayLastDigits - unit.length()); QRect bounding; p.drawText(QRect(xCoord - 40, plotAreaBottom + 5, 80, AxisLabelSize), Qt::AlignHCenter, tickValue, &bounding); + lastTickLabelEnd = bounding.x() + bounding.width(); p.setPen(QPen(QColor("orange"))); - p.drawText(QRect(0, plotAreaBottom + 5, bounding.x() - 1, AxisLabelSize), Qt::AlignRight, ".."); - p.setPen(QPen(pref.Graphs.Color.axis, 1)); - } - p.drawLine(xCoord, plotAreaBottom, xCoord, plotAreaBottom + 2); - if(xCoord != plotAreaLeft && xCoord != plotAreaLeft + plotAreaWidth) { - p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine)); - p.drawLine(xCoord, plotAreaTop, xCoord, plotAreaBottom); + p.drawText(QRect(0, plotAreaBottom + 5, bounding.x() - 1, AxisLabelSize), Qt::AlignRight, "..", &bounding); } } } @@ -648,8 +661,48 @@ void TraceXYPlot::updateAxisTicks() return div_step; }; + auto createLogarithmicTicks = [](vector& ticks, double start, double stop, int minDivisions) { + Q_ASSERT(stop > start); + Q_ASSERT(start > 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); + }; + if(XAxis.mode == XAxisMode::Manual) { - createEvenlySpacedTicks(XAxis.ticks, XAxis.rangeMin, XAxis.rangeMax, XAxis.rangeDiv); + if(XAxis.log) { + createLogarithmicTicks(XAxis.ticks, XAxis.rangeMin, XAxis.rangeMax, 20); + } else { + createEvenlySpacedTicks(XAxis.ticks, XAxis.rangeMin, XAxis.rangeMax, XAxis.rangeDiv); + } } else { XAxis.ticks.clear(); // automatic mode, figure out limits @@ -688,7 +741,11 @@ void TraceXYPlot::updateAxisTicks() // found min/max values XAxis.rangeMin = min; XAxis.rangeMax = max; - XAxis.rangeDiv = createAutomaticTicks(XAxis.ticks, min, max, 8); + if(XAxis.log) { + createLogarithmicTicks(XAxis.ticks, XAxis.rangeMin, XAxis.rangeMax, 20); + } else { + XAxis.rangeDiv = createAutomaticTicks(XAxis.ticks, min, max, 8); + } } } @@ -1006,7 +1063,7 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo QPoint TraceXYPlot::plotValueToPixel(QPointF plotValue, int Yaxis) { QPoint p; - p.setX(Util::Scale(plotValue.x(), XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth)); + p.setX(Util::Scale(plotValue.x(), XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth, XAxis.log)); p.setY(Util::Scale(plotValue.y(), YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax, plotAreaBottom, plotAreaTop)); return p; } @@ -1014,7 +1071,7 @@ QPoint TraceXYPlot::plotValueToPixel(QPointF plotValue, int Yaxis) QPointF TraceXYPlot::pixelToPlotValue(QPoint pixel, int Yaxis) { QPointF p; - p.setX(Util::Scale(pixel.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth, XAxis.rangeMin, XAxis.rangeMax)); + p.setX(Util::Scale(pixel.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth, XAxis.rangeMin, XAxis.rangeMax, false, XAxis.log)); p.setY(Util::Scale(pixel.y(), plotAreaBottom, plotAreaTop, YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax)); return p; } diff --git a/Software/PC_Application/Traces/tracexyplot.h b/Software/PC_Application/Traces/tracexyplot.h index 6d553a1..6ff87bc 100644 --- a/Software/PC_Application/Traces/tracexyplot.h +++ b/Software/PC_Application/Traces/tracexyplot.h @@ -50,7 +50,7 @@ public: }; void setYAxis(int axis, YAxisType type, bool log, bool autorange, double min, double max, double div); - void setXAxis(XAxisType type, XAxisMode mode, double min, double max, double div); + void setXAxis(XAxisType type, XAxisMode mode, bool log, double min, double max, double div); void enableTrace(Trace *t, bool enabled) override; void updateSpan(double min, double max) override; void replot() override; diff --git a/Software/PC_Application/Traces/xyplotaxisdialog.cpp b/Software/PC_Application/Traces/xyplotaxisdialog.cpp index 46fbfdb..5504853 100644 --- a/Software/PC_Application/Traces/xyplotaxisdialog.cpp +++ b/Software/PC_Application/Traces/xyplotaxisdialog.cpp @@ -71,7 +71,7 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) { ui->Xmin->setEnabled(!checked); ui->Xmax->setEnabled(!checked); - ui->Xdivs->setEnabled(!checked); + ui->Xdivs->setEnabled(!checked && ui->Xlinear->isChecked()); ui->Xautomode->setEnabled(checked); }); @@ -82,6 +82,9 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : XAxisTypeChanged((int) plot->XAxis.type); connect(ui->XType, qOverload(&QComboBox::currentIndexChanged), this, &XYplotAxisDialog::XAxisTypeChanged); + connect(ui->Xlog, &QCheckBox::toggled, [=](bool checked){ + ui->Xdivs->setEnabled(!checked && !ui->Xauto->isChecked()); + }); // Fill initial values ui->Y1type->setCurrentIndex((int) plot->YAxis[0].type); @@ -106,6 +109,11 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) : ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax); ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv); + if(plot->XAxis.log) { + ui->Xlog->setChecked(true); + } else { + ui->Xlinear->setChecked(true); + } ui->Xauto->setChecked(plot->XAxis.mode != TraceXYPlot::XAxisMode::Manual); if(plot->XAxis.mode == TraceXYPlot::XAxisMode::UseSpan) { ui->Xautomode->setCurrentIndex(0); @@ -137,7 +145,7 @@ void XYplotAxisDialog::on_buttonBox_accepted() } else { mode = TraceXYPlot::XAxisMode::Manual; } - plot->setXAxis((TraceXYPlot::XAxisType) ui->XType->currentIndex(), mode, ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value()); + plot->setXAxis((TraceXYPlot::XAxisType) ui->XType->currentIndex(), mode, ui->Xlog->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value()); } static void enableComboBoxItem(QComboBox *cb, int itemNum, bool enable) { @@ -168,12 +176,18 @@ void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex) if(type == TraceXYPlot::XAxisType::Frequency) { enableComboBoxItem(ui->Xautomode, 0, true); + ui->Xlog->setEnabled(true); } else { // auto mode using span not supported in time mode if(ui->Xautomode->currentIndex() == 0) { ui->Xautomode->setCurrentIndex(1); - enableComboBoxItem(ui->Xautomode, 0, false); } + enableComboBoxItem(ui->Xautomode, 0, false); + // log option only available for frequency axis + if(ui->Xlog->isChecked()) { + ui->Xlinear->setChecked(true); + } + ui->Xlog->setEnabled(false); } QString unit = TraceXYPlot::AxisUnit(type); diff --git a/Software/PC_Application/Traces/xyplotaxisdialog.ui b/Software/PC_Application/Traces/xyplotaxisdialog.ui index aea77ce..7e56d49 100644 --- a/Software/PC_Application/Traces/xyplotaxisdialog.ui +++ b/Software/PC_Application/Traces/xyplotaxisdialog.ui @@ -21,7 +21,7 @@ - + @@ -325,17 +325,38 @@ - + + + + + Linear + + + Xgroup + + + + + + + true + + + Log + + + Xgroup + + + + + + + - Qt::Vertical + Qt::Horizontal - - - 20 - 40 - - - + @@ -467,5 +488,6 @@ + diff --git a/Software/PC_Application/Util/util.h b/Software/PC_Application/Util/util.h index 0cd6372..806262e 100644 --- a/Software/PC_Application/Util/util.h +++ b/Software/PC_Application/Util/util.h @@ -9,10 +9,18 @@ #include namespace Util { - template T Scale(T value, T from_low, T from_high, T to_low, T to_high) { - value -= from_low; - value *= (to_high - to_low) / (from_high - from_low); - value += to_low; + template T Scale(T value, T from_low, T from_high, T to_low, T to_high, bool log_from = false, bool log_to = false) { + double normalized; + if(log_from) { + normalized = log10(value / from_low) / log10(from_high / from_low); + } else { + normalized = (value - from_low) / (from_high - from_low); + } + if(log_to) { + value = to_low * pow(10.0, normalized * log10(to_high / to_low)); + } else { + value = normalized * (to_high - to_low) + to_low; + } return value; }