pan/zoom for smith chart and polar plot

This commit is contained in:
Jan Käberich 2022-10-29 22:59:17 +02:00
parent 7889676ac7
commit dfd1abeea5
9 changed files with 189 additions and 71 deletions

View File

@ -124,24 +124,24 @@
<widget class="SIUnitEdit" name="zoomFactor"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;|Γ| at edge:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>Offset X axis:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="zoomReflection"/>
<widget class="SIUnitEdit" name="offsetXaxis"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Offset real axis:</string>
<string>Offset Y axis:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="offsetRealAxis"/>
<widget class="SIUnitEdit" name="offsetYaxis"/>
</item>
</layout>
</widget>

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>976</width>
<height>460</height>
<width>974</width>
<height>491</height>
</rect>
</property>
<property name="windowTitle">
@ -155,13 +155,23 @@
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Offset real axis</string>
<string>Offset real axis:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="offsetRealAxis"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Offset imag axis:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="offsetImagAxis"/>
</item>
</layout>
</widget>
</item>

View File

@ -16,7 +16,7 @@ TracePolar::TracePolar(TraceModel &model, QWidget *parent)
fmin = 0;
fmax = 6000000000;
edgeReflection = 1.0;
dx = 0;
offset = QPointF(0.0, 0.0);
initializeTraceInfo();
}
@ -26,7 +26,8 @@ nlohmann::json TracePolar::toJSON()
j["limit_to_span"] = limitToSpan;
j["limit_to_edge"] = limitToEdge;
j["edge_reflection"] = edgeReflection;
j["offset_axis_x"] = dx;
j["offset_axis_x"] = offset.x();
j["offset_axis_y"] = offset.y();
j["frequency_override"] = manualFrequencyRange;
j["override_min"] = fmin;
j["override_max"] = fmax;
@ -48,7 +49,7 @@ void TracePolar::fromJSON(nlohmann::json j)
manualFrequencyRange = j.value("frequency_override", false);
fmin = j.value("override_min", 0.0);
fmax = j.value("override_max", 6000000000.0);
dx = j.value("offset_axis_x", 0.0);
offset = QPointF(j.value("offset_axis_x", 0.0), j.value("offset_axis_y", 0.0));
for(unsigned int hash : j["traces"]) {
// attempt to find the traces with this hash
bool found = false;
@ -65,21 +66,59 @@ void TracePolar::fromJSON(nlohmann::json j)
}
}
void TracePolar::wheelEvent(QWheelEvent *event)
void TracePolar::move(const QPoint &vect)
{
// most mousewheel have 15 degree increments, the reported delta is in 1/8th degree -> 120
auto increment = event->angleDelta().y() / 120.0;
// round toward bigger step in case of special higher resolution mousewheel
int steps = increment > 0 ? ceil(increment) : floor(increment);
Trace::Data center;
center.y = 0.0;
center.x = 0.0;
auto shift = pixelToData(dataToPixel(center) + vect);
offset.rx() += shift.real();
offset.ry() += shift.imag();
replot();
}
constexpr double zoomfactor = 1.1;
auto zoom = pow(zoomfactor, steps);
edgeReflection /= zoom;
void TracePolar::zoom(const QPoint &center, double factor, bool horizontally, bool vertically)
{
Q_UNUSED(horizontally);
Q_UNUSED(vertically);
auto pos = pixelToData(center);
auto shift = QPointF(pos.real(), pos.imag());
offset -= shift;
edgeReflection *= factor;
offset += shift * factor;
replot();
}
auto incrementX = event->angleDelta().x() / 120.0;
dx += incrementX/10;
void TracePolar::setAuto(bool horizontally, bool vertically)
{
Q_UNUSED(horizontally);
Q_UNUSED(vertically);
edgeReflection = 1.0;
offset = QPointF(0.0, 0.0);
replot();
}
triggerReplot();
//void TracePolar::wheelEvent(QWheelEvent *event)
//{
// // most mousewheel have 15 degree increments, the reported delta is in 1/8th degree -> 120
// auto increment = event->angleDelta().y() / 120.0;
// // round toward bigger step in case of special higher resolution mousewheel
// int steps = increment > 0 ? ceil(increment) : floor(increment);
// constexpr double zoomfactor = 1.1;
// auto zoom = pow(zoomfactor, steps);
// edgeReflection /= zoom;
// auto incrementX = event->angleDelta().x() / 120.0;
//// dx += incrementX/10;
// triggerReplot();
//}
bool TracePolar::positionWithinGraphArea(const QPoint &p)
{
// TODO
return true;
}
QPoint TracePolar::dataToPixel(std::complex<double> d)
@ -92,19 +131,24 @@ QPoint TracePolar:: dataToPixel(Trace::Data d)
return dataToPixel(d.y);
}
std::complex<double> TracePolar::dataAddDx(std::complex<double> d)
std::complex<double> TracePolar::dataAddOffset(std::complex<double> d)
{
auto dataShift = complex<double>(dx, 0);
auto dataShift = complex<double>(offset.x(), offset.y());
d = d + dataShift;
return d;
}
Trace::Data TracePolar::dataAddDx(Trace::Data d)
Trace::Data TracePolar::dataAddOffset(Trace::Data d)
{
d.y = dataAddDx(d.y);
d.y = dataAddOffset(d.y);
return d;
}
QPoint TracePolar::dataToPixel(QPointF d)
{
return dataToPixel(complex<double>(d.x(), d.y()));
}
std::complex<double> TracePolar::pixelToData(QPoint p)
{
auto data = transform.inverted().map(QPointF(p));
@ -117,7 +161,7 @@ QPoint TracePolar::markerToPixel(Marker *m)
// if(!m->isTimeDomain()) {
if(m->getPosition() >= sweep_fmin && m->getPosition() <= sweep_fmax) {
auto d = m->getData();
d = dataAddDx(d);
d = dataAddOffset(d);
ret = dataToPixel(d);
}
// }
@ -132,7 +176,7 @@ double TracePolar::nearestTracePoint(Trace *t, QPoint pixel, double *distance)
auto samples = t->size();
for(unsigned int i=0;i<samples;i++) {
auto data = t->sample(i);
data = dataAddDx(data);
data = dataAddOffset(data);
auto plotPoint = dataToPixel(data);
if (plotPoint.isNull()) {
// destination point outside of currently displayed range
@ -149,8 +193,8 @@ double TracePolar::nearestTracePoint(Trace *t, QPoint pixel, double *distance)
closestDistance = sqrt(closestDistance);
if(closestIndex > 0) {
auto l1 = dataToPixel(dataAddDx(t->sample(closestIndex-1)));
auto l2 = dataToPixel(dataAddDx(t->sample(closestIndex)));
auto l1 = dataToPixel(dataAddOffset(t->sample(closestIndex-1)));
auto l2 = dataToPixel(dataAddOffset(t->sample(closestIndex)));
double ratio;
auto distance = Util::distanceToLine(pixel, l1, l2, nullptr, &ratio);
if(distance < closestDistance) {
@ -159,8 +203,8 @@ double TracePolar::nearestTracePoint(Trace *t, QPoint pixel, double *distance)
}
}
if(closestIndex < t->size() - 1) {
auto l1 = dataToPixel(dataAddDx(t->sample(closestIndex)));
auto l2 = dataToPixel(dataAddDx(t->sample(closestIndex+1)));
auto l1 = dataToPixel(dataAddOffset(t->sample(closestIndex)));
auto l2 = dataToPixel(dataAddOffset(t->sample(closestIndex+1)));
double ratio;
auto distance = Util::distanceToLine(pixel, l1, l2, nullptr, &ratio);
if(distance < closestDistance) {

View File

@ -21,7 +21,11 @@ public:
virtual nlohmann::json toJSON() override; // derived classes must call TracePolar::joJSON before doing anything
virtual void fromJSON(nlohmann::json j) override; // derived classes must call TracePolar::joJSON before doing anything
void wheelEvent(QWheelEvent *event) override;
virtual void move(const QPoint &vect) override;
virtual void zoom(const QPoint &center, double factor, bool horizontally, bool vertically) override;
virtual void setAuto(bool horizontally, bool vertically) override;
// void wheelEvent(QWheelEvent *event) override;
public slots:
virtual void axisSetupDialog() {}
@ -29,9 +33,11 @@ public slots:
protected:
static constexpr double polarCoordMax = 4096;
virtual std::complex<double> dataAddDx(std::complex<double> d);
virtual Trace::Data dataAddDx(Trace::Data d);
virtual bool positionWithinGraphArea(const QPoint &p) override;
virtual std::complex<double> dataAddOffset(std::complex<double> d);
virtual Trace::Data dataAddOffset(Trace::Data d);
QPoint dataToPixel(QPointF d);
QPoint dataToPixel(std::complex<double> d);
QPoint dataToPixel(Trace::Data d);
std::complex<double> pixelToData(QPoint p);
@ -54,7 +60,7 @@ protected:
double fmin, fmax; // frequency range when manual range is selected
double edgeReflection; // magnitude of reflection coefficient at the edge of the polar chart (zoom factor)
double dx;
QPointF offset;
QTransform transform;
};

View File

@ -48,12 +48,12 @@ void TracePolarChart::axisSetupDialog()
ui->displayFreqOverride->setChecked(manualFrequencyRange);
emit ui->displayFreqOverride->toggled(manualFrequencyRange);
ui->zoomReflection->setPrecision(3);
ui->zoomFactor->setPrecision(3);
ui->offsetRealAxis->setPrecision(3);
ui->zoomReflection->setValue(edgeReflection);
ui->offsetXaxis->setPrecision(3);
ui->offsetYaxis->setPrecision(3);
ui->zoomFactor->setValue(1.0/edgeReflection);
ui->offsetRealAxis->setValue(dx);
ui->offsetXaxis->setValue(offset.x());
ui->offsetYaxis->setValue(offset.y());
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
limitToSpan = ui->displayModeFreq->currentIndex() == 1;
@ -65,14 +65,12 @@ void TracePolarChart::axisSetupDialog()
});
connect(ui->zoomFactor, &SIUnitEdit::valueChanged, [=](){
edgeReflection = 1.0 / ui->zoomFactor->value();
ui->zoomReflection->setValueQuiet(edgeReflection);
});
connect(ui->zoomReflection, &SIUnitEdit::valueChanged, [=](){
edgeReflection = ui->zoomReflection->value();
ui->zoomFactor->setValueQuiet(1.0 / edgeReflection);
connect(ui->offsetXaxis, &SIUnitEdit::valueChanged, [=](){
offset = QPointF(ui->offsetXaxis->value(), offset.y());
});
connect(ui->offsetRealAxis, &SIUnitEdit::valueChanged, [=](){
dx = ui->offsetRealAxis->value();
connect(ui->offsetXaxis, &SIUnitEdit::valueChanged, [=](){
offset = QPointF(offset.x(), ui->offsetXaxis->value());
});
if(AppWindow::showGUI()) {
dialog->show();
@ -123,20 +121,20 @@ void TracePolarChart::draw(QPainter &p) {
p.setPen(pen);
for(int i=1;i<Circles;i++) {
auto radius = (double) i / Circles;
drawArc(PolarArc(QPointF(0.0 + dx,0), radius, 0, 2*M_PI));
drawArc(PolarArc(offset, radius, 0, 2*M_PI));
}
auto constraintLineToCircle = [&](PolarArc cir) { // PolarArc
if ( (cir.spanAngle == 90 )&& (dx != 0.0)) {
auto angle = acos(dx/cir.radius);
auto p1 = complex<double>(dx, cir.center.y() + cir.radius*sin(angle));
auto p2 = complex<double>(dx, cir.center.y() - cir.radius*sin(angle));
if ( (cir.spanAngle == 90 )&& (offset == QPointF(0.0, 0.0))) {
auto angle = acos(offset.x() / cir.radius);
auto p1 = complex<double>(offset.x(), cir.center.y() + cir.radius*sin(angle));
auto p2 = complex<double>(offset.x(), cir.center.y() - cir.radius*sin(angle));
p.drawLine(dataToPixel(p1),dataToPixel(p2));
}
else {
auto slope = tan(cir.spanAngle*2*M_PI/360);
auto y0 = cir.center.y();
auto f = dx;
auto f = offset.x();
auto a = 1 + (slope*slope);
auto b = (-2*cir.center.x())-(2*f*slope*slope)+(2*slope*y0)-(2*cir.center.y()*slope);
auto c = (cir.center.x()*cir.center.x()) +(cir.center.y()*cir.center.y()) - (cir.radius*cir.radius) + (y0*y0) \
@ -157,8 +155,15 @@ void TracePolarChart::draw(QPainter &p) {
constexpr int Lines = 6;
for(int i=0;i<Lines;i++) {
auto angle = (double) i * 30;
constraintLineToCircle(PolarArc(QPointF(0,0), edgeReflection, 0, angle)); // PolarArc
auto angle = (double) i * 30 / 180.0 * M_PI;
auto p1 = QPointF(sin(angle)*100, cos(angle)*100);
auto p2 = -p1;
p1 += offset;
p2 += offset;
if(TracePolar::constrainLineToCircle(p1, p2, QPointF(0,0), edgeReflection)) {
// center line visible
p.drawLine(dataToPixel(p1),dataToPixel(p2));
}
}
for(auto t : traces) {
@ -195,8 +200,8 @@ void TracePolarChart::draw(QPainter &p) {
}
}
last = dataAddDx(last);
now = dataAddDx(now);
last = dataAddOffset(last);
now = dataAddOffset(now);
// scale to size of smith diagram
QPointF p1 = dataToPixel(last);
@ -228,7 +233,7 @@ void TracePolarChart::draw(QPainter &p) {
continue;
}
auto coords = m->getData();
coords = dataAddDx(coords);
coords = dataAddOffset(coords);
if (limitToEdge && abs(coords) > edgeReflection) {
// outside of visible area
@ -278,7 +283,7 @@ QString TracePolarChart::mouseText(QPoint pos)
{
auto dataDx = pixelToData(pos);
if(abs(dataDx) <= edgeReflection) {
auto data = complex<double>(dataDx.real()-dx, dataDx.imag());
auto data = complex<double>(dataDx.real()-offset.x(), dataDx.imag()-offset.y());
auto ret = Unit::ToString(abs(data), "", " ", 3);
ret += QString("");
auto phase = atan(data.imag()/data.real())*180/M_PI;

View File

@ -84,7 +84,10 @@ void TraceSmithChart::axisSetupDialog()
ui->zoomFactor->setPrecision(3);
ui->zoomReflection->setValue(edgeReflection);
ui->zoomFactor->setValue(1.0/edgeReflection);
ui->offsetRealAxis->setValue(dx);
ui->offsetRealAxis->setPrecision(4);
ui->offsetRealAxis->setValue(offset.x());
ui->offsetImagAxis->setPrecision(4);
ui->offsetImagAxis->setValue(offset.y());
ui->impedance->setUnit("Ω");
ui->impedance->setPrecision(3);
@ -113,7 +116,10 @@ void TraceSmithChart::axisSetupDialog()
ui->zoomFactor->setValueQuiet(1.0 / edgeReflection);
});
connect(ui->offsetRealAxis, &SIUnitEdit::valueChanged, [=](){
dx = ui->offsetRealAxis->value();
offset = QPointF(ui->offsetRealAxis->value(), offset.y());
});
connect(ui->offsetImagAxis, &SIUnitEdit::valueChanged, [=](){
offset = QPointF(offset.x(), ui->offsetRealAxis->value());
});
connect(ui->impedance, &SIUnitEdit::valueChanged, [=](){
Z0 = ui->impedance->value();
@ -232,17 +238,22 @@ void TraceSmithChart::draw(QPainter &p) {
p.setPen(pen);
for(int i=1;i<Circles * 2;i++) {
auto radius = (double) i / Circles;
drawArc(SmithChartArc(QPointF(1.0 - radius+dx, 0.0), radius, 0, 2*M_PI));
drawArc(SmithChartArc(QPointF(1.0 + radius+dx, 0.0), radius, 0, 2*M_PI));
drawArc(SmithChartArc(QPointF(1.0 - radius+offset.x(), 0.0+offset.y()), radius, 0, 2*M_PI));
drawArc(SmithChartArc(QPointF(1.0 + radius+offset.x(), 0.0+offset.y()), radius, 0, 2*M_PI));
}
p.drawLine(dataToPixel(complex<double>(edgeReflection,0)),dataToPixel(complex<double>(-edgeReflection,0)));
QPointF p1 = QPointF(-100, offset.y());
QPointF p2 = QPointF(100, offset.y());
if(TracePolar::constrainLineToCircle(p1, p2, QPointF(0,0), edgeReflection)) {
// center line visible
p.drawLine(dataToPixel(p1),dataToPixel(p2));
}
constexpr std::array<double, 5> impedanceLines = {10, 25, 50, 100, 250};
for(auto z : impedanceLines) {
z /= Z0;
auto radius = 1.0/z;
drawArc(SmithChartArc(QPointF(1.0+dx, radius), radius, 0, 2*M_PI));
drawArc(SmithChartArc(QPointF(1.0+dx, -radius), radius, 0, 2*M_PI));
drawArc(SmithChartArc(QPointF(1.0+offset.x(), radius+offset.y()), radius, 0, 2*M_PI));
drawArc(SmithChartArc(QPointF(1.0+offset.x(), -radius+offset.y()), radius, 0, 2*M_PI));
}
// draw custom constant parameter lines
@ -251,6 +262,7 @@ void TraceSmithChart::draw(QPainter &p) {
pen.setCosmetic(true);
p.setPen(pen);
for(auto arc : line.getArcs(Z0)) {
arc.center += offset;
drawArc(arc);
}
}
@ -289,8 +301,8 @@ void TraceSmithChart::draw(QPainter &p) {
}
}
last = dataAddDx(last);
now = dataAddDx(now);
last = dataAddOffset(last);
now = dataAddOffset(now);
// scale to size of smith diagram
QPointF p1 = dataToPixel(last);
@ -325,7 +337,7 @@ void TraceSmithChart::draw(QPainter &p) {
continue;
}
auto coords = m->getData();
coords = dataAddDx(coords);
coords = dataAddOffset(coords);
if (limitToEdge && abs(coords) > edgeReflection) {
// outside of visible area
@ -388,7 +400,7 @@ QString TraceSmithChart::mouseText(QPoint pos)
{
auto dataDx = pixelToData(pos);
if(abs(dataDx) <= edgeReflection) {
auto data = complex<double>(dataDx.real()-dx, dataDx.imag());
auto data = complex<double>(dataDx.real()-offset.x(), dataDx.imag()-offset.y());
data = Z0 * (1.0 + data) / (1.0 - data);
auto ret = Unit::ToString(data.real(), "", " ", 3);
if(data.imag() >= 0) {

View File

@ -61,6 +61,38 @@ void TraceWaterfall::replot()
TracePlot::replot();
}
//void TraceWaterfall::move(const QPoint &vect)
//{
// if(!xAxis.getLog()) {
// // can only move axis in linear mode
// // calculate amount of movement
// double distance = xAxis.inverseTransform(vect.x(), 0, plotAreaWidth) - xAxis.getRangeMin();
// xAxis.set(xAxis.getType(), false, false, xAxis.getRangeMin() - distance, xAxis.getRangeMax() - distance, xAxis.getRangeDiv());
// }
// replot();
//}
//void TraceWaterfall::zoom(const QPoint &center, double factor, bool horizontally, bool vertically)
//{
// if(horizontally && !xAxis.getLog()) {
// // can only zoom axis in linear mode
// // calculate center point
// double cp = xAxis.inverseTransform(center.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth);
// double min = ((xAxis.getRangeMin() - cp) * factor) + cp;
// double max = ((xAxis.getRangeMax() - cp) * factor) + cp;
// xAxis.set(xAxis.getType(), false, false, min, max, xAxis.getRangeDiv() * factor);
// }
// replot();
//}
//void TraceWaterfall::setAuto(bool horizontally, bool vertically)
//{
// if(horizontally) {
// xAxis.set(xAxis.getType(), xAxis.getLog(), true, xAxis.getRangeMin(), xAxis.getRangeMax(), xAxis.getRangeDiv());
// }
// replot();
//}
void TraceWaterfall::fromJSON(nlohmann::json j)
{
resetWaterfall();

View File

@ -17,6 +17,11 @@ public:
virtual void enableTrace(Trace *t, bool enabled) override;
void replot() override;
// virtual void move(const QPoint &vect) override;
// virtual void zoom(const QPoint &center, double factor, bool horizontally, bool vertically) override;
// virtual void setAuto(bool horizontally, bool vertically) override;
virtual Type getType() override { return Type::Waterfall;}
void fromJSON(nlohmann::json j) override;

View File

@ -1223,7 +1223,11 @@
</widget>
</item>
<item>
<widget class="QSpinBox" name="GraphsSweepTriangleSize"/>
<widget class="QSpinBox" name="GraphsSweepTriangleSize">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_52">