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);
// 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,9 +508,14 @@ void TraceXYPlot::draw(QPainter &p)
if(XAxis.ticks.size() >= 1) {
// draw X ticks
int significantDigits;
bool displayFullFreq;
if(XAxis.log) {
significantDigits = 5;
displayFullFreq = true;
} else {
// 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]);
@ -516,11 +523,9 @@ void TraceXYPlot::draw(QPainter &p)
// only one tick, set arbitrary number of digits
step = max / 1000;
}
if(minLabel > 0 && minLabel < step) {
step = minLabel;
significantDigits = floor(log10(max)) - floor(log10(step)) + 1;
displayFullFreq = significantDigits <= 5;
}
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<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);
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<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.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,9 +741,13 @@ void TraceXYPlot::updateAxisTicks()
// found min/max values
XAxis.rangeMin = min;
XAxis.rangeMax = max;
if(XAxis.log) {
createLogarithmicTicks(XAxis.ticks, XAxis.rangeMin, XAxis.rangeMax, 20);
} else {
XAxis.rangeDiv = createAutomaticTicks(XAxis.ticks, min, max, 8);
}
}
}
for(int i=0;i<2;i++) {
if(!YAxis[i].autorange) {
@ -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<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));
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<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));
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 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;

View File

@ -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<int>(&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);

View File

@ -21,7 +21,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
@ -325,17 +325,38 @@
</widget>
</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">
<enum>Qt::Vertical</enum>
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_6">
@ -467,5 +488,6 @@
<buttongroups>
<buttongroup name="Y1group"/>
<buttongroup name="Y2group"/>
<buttongroup name="Xgroup"/>
</buttongroups>
</ui>

View File

@ -9,10 +9,18 @@
#include <QColor>
namespace Util {
template<typename T> 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<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) {
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;
}