Log option for X axis in XY plot (frequency axis only)

This commit is contained in:
Jan Käberich 2022-01-05 14:59:25 +01:00
parent dd8f0c4fa9
commit 0379040a05
5 changed files with 151 additions and 50 deletions

View File

@ -44,7 +44,7 @@ TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent)
setYAxis(1, YAxisType::Phase, false, false, -180, 180, 30); setYAxis(1, YAxisType::Phase, false, false, -180, 180, 30);
// enable autoscaling and set for full span (no information about actual span available yet) // enable autoscaling and set for full span (no information about actual span available yet)
updateSpan(0, 6000000000); updateSpan(0, 6000000000);
setXAxis(XAxisType::Frequency, XAxisMode::UseSpan, 0, 6000000000, 600000000); setXAxis(XAxisType::Frequency, XAxisMode::UseSpan, false, 0, 6000000000, 600000000);
initializeTraceInfo(); initializeTraceInfo();
} }
@ -76,10 +76,11 @@ void TraceXYPlot::setYAxis(int axis, TraceXYPlot::YAxisType type, bool log, bool
replot(); 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.type = type;
XAxis.mode = mode; XAxis.mode = mode;
XAxis.log = log;
XAxis.rangeMin = min; XAxis.rangeMin = min;
XAxis.rangeMax = max; XAxis.rangeMax = max;
XAxis.rangeDiv = div; XAxis.rangeDiv = div;
@ -165,7 +166,8 @@ void TraceXYPlot::fromJSON(nlohmann::json j)
auto xmin = jX.value("min", 0.0); auto xmin = jX.value("min", 0.0);
auto xmax = jX.value("max", 6000000000.0); auto xmax = jX.value("max", 6000000000.0);
auto xdiv = jX.value("div", 600000000.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"]}; nlohmann::json jY[2] = {j["YPrimary"], j["YSecondary"]};
for(unsigned int i=0;i<2;i++) { for(unsigned int i=0;i<2;i++) {
YAxisType ytype; YAxisType ytype;
@ -220,17 +222,17 @@ bool TraceXYPlot::configureForTrace(Trace *t)
{ {
switch(t->outputType()) { switch(t->outputType()) {
case Trace::DataType::Frequency: 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(0, YAxisType::Magnitude, false, true, 0, 1, 1.0);
setYAxis(1, YAxisType::Phase, false, true, 0, 1, 1.0); setYAxis(1, YAxisType::Phase, false, true, 0, 1, 1.0);
break; break;
case Trace::DataType::Time: 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(0, YAxisType::ImpulseMag, false, true, 0, 1, 1.0);
setYAxis(1, YAxisType::Disabled, false, true, 0, 1, 1.0); setYAxis(1, YAxisType::Disabled, false, true, 0, 1, 1.0);
break; break;
case Trace::DataType::Power: 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(0, YAxisType::Magnitude, false, true, 0, 1, 1.0);
setYAxis(1, YAxisType::Phase, false, true, 0, 1, 1.0); setYAxis(1, YAxisType::Phase, false, true, 0, 1, 1.0);
break; break;
@ -506,21 +508,24 @@ void TraceXYPlot::draw(QPainter &p)
if(XAxis.ticks.size() >= 1) { if(XAxis.ticks.size() >= 1) {
// draw X ticks // draw X ticks
// this only works for evenly distributed ticks: int significantDigits;
auto max = qMax(abs(XAxis.ticks.front()), abs(XAxis.ticks.back())); bool displayFullFreq;
auto minLabel = qMin(abs(XAxis.ticks.front()), abs(XAxis.ticks.back())); if(XAxis.log) {
double step; significantDigits = 5;
if(XAxis.ticks.size() >= 2) { displayFullFreq = true;
step = abs(XAxis.ticks[0] - XAxis.ticks[1]);
} else { } else {
// only one tick, set arbitrary number of digits // this only works for evenly distributed ticks:
step = max / 1000; 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; constexpr int displayLastDigits = 4;
QString prefixes = "fpnum kMG"; QString prefixes = "fpnum kMG";
QString unit = ""; 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); p.drawText(QRect(bounding.x() + bounding.width(), plotAreaBottom + AxisLabelSize + 5, w.width(), AxisLabelSize), 0, back);
} }
int lastTickLabelEnd = 0;
for(auto t : XAxis.ticks) { for(auto t : XAxis.ticks) {
auto xCoord = Util::Scale<double>(t, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth); auto xCoord = Util::Scale<double>(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); auto tickValue = Unit::ToString(t, unit, prefixes, significantDigits);
p.setPen(QPen(pref.Graphs.Color.axis, 1)); p.setPen(QPen(pref.Graphs.Color.axis, 1));
if(displayFullFreq) { 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 { } else {
// check if the same prefix was used as in the fullFreq string // check if the same prefix was used as in the fullFreq string
if(tickValue.at(tickValue.size() - 1) != commonPrefix) { if(tickValue.at(tickValue.size() - 1) != commonPrefix) {
@ -559,14 +577,9 @@ void TraceXYPlot::draw(QPainter &p)
tickValue.remove(0, tickValue.size() - displayLastDigits - unit.length()); tickValue.remove(0, tickValue.size() - displayLastDigits - unit.length());
QRect bounding; QRect bounding;
p.drawText(QRect(xCoord - 40, plotAreaBottom + 5, 80, AxisLabelSize), Qt::AlignHCenter, tickValue, &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.setPen(QPen(QColor("orange")));
p.drawText(QRect(0, plotAreaBottom + 5, bounding.x() - 1, AxisLabelSize), Qt::AlignRight, ".."); p.drawText(QRect(0, plotAreaBottom + 5, bounding.x() - 1, AxisLabelSize), Qt::AlignRight, "..", &bounding);
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);
} }
} }
} }
@ -648,8 +661,48 @@ void TraceXYPlot::updateAxisTicks()
return div_step; return div_step;
}; };
auto createLogarithmicTicks = [](vector<double>& 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) { 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 { } else {
XAxis.ticks.clear(); XAxis.ticks.clear();
// automatic mode, figure out limits // automatic mode, figure out limits
@ -688,7 +741,11 @@ void TraceXYPlot::updateAxisTicks()
// found min/max values // found min/max values
XAxis.rangeMin = min; XAxis.rangeMin = min;
XAxis.rangeMax = max; 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 TraceXYPlot::plotValueToPixel(QPointF plotValue, int Yaxis)
{ {
QPoint p; QPoint p;
p.setX(Util::Scale<double>(plotValue.x(), XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth)); p.setX(Util::Scale<double>(plotValue.x(), XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth, XAxis.log));
p.setY(Util::Scale<double>(plotValue.y(), YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax, plotAreaBottom, plotAreaTop)); p.setY(Util::Scale<double>(plotValue.y(), YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax, plotAreaBottom, plotAreaTop));
return p; return p;
} }
@ -1014,7 +1071,7 @@ QPoint TraceXYPlot::plotValueToPixel(QPointF plotValue, int Yaxis)
QPointF TraceXYPlot::pixelToPlotValue(QPoint pixel, int Yaxis) QPointF TraceXYPlot::pixelToPlotValue(QPoint pixel, int Yaxis)
{ {
QPointF p; QPointF p;
p.setX(Util::Scale<double>(pixel.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth, XAxis.rangeMin, XAxis.rangeMax)); p.setX(Util::Scale<double>(pixel.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth, XAxis.rangeMin, XAxis.rangeMax, false, XAxis.log));
p.setY(Util::Scale<double>(pixel.y(), plotAreaBottom, plotAreaTop, YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax)); p.setY(Util::Scale<double>(pixel.y(), plotAreaBottom, plotAreaTop, YAxis[Yaxis].rangeMin, YAxis[Yaxis].rangeMax));
return p; return p;
} }

View File

@ -50,7 +50,7 @@ public:
}; };
void setYAxis(int axis, YAxisType type, bool log, bool autorange, double min, double max, double div); 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 enableTrace(Trace *t, bool enabled) override;
void updateSpan(double min, double max) override; void updateSpan(double min, double max) override;
void replot() override; void replot() override;

View File

@ -71,7 +71,7 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) { connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) {
ui->Xmin->setEnabled(!checked); ui->Xmin->setEnabled(!checked);
ui->Xmax->setEnabled(!checked); ui->Xmax->setEnabled(!checked);
ui->Xdivs->setEnabled(!checked); ui->Xdivs->setEnabled(!checked && ui->Xlinear->isChecked());
ui->Xautomode->setEnabled(checked); ui->Xautomode->setEnabled(checked);
}); });
@ -82,6 +82,9 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
XAxisTypeChanged((int) plot->XAxis.type); XAxisTypeChanged((int) plot->XAxis.type);
connect(ui->XType, qOverload<int>(&QComboBox::currentIndexChanged), this, &XYplotAxisDialog::XAxisTypeChanged); connect(ui->XType, qOverload<int>(&QComboBox::currentIndexChanged), this, &XYplotAxisDialog::XAxisTypeChanged);
connect(ui->Xlog, &QCheckBox::toggled, [=](bool checked){
ui->Xdivs->setEnabled(!checked && !ui->Xauto->isChecked());
});
// Fill initial values // Fill initial values
ui->Y1type->setCurrentIndex((int) plot->YAxis[0].type); ui->Y1type->setCurrentIndex((int) plot->YAxis[0].type);
@ -106,6 +109,11 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax); ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax);
ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv); 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); ui->Xauto->setChecked(plot->XAxis.mode != TraceXYPlot::XAxisMode::Manual);
if(plot->XAxis.mode == TraceXYPlot::XAxisMode::UseSpan) { if(plot->XAxis.mode == TraceXYPlot::XAxisMode::UseSpan) {
ui->Xautomode->setCurrentIndex(0); ui->Xautomode->setCurrentIndex(0);
@ -137,7 +145,7 @@ void XYplotAxisDialog::on_buttonBox_accepted()
} else { } else {
mode = TraceXYPlot::XAxisMode::Manual; 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) { static void enableComboBoxItem(QComboBox *cb, int itemNum, bool enable) {
@ -168,12 +176,18 @@ void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex)
if(type == TraceXYPlot::XAxisType::Frequency) { if(type == TraceXYPlot::XAxisType::Frequency) {
enableComboBoxItem(ui->Xautomode, 0, true); enableComboBoxItem(ui->Xautomode, 0, true);
ui->Xlog->setEnabled(true);
} else { } else {
// auto mode using span not supported in time mode // auto mode using span not supported in time mode
if(ui->Xautomode->currentIndex() == 0) { if(ui->Xautomode->currentIndex() == 0) {
ui->Xautomode->setCurrentIndex(1); 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); QString unit = TraceXYPlot::AxisUnit(type);

View File

@ -21,7 +21,7 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_5">
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
@ -325,17 +325,38 @@
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="verticalSpacer"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QRadioButton" name="Xlinear">
<property name="text">
<string>Linear</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Xgroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Xlog">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Log</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Xgroup</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_8">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> </widget>
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item> </item>
<item> <item>
<layout class="QFormLayout" name="formLayout_6"> <layout class="QFormLayout" name="formLayout_6">
@ -467,5 +488,6 @@
<buttongroups> <buttongroups>
<buttongroup name="Y1group"/> <buttongroup name="Y1group"/>
<buttongroup name="Y2group"/> <buttongroup name="Y2group"/>
<buttongroup name="Xgroup"/>
</buttongroups> </buttongroups>
</ui> </ui>

View File

@ -9,10 +9,18 @@
#include <QColor> #include <QColor>
namespace Util { namespace Util {
template<typename T> T Scale(T value, T from_low, T from_high, T to_low, T to_high) { template<typename T> T Scale(T value, T from_low, T from_high, T to_low, T to_high, bool log_from = false, bool log_to = false) {
value -= from_low; double normalized;
value *= (to_high - to_low) / (from_high - from_low); if(log_from) {
value += to_low; 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; return value;
} }