diff --git a/Software/PC_Application/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI.pro index a603252..51c7ead 100644 --- a/Software/PC_Application/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI.pro @@ -102,6 +102,7 @@ HEADERS += \ Traces/tracewaterfall.h \ Traces/tracewidget.h \ Traces/tracexyplot.h \ + Traces/tracepolar.h \ Traces/waterfallaxisdialog.h \ Traces/xyplotaxisdialog.h \ Traces/tracepolarchart.h \ @@ -228,6 +229,7 @@ SOURCES += \ Traces/tracewaterfall.cpp \ Traces/tracewidget.cpp \ Traces/tracexyplot.cpp \ + Traces/tracepolar.cpp \ Traces/waterfallaxisdialog.cpp \ Traces/xyplotaxisdialog.cpp \ Util/util.cpp \ diff --git a/Software/PC_Application/Traces/smithchartdialog.ui b/Software/PC_Application/Traces/smithchartdialog.ui index 7843bad..1adddc7 100644 --- a/Software/PC_Application/Traces/smithchartdialog.ui +++ b/Software/PC_Application/Traces/smithchartdialog.ui @@ -117,6 +117,16 @@ + + + + Offset real axis + + + + + + diff --git a/Software/PC_Application/Traces/tracepolar.cpp b/Software/PC_Application/Traces/tracepolar.cpp new file mode 100644 index 0000000..61e5d2c --- /dev/null +++ b/Software/PC_Application/Traces/tracepolar.cpp @@ -0,0 +1,312 @@ +#include "tracepolar.h" + +#include "Marker/marker.h" +#include "Util/util.h" + +#include + +using namespace std; + +TracePolar::TracePolar(TraceModel &model, QWidget *parent) + : TracePlot(model, parent) +{ + limitToSpan = true; + limitToEdge = true; + edgeReflection = 1.0; + initializeTraceInfo(); +} + +nlohmann::json TracePolar::toJSON() +{ + nlohmann::json j; + j["limit_to_span"] = limitToSpan; + j["limit_to_edge"] = limitToEdge; + j["edge_reflection"] = edgeReflection; + j["offset_axis_x"] = dx; + nlohmann::json jtraces; + for(auto t : traces) { + if(t.second) { + jtraces.push_back(t.first->toHash()); + } + } + j["traces"] = jtraces; + return j; +} + +void TracePolar::fromJSON(nlohmann::json j) +{ + limitToSpan = j.value("limit_to_span", true); + limitToEdge = j.value("limit_to_edge", false); + edgeReflection = j.value("edge_reflection", 1.0); + dx = j.value("offset_axis_x", 0.0); + for(unsigned int hash : j["traces"]) { + // attempt to find the traces with this hash + bool found = false; + for(auto t : model.getTraces()) { + if(t->toHash() == hash) { + enableTrace(t, true); + found = true; + break; + } + } + if(!found) { + qWarning() << "Unable to find trace with hash" << hash; + } + } +} + +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(); +} + +QPoint TracePolar::dataToPixel(std::complex d) +{ + return transform.map(QPoint(d.real() * polarCoordMax * (1.0 / edgeReflection), -d.imag() * polarCoordMax * (1.0 / edgeReflection))); +} + +QPoint TracePolar::dataToPixel(Trace::Data d) +{ + return dataToPixel(d.y); +} + +std::complex TracePolar::dataAddDx(std::complex d) +{ + auto dataShift = complex(dx, 0); + d = d + dataShift; + return d; +} + +Trace::Data TracePolar::dataAddDx(Trace::Data d) +{ + d.y = dataAddDx(d.y); + return d; +} + +std::complex TracePolar::pixelToData(QPoint p) +{ + auto data = transform.inverted().map(QPointF(p)); + return complex(data.x() / polarCoordMax * edgeReflection, -data.y() / polarCoordMax * edgeReflection); +} + +QPoint TracePolar::markerToPixel(Marker *m) +{ + QPoint ret = QPoint(); +// if(!m->isTimeDomain()) { + if(m->getPosition() >= sweep_fmin && m->getPosition() <= sweep_fmax) { + auto d = m->getData(); + d = dataAddDx(d); + ret = dataToPixel(d); + } +// } + return ret; +} + +double TracePolar::nearestTracePoint(Trace *t, QPoint pixel, double *distance) +{ + double closestDistance = numeric_limits::max(); + double closestXpos = 0; + unsigned int closestIndex = 0; + auto samples = t->size(); + for(unsigned int i=0;isample(i); + data = dataAddDx(data); + auto plotPoint = dataToPixel(data); + if (plotPoint.isNull()) { + // destination point outside of currently displayed range + continue; + } + auto diff = plotPoint - pixel; + unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y(); + if(distance < closestDistance) { + closestDistance = distance; + closestXpos = t->sample(i).x; + closestIndex = i; + } + } + closestDistance = sqrt(closestDistance); + + if(closestIndex > 0) { + auto l1 = dataToPixel(dataAddDx(t->sample(closestIndex-1))); + auto l2 = dataToPixel(dataAddDx(t->sample(closestIndex))); + double ratio; + auto distance = Util::distanceToLine(pixel, l1, l2, nullptr, &ratio); + if(distance < closestDistance) { + closestDistance = distance; + closestXpos = t->sample(closestIndex-1).x + (t->sample(closestIndex).x - t->sample(closestIndex-1).x) * ratio; + } + } + if(closestIndex < t->size() - 1) { + auto l1 = dataToPixel(dataAddDx(t->sample(closestIndex))); + auto l2 = dataToPixel(dataAddDx(t->sample(closestIndex+1))); + double ratio; + auto distance = Util::distanceToLine(pixel, l1, l2, nullptr, &ratio); + if(distance < closestDistance) { + closestDistance = distance; + closestXpos = t->sample(closestIndex).x + (t->sample(closestIndex+1).x - t->sample(closestIndex).x) * ratio; + } + } + if(distance) { + *distance = closestDistance; + } + return closestXpos; +} + +bool TracePolar::markerVisible(double x) +{ + if(limitToSpan) { + if(x >= sweep_fmin && x <= sweep_fmax) { + return true; + } else { + return false; + } + } else { + // complete traces visible + return true; + } +} + +void TracePolar::updateContextMenu() +{ + contextmenu->clear(); + auto setup = new QAction("Setup...", contextmenu); + connect(setup, &QAction::triggered, this, &TracePolar::axisSetupDialog, Qt::UniqueConnection); + contextmenu->addAction(setup); + + contextmenu->addSeparator(); + auto image = new QAction("Save image...", contextmenu); + contextmenu->addAction(image); + connect(image, &QAction::triggered, [=]() { + auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, QFileDialog::DontUseNativeDialog); + if(filename.isEmpty()) { + // aborted selection + return; + } + if(filename.endsWith(".png")) { + filename.chop(4); + } + filename += ".png"; + grab().save(filename); + }); + + auto createMarker = contextmenu->addAction("Add marker here"); + bool activeTraces = false; + for(auto t : traces) { + if(t.second) { + activeTraces = true; + break; + } + } + if(!activeTraces) { + createMarker->setEnabled(false); + } + connect(createMarker, &QAction::triggered, [=](){ + createMarkerAtPosition(contextmenuClickpoint); + }); + + contextmenu->addSection("Traces"); + // Populate context menu + for(auto t : orderedTraces()) { + if(!supported(t)) { + continue; + } + auto action = new QAction(t->name(), contextmenu); + action->setCheckable(true); + if(traces[t]) { + action->setChecked(true); + } + connect(action, &QAction::toggled, [=](bool active) { + enableTrace(t, active); + }); + contextmenu->addAction(action); + } + + finishContextMenu(); +} + +PolarArc::PolarArc(QPointF center, double radius, double startAngle, double spanAngle) + : center(center), + radius(radius), + startAngle(startAngle), + spanAngle(spanAngle) +{ + +} + +void PolarArc::constrainToCircle(QPointF center, double radius) +{ + // check if arc/circle intersect + auto centerDiff = this->center - center; + auto centerDistSquared = centerDiff.x() * centerDiff.x() + centerDiff.y() * centerDiff.y(); + if (centerDistSquared >= (radius + this->radius) * (radius + this->radius)) { + // arc completely outside of constraining circle + spanAngle = 0.0; + return; + } else if (centerDistSquared <= (radius - this->radius) * (radius - this->radius)) { + if (radius >= this->radius) { + // arc completely in constraining circle, do nothing + return; + } else { + // arc completely outside of circle + spanAngle = 0.0; + return; + } + } else { + // there are intersections between the arc and the circle. Calculate points according to https://stackoverflow.com/questions/3349125/circle-circle-intersection-points + auto distance = sqrt(centerDistSquared); + auto a = (this->radius*this->radius-radius*radius+distance*distance) / (2*distance); + auto h = sqrt(this->radius*this->radius - a*a); + auto intersectMiddle = this->center + a*(center - this->center) / distance; + auto rotatedCenterDiff = center - this->center; + swap(rotatedCenterDiff.rx(), rotatedCenterDiff.ry()); + rotatedCenterDiff.setY(-rotatedCenterDiff.y()); + auto intersect1 = intersectMiddle + h * rotatedCenterDiff / distance; + auto intersect2 = intersectMiddle - h * rotatedCenterDiff / distance; + + // got intersection points, convert into angles from arc center + auto wrapAngle = [](double angle) -> double { + double ret = fmod(angle, 2*M_PI); + if(ret < 0) { + ret += 2*M_PI; + } + return ret; + }; + + auto angle1 = wrapAngle(atan2((intersect1 - this->center).y(), (intersect1 - this->center).x())); + auto angle2 = wrapAngle(atan2((intersect2 - this->center).y(), (intersect2 - this->center).x())); + + auto angleDiff = wrapAngle(angle2 - angle1); + if ((angleDiff >= M_PI) ^ (a > 0.0)) { + // allowed angles go from intersect1 to intersect2 + if(startAngle < angle1) { + startAngle = angle1; + } + auto maxSpan = wrapAngle(angle2 - startAngle); + if(spanAngle > maxSpan) { + spanAngle = maxSpan; + } + } else { + // allowed angles go from intersect2 to intersect1 + if(startAngle < angle2) { + startAngle = angle2; + } + auto maxSpan = wrapAngle(angle1 - startAngle); + if(spanAngle > maxSpan) { + spanAngle = maxSpan; + } + } + } +} + diff --git a/Software/PC_Application/Traces/tracepolar.h b/Software/PC_Application/Traces/tracepolar.h new file mode 100644 index 0000000..1865e32 --- /dev/null +++ b/Software/PC_Application/Traces/tracepolar.h @@ -0,0 +1,53 @@ +#ifndef TRACEPOLAR_H +#define TRACEPOLAR_H + +#include "traceplot.h" + +class PolarArc { +public: + PolarArc(QPointF center, double radius, double startAngle = 0.0, double spanAngle = 2*M_PI); + void constrainToCircle(QPointF center, double radius); + QPointF center; + double radius; + double startAngle, spanAngle; +}; + +class TracePolar : public TracePlot +{ + Q_OBJECT +public: + TracePolar(TraceModel &model, QWidget *parent = 0); + + 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; + +public slots: + virtual void axisSetupDialog() {}; + +protected: + static constexpr double polarCoordMax = 4096; + + virtual std::complex dataAddDx(std::complex d); + virtual Trace::Data dataAddDx(Trace::Data d); + + QPoint dataToPixel(std::complex d); + QPoint dataToPixel(Trace::Data d); + std::complex pixelToData(QPoint p); + + QPoint markerToPixel(Marker *m) override; + double nearestTracePoint(Trace *t, QPoint pixel, double *distance = nullptr) override; + virtual bool markerVisible(double x) override; + + virtual void updateContextMenu() override; + virtual bool supported(Trace *t) override {Q_UNUSED(t) return false;}; + + bool limitToSpan; + bool limitToEdge; + double edgeReflection; // magnitude of reflection coefficient at the edge of the polar chart (zoom factor) + double dx; + QTransform transform; +}; + +#endif // TRACEPOLAR_H diff --git a/Software/PC_Application/Traces/tracepolarchart.cpp b/Software/PC_Application/Traces/tracepolarchart.cpp index dee64dc..48e60f0 100644 --- a/Software/PC_Application/Traces/tracepolarchart.cpp +++ b/Software/PC_Application/Traces/tracepolarchart.cpp @@ -2,10 +2,7 @@ #include "ui_polarchartdialog.h" #include "preferences.h" -#include "tracesmithchart.h" #include "unit.h" -#include "Marker/marker.h" -#include "Util/util.h" #include "appwindow.h" #include @@ -14,29 +11,8 @@ using namespace std; TracePolarChart::TracePolarChart(TraceModel &model, QWidget *parent) - : TracePlot(model, parent) + : TracePolar(model, parent) { - limitToSpan = true; - limitToEdge = true; - edgeReflection = 1.0; - dx = 0.0; - initializeTraceInfo(); -} - -void TracePolarChart::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(); } void TracePolarChart::axisSetupDialog() @@ -82,35 +58,6 @@ void TracePolarChart::axisSetupDialog() } } -QPoint TracePolarChart::dataToPixel(std::complex d) -{ - return transform.map(QPoint(d.real() * polarCoordMax * (1.0 / edgeReflection), -d.imag() * polarCoordMax * (1.0 / edgeReflection))); -} - -QPoint TracePolarChart::dataToPixel(Trace::Data d) -{ - return dataToPixel(d.y); -} - -std::complex TracePolarChart::dataAddDx(std::complex d) -{ - auto dataShift = complex(dx, 0); - d = d + dataShift; - return d; -} - -Trace::Data TracePolarChart::dataAddDx(Trace::Data d) -{ - d.y = dataAddDx(d.y); - return d; -} - -std::complex TracePolarChart::pixelToData(QPoint p) -{ - auto data = transform.inverted().map(QPointF(p)); - return complex(data.x() / polarCoordMax * edgeReflection, -data.y() / polarCoordMax * edgeReflection); -} - void TracePolarChart::draw(QPainter &p) { auto pref = Preferences::getInstance(); @@ -124,7 +71,7 @@ void TracePolarChart::draw(QPainter &p) { transform = p.transform(); p.restore(); - auto drawArc = [&](SmithChartArc a) { + auto drawArc = [&](PolarArc a) { a.constrainToCircle(QPointF(0,0), edgeReflection); auto topleft = dataToPixel(complex(a.center.x() - a.radius, a.center.y() - a.radius)); auto bottomright = dataToPixel(complex(a.center.x() + a.radius, a.center.y() + a.radius)); @@ -137,7 +84,7 @@ void TracePolarChart::draw(QPainter &p) { auto pen = QPen(pref.Graphs.Color.axis); pen.setCosmetic(true); p.setPen(pen); - drawArc(SmithChartArc(QPointF(0.0, 0.0), edgeReflection, 0, 2*M_PI)); + drawArc(PolarArc(QPointF(0.0, 0.0), edgeReflection, 0, 2*M_PI)); constexpr int Circles = 6; pen = QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine); @@ -145,10 +92,10 @@ void TracePolarChart::draw(QPainter &p) { p.setPen(pen); for(int i=1;i(dx, cir.center.y() + cir.radius*sin(angle)); @@ -180,7 +127,7 @@ void TracePolarChart::draw(QPainter &p) { constexpr int Lines = 6; for(int i=0;itoHash() == hash) { - enableTrace(t, true); - found = true; - break; - } - } - if(!found) { - qWarning() << "Unable to find trace with hash" << hash; - } - } -} - -nlohmann::json TracePolarChart::toJSON() -{ - nlohmann::json j; - j["limit_to_span"] = limitToSpan; - j["limit_to_edge"] = limitToEdge; - j["edge_reflection"] = edgeReflection; - j["offset_axis_x"] = dx; - nlohmann::json jtraces; - for(auto t : traces) { - if(t.second) { - jtraces.push_back(t.first->toHash()); - } - } - j["traces"] = jtraces; - return j; -} - -double TracePolarChart::nearestTracePoint(Trace *t, QPoint pixel, double *distance) -{ - double closestDistance = numeric_limits::max(); - double closestXpos = 0; - unsigned int closestIndex = 0; - auto samples = t->size(); - for(unsigned int i=0;isample(i); - data = dataAddDx(data); - auto plotPoint = dataToPixel(data); - if (plotPoint.isNull()) { - // destination point outside of currently displayed range - continue; - } - auto diff = plotPoint - pixel; - unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y(); - if(distance < closestDistance) { - closestDistance = distance; - closestXpos = t->sample(i).x; - closestIndex = i; - } - } - closestDistance = sqrt(closestDistance); - - if(closestIndex > 0) { - auto l1 = dataToPixel(dataAddDx(t->sample(closestIndex-1))); - auto l2 = dataToPixel(dataAddDx(t->sample(closestIndex))); - double ratio; - auto distance = Util::distanceToLine(pixel, l1, l2, nullptr, &ratio); - if(distance < closestDistance) { - closestDistance = distance; - closestXpos = t->sample(closestIndex-1).x + (t->sample(closestIndex).x - t->sample(closestIndex-1).x) * ratio; - } - } - if(closestIndex < t->size() - 1) { - auto l1 = dataToPixel(dataAddDx(t->sample(closestIndex))); - auto l2 = dataToPixel(dataAddDx(t->sample(closestIndex+1))); - double ratio; - auto distance = Util::distanceToLine(pixel, l1, l2, nullptr, &ratio); - if(distance < closestDistance) { - closestDistance = distance; - closestXpos = t->sample(closestIndex).x + (t->sample(closestIndex+1).x - t->sample(closestIndex).x) * ratio; - } - } - if(distance) { - *distance = closestDistance; - } - return closestXpos; -} - bool TracePolarChart::dropSupported(Trace *t) { if(!t->isReflection()) { @@ -368,95 +226,11 @@ bool TracePolarChart::dropSupported(Trace *t) } } -bool TracePolarChart::markerVisible(double x) -{ - if(limitToSpan) { - if(x >= sweep_fmin && x <= sweep_fmax) { - return true; - } else { - return false; - } - } else { - // complete traces visible - return true; - } -} - bool TracePolarChart::supported(Trace *t) { return dropSupported(t); } -void TracePolarChart::updateContextMenu() -{ - contextmenu->clear(); - auto setup = new QAction("Setup...", contextmenu); - connect(setup, &QAction::triggered, this, &TracePolarChart::axisSetupDialog); - contextmenu->addAction(setup); - - contextmenu->addSeparator(); - auto image = new QAction("Save image...", contextmenu); - contextmenu->addAction(image); - connect(image, &QAction::triggered, [=]() { - auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, QFileDialog::DontUseNativeDialog); - if(filename.isEmpty()) { - // aborted selection - return; - } - if(filename.endsWith(".png")) { - filename.chop(4); - } - filename += ".png"; - grab().save(filename); - }); - - auto createMarker = contextmenu->addAction("Add marker here"); - bool activeTraces = false; - for(auto t : traces) { - if(t.second) { - activeTraces = true; - break; - } - } - if(!activeTraces) { - createMarker->setEnabled(false); - } - - connect(createMarker, &QAction::triggered, [=](){ - createMarkerAtPosition(contextmenuClickpoint); - }); - - contextmenu->addSection("Traces"); - // Populate context menu - for(auto t : orderedTraces()) { - if(!supported(t)) { - continue; - } - auto action = new QAction(t->name(), contextmenu); - action->setCheckable(true); - if(traces[t]) { - action->setChecked(true); - } - connect(action, &QAction::toggled, [=](bool active) { - enableTrace(t, active); - }); - contextmenu->addAction(action); - } - - finishContextMenu(); -} - -QPoint TracePolarChart::markerToPixel(Marker *m) -{ - QPoint ret = QPoint(); - if(m->getPosition() >= sweep_fmin && m->getPosition() <= sweep_fmax) { - auto d = m->getData(); - d = dataAddDx(d); - ret = dataToPixel(d); - } - return ret; -} - QString TracePolarChart::mouseText(QPoint pos) { auto dataDx = pixelToData(pos); @@ -480,12 +254,3 @@ QString TracePolarChart::mouseText(QPoint pos) return QString(); } } - -PolarChartCircle::PolarChartCircle(QPointF center, double radius, double startAngle, double spanAngle) - : center(center), - radius(radius), - startAngle(startAngle), - spanAngle(spanAngle) -{ - -} diff --git a/Software/PC_Application/Traces/tracepolarchart.h b/Software/PC_Application/Traces/tracepolarchart.h index 44a6a16..18b89f4 100644 --- a/Software/PC_Application/Traces/tracepolarchart.h +++ b/Software/PC_Application/Traces/tracepolarchart.h @@ -1,19 +1,9 @@ #ifndef TRACEPOLARCHART_H #define TRACEPOLARCHART_H -#include "traceplot.h" +#include "tracepolar.h" -class PolarChartCircle -{ -public: - PolarChartCircle(QPointF center, double radius, double startAngle = 0.0, double spanAngle = 2*M_PI); - QPointF center; - double radius; - double startAngle, spanAngle; -}; - - -class TracePolarChart : public TracePlot +class TracePolarChart : public TracePolar { Q_OBJECT public: @@ -21,36 +11,14 @@ public: virtual Type getType() override { return Type::PolarChart;}; - virtual nlohmann::json toJSON() override; - virtual void fromJSON(nlohmann::json j) override; - - void wheelEvent(QWheelEvent *event) override; public slots: - void axisSetupDialog(); + virtual void axisSetupDialog() override; private: - static constexpr double polarCoordMax = 4096; - - std::complex dataAddDx(std::complex d); - Trace::Data dataAddDx(Trace::Data d); - - QPoint dataToPixel(std::complex d); - QPoint dataToPixel(Trace::Data d); - std::complex pixelToData(QPoint p); - QPoint markerToPixel(Marker *m) override; - double nearestTracePoint(Trace *t, QPoint pixel, double *distance = nullptr) override; - virtual bool markerVisible(double x); - - virtual void updateContextMenu() override; bool supported(Trace *t) override; virtual void draw(QPainter& painter) override; virtual bool dropSupported(Trace *t) override; QString mouseText(QPoint pos) override; - bool limitToSpan; - bool limitToEdge; - double edgeReflection; - double dx; - QTransform transform; }; #endif // TRACEPOLARCHART_H diff --git a/Software/PC_Application/Traces/tracesmithchart.cpp b/Software/PC_Application/Traces/tracesmithchart.cpp index e690320..35d4180 100644 --- a/Software/PC_Application/Traces/tracesmithchart.cpp +++ b/Software/PC_Application/Traces/tracesmithchart.cpp @@ -1,11 +1,9 @@ #include "tracesmithchart.h" -#include "Marker/marker.h" #include "preferences.h" #include "ui_smithchartdialog.h" #include "unit.h" #include "QFileDialog" -#include "Util/util.h" #include "appwindow.h" #include "CustomWidgets/informationbox.h" @@ -18,29 +16,16 @@ using namespace std; TraceSmithChart::TraceSmithChart(TraceModel &model, QWidget *parent) - : TracePlot(model, parent) + : TracePolar(model, parent) { - limitToSpan = true; - limitToEdge = true; - edgeReflection = 1.0; Z0 = 50.0; - initializeTraceInfo(); } nlohmann::json TraceSmithChart::toJSON() { nlohmann::json j; - j["limit_to_span"] = limitToSpan; - j["limit_to_edge"] = limitToEdge; - j["edge_reflection"] = edgeReflection; + j = TracePolar::toJSON(); j["Z0"] = Z0; - nlohmann::json jtraces; - for(auto t : traces) { - if(t.second) { - jtraces.push_back(t.first->toHash()); - } - } - j["traces"] = jtraces; nlohmann::json jlines; for(auto line : constantLines) { jlines.push_back(line.toJSON()); @@ -51,24 +36,8 @@ nlohmann::json TraceSmithChart::toJSON() void TraceSmithChart::fromJSON(nlohmann::json j) { - limitToSpan = j.value("limit_to_span", true); - limitToEdge = j.value("limit_to_edge", false); - edgeReflection = j.value("edge_reflection", 1.0); + TracePolar::fromJSON(j); Z0 = j.value("Z0", 50.0); - for(unsigned int hash : j["traces"]) { - // attempt to find the traces with this hash - bool found = false; - for(auto t : model.getTraces()) { - if(t->toHash() == hash) { - enableTrace(t, true); - found = true; - break; - } - } - if(!found) { - qWarning() << "Unable to find trace with hash" << hash; - } - } if(j.contains("constantLines")) { for(auto jline : j["constantLines"]) { SmithChartConstantLine line; @@ -78,19 +47,6 @@ void TraceSmithChart::fromJSON(nlohmann::json j) } } -void TraceSmithChart::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; - triggerReplot(); -} - void TraceSmithChart::axisSetupDialog() { auto dialog = new QDialog(); @@ -110,6 +66,7 @@ void TraceSmithChart::axisSetupDialog() ui->zoomFactor->setPrecision(3); ui->zoomReflection->setValue(edgeReflection); ui->zoomFactor->setValue(1.0/edgeReflection); + ui->offsetRealAxis->setValue(dx); ui->impedance->setUnit("Ω"); ui->impedance->setPrecision(3); @@ -133,6 +90,9 @@ void TraceSmithChart::axisSetupDialog() edgeReflection = ui->zoomReflection->value(); ui->zoomFactor->setValueQuiet(1.0 / edgeReflection); }); + connect(ui->offsetRealAxis, &SIUnitEdit::valueChanged, [=](){ + dx = ui->offsetRealAxis->value(); + }); connect(ui->impedance, &SIUnitEdit::valueChanged, [=](){ Z0 = ui->impedance->value(); for(auto t : traces) { @@ -189,95 +149,6 @@ void TraceSmithChart::axisSetupDialog() } } -QPoint TraceSmithChart::dataToPixel(std::complex d) -{ - return transform.map(QPoint(d.real() * smithCoordMax * (1.0 / edgeReflection), -d.imag() * smithCoordMax * (1.0 / edgeReflection))); -} - -QPoint TraceSmithChart::dataToPixel(Trace::Data d) -{ - return dataToPixel(d.y);} - -std::complex TraceSmithChart::pixelToData(QPoint p) -{ - auto data = transform.inverted().map(QPointF(p)); - return complex(data.x() / smithCoordMax * edgeReflection, -data.y() / smithCoordMax * edgeReflection); -} - -QPoint TraceSmithChart::markerToPixel(Marker *m) -{ - QPoint ret = QPoint(); -// if(!m->isTimeDomain()) { - if(m->getPosition() >= sweep_fmin && m->getPosition() <= sweep_fmax) { - auto d = m->getData(); - ret = dataToPixel(d); - } -// } - return ret; -} - -double TraceSmithChart::nearestTracePoint(Trace *t, QPoint pixel, double *distance) -{ - double closestDistance = numeric_limits::max(); - double closestXpos = 0; - unsigned int closestIndex = 0; - auto samples = t->size(); - for(unsigned int i=0;isample(i); - auto plotPoint = dataToPixel(data); - if (plotPoint.isNull()) { - // destination point outside of currently displayed range - continue; - } - auto diff = plotPoint - pixel; - unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y(); - if(distance < closestDistance) { - closestDistance = distance; - closestXpos = t->sample(i).x; - closestIndex = i; - } - } - closestDistance = sqrt(closestDistance); - if(closestIndex > 0) { - auto l1 = dataToPixel(t->sample(closestIndex-1)); - auto l2 = dataToPixel(t->sample(closestIndex)); - double ratio; - auto distance = Util::distanceToLine(pixel, l1, l2, nullptr, &ratio); - if(distance < closestDistance) { - closestDistance = distance; - closestXpos = t->sample(closestIndex-1).x + (t->sample(closestIndex).x - t->sample(closestIndex-1).x) * ratio; - } - } - if(closestIndex < t->size() - 1) { - auto l1 = dataToPixel(t->sample(closestIndex)); - auto l2 = dataToPixel(t->sample(closestIndex+1)); - double ratio; - auto distance = Util::distanceToLine(pixel, l1, l2, nullptr, &ratio); - if(distance < closestDistance) { - closestDistance = distance; - closestXpos = t->sample(closestIndex).x + (t->sample(closestIndex+1).x - t->sample(closestIndex).x) * ratio; - } - } - if(distance) { - *distance = closestDistance; - } - return closestXpos; -} - -bool TraceSmithChart::markerVisible(double x) -{ - if(limitToSpan) { - if(x >= sweep_fmin && x <= sweep_fmax) { - return true; - } else { - return false; - } - } else { - // complete traces visible - return true; - } -} - bool TraceSmithChart::configureForTrace(Trace *t) { if(dropSupported(t)) { @@ -299,7 +170,7 @@ void TraceSmithChart::draw(QPainter &p) { auto w = p.window(); p.save(); p.translate(w.width()/2, w.height()/2); - auto scale = qMin(w.height(), w.width()) / (2.0 * smithCoordMax); + auto scale = qMin(w.height(), w.width()) / (2.0 * polarCoordMax); p.scale(scale, scale); transform = p.transform(); @@ -326,8 +197,8 @@ void TraceSmithChart::draw(QPainter &p) { p.setPen(pen); for(int i=1;i(edgeReflection,0)),dataToPixel(complex(-edgeReflection,0))); @@ -335,8 +206,8 @@ void TraceSmithChart::draw(QPainter &p) { for(auto z : impedanceLines) { z /= Z0; auto radius = 1.0/z; - drawArc(SmithChartArc(QPointF(1.0, radius), radius, 0, 2*M_PI)); - drawArc(SmithChartArc(QPointF(1.0, -radius), radius, 0, 2*M_PI)); + drawArc(SmithChartArc(QPointF(1.0+dx, radius), radius, 0, 2*M_PI)); + drawArc(SmithChartArc(QPointF(1.0+dx, -radius), radius, 0, 2*M_PI)); } // draw custom constant parameter lines @@ -372,6 +243,10 @@ void TraceSmithChart::draw(QPainter &p) { if(isnan(now.y.real())) { break; } + + last = dataAddDx(last); + now = dataAddDx(now); + if (limitToEdge && (abs(last.y) > edgeReflection || abs(now.y) > edgeReflection)) { // outside of visible area continue; @@ -400,6 +275,8 @@ void TraceSmithChart::draw(QPainter &p) { continue; } auto coords = m->getData(); + coords = dataAddDx(coords); + if (limitToEdge && abs(coords) > edgeReflection) { // outside of visible area continue; @@ -415,7 +292,7 @@ void TraceSmithChart::draw(QPainter &p) { p.setOpacity(0.5); p.setBrush(Qt::white); p.setPen(Qt::white); - p.drawEllipse(-smithCoordMax, -smithCoordMax, 2*smithCoordMax, 2*smithCoordMax); + p.drawEllipse(-polarCoordMax, -polarCoordMax, 2*polarCoordMax, 2*polarCoordMax); auto font = p.font(); font.setPixelSize(20); p.setFont(font); @@ -459,8 +336,9 @@ bool TraceSmithChart::dropSupported(Trace *t) QString TraceSmithChart::mouseText(QPoint pos) { - auto data = pixelToData(pos); - if(abs(data) <= edgeReflection) { + auto dataDx = pixelToData(pos); + if(abs(dataDx) <= edgeReflection) { + auto data = complex(dataDx.real()-dx, dataDx.imag()); data = Z0 * (1.0 + data) / (1.0 - data); auto ret = Unit::ToString(data.real(), "", " ", 3); if(data.imag() >= 0) { @@ -473,64 +351,6 @@ QString TraceSmithChart::mouseText(QPoint pos) } } -void TraceSmithChart::updateContextMenu() -{ - contextmenu->clear(); - auto setup = new QAction("Setup...", contextmenu); - connect(setup, &QAction::triggered, this, &TraceSmithChart::axisSetupDialog); - contextmenu->addAction(setup); - - contextmenu->addSeparator(); - auto image = new QAction("Save image...", contextmenu); - contextmenu->addAction(image); - connect(image, &QAction::triggered, [=]() { - auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, QFileDialog::DontUseNativeDialog); - if(filename.isEmpty()) { - // aborted selection - return; - } - if(filename.endsWith(".png")) { - filename.chop(4); - } - filename += ".png"; - grab().save(filename); - }); - - auto createMarker = contextmenu->addAction("Add marker here"); - bool activeTraces = false; - for(auto t : traces) { - if(t.second) { - activeTraces = true; - break; - } - } - if(!activeTraces) { - createMarker->setEnabled(false); - } - connect(createMarker, &QAction::triggered, [=](){ - createMarkerAtPosition(contextmenuClickpoint); - }); - - contextmenu->addSection("Traces"); - // Populate context menu - for(auto t : orderedTraces()) { - if(!supported(t)) { - continue; - } - auto action = new QAction(t->name(), contextmenu); - action->setCheckable(true); - if(traces[t]) { - action->setChecked(true); - } - connect(action, &QAction::toggled, [=](bool active) { - enableTrace(t, active); - }); - contextmenu->addAction(action); - } - - finishContextMenu(); -} - bool TraceSmithChart::supported(Trace *t) { if(t->getReferenceImpedance() != Z0) { diff --git a/Software/PC_Application/Traces/tracesmithchart.h b/Software/PC_Application/Traces/tracesmithchart.h index c8f9276..0764124 100644 --- a/Software/PC_Application/Traces/tracesmithchart.h +++ b/Software/PC_Application/Traces/tracesmithchart.h @@ -1,7 +1,7 @@ #ifndef TRACESMITHCHART_H #define TRACESMITHCHART_H -#include "traceplot.h" +#include "tracepolar.h" #include #include @@ -98,7 +98,7 @@ private: TraceSmithChart &chart; }; -class TraceSmithChart : public TracePlot +class TraceSmithChart : public TracePolar { Q_OBJECT friend class SmithChartContantLineModel; @@ -110,34 +110,17 @@ public: virtual nlohmann::json toJSON() override; virtual void fromJSON(nlohmann::json j) override; - void wheelEvent(QWheelEvent *event) override; public slots: - void axisSetupDialog(); + virtual void axisSetupDialog() override; protected: - static constexpr double screenUsage = 0.9; - static constexpr double smithCoordMax = 4096; - - QPoint dataToPixel(std::complex d); - QPoint dataToPixel(Trace::Data d); - std::complex pixelToData(QPoint p); - QPoint markerToPixel(Marker *m) override; - double nearestTracePoint(Trace *t, QPoint pixel, double *distance = nullptr) override; - virtual bool markerVisible(double x); - - //void paintEvent(QPaintEvent *event) override; - virtual bool configureForTrace(Trace *t); - virtual void updateContextMenu() override; + virtual bool configureForTrace(Trace *t) override; bool supported(Trace *t) override; virtual void draw(QPainter& painter) override; virtual void traceDropped(Trace *t, QPoint position) override; virtual bool dropSupported(Trace *t) override; QString mouseText(QPoint pos) override; - bool limitToSpan; - bool limitToEdge; - double edgeReflection; // magnitude of reflection coefficient at the edge of the smith chart (zoom factor) double Z0; - QTransform transform; std::vector constantLines; };