widgets/charts: implement base polar chart class
This commit is contained in:
parent
5b26a4a9c1
commit
0f22bd1287
@ -102,6 +102,7 @@ HEADERS += \
|
|||||||
Traces/tracewaterfall.h \
|
Traces/tracewaterfall.h \
|
||||||
Traces/tracewidget.h \
|
Traces/tracewidget.h \
|
||||||
Traces/tracexyplot.h \
|
Traces/tracexyplot.h \
|
||||||
|
Traces/tracepolar.h \
|
||||||
Traces/waterfallaxisdialog.h \
|
Traces/waterfallaxisdialog.h \
|
||||||
Traces/xyplotaxisdialog.h \
|
Traces/xyplotaxisdialog.h \
|
||||||
Traces/tracepolarchart.h \
|
Traces/tracepolarchart.h \
|
||||||
@ -228,6 +229,7 @@ SOURCES += \
|
|||||||
Traces/tracewaterfall.cpp \
|
Traces/tracewaterfall.cpp \
|
||||||
Traces/tracewidget.cpp \
|
Traces/tracewidget.cpp \
|
||||||
Traces/tracexyplot.cpp \
|
Traces/tracexyplot.cpp \
|
||||||
|
Traces/tracepolar.cpp \
|
||||||
Traces/waterfallaxisdialog.cpp \
|
Traces/waterfallaxisdialog.cpp \
|
||||||
Traces/xyplotaxisdialog.cpp \
|
Traces/xyplotaxisdialog.cpp \
|
||||||
Util/util.cpp \
|
Util/util.cpp \
|
||||||
|
@ -117,6 +117,16 @@
|
|||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="SIUnitEdit" name="zoomReflection"/>
|
<widget class="SIUnitEdit" name="zoomReflection"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Offset real axis</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="SIUnitEdit" name="offsetRealAxis"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
312
Software/PC_Application/Traces/tracepolar.cpp
Normal file
312
Software/PC_Application/Traces/tracepolar.cpp
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
#include "tracepolar.h"
|
||||||
|
|
||||||
|
#include "Marker/marker.h"
|
||||||
|
#include "Util/util.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
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<double> 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<double> TracePolar::dataAddDx(std::complex<double> d)
|
||||||
|
{
|
||||||
|
auto dataShift = complex<double>(dx, 0);
|
||||||
|
d = d + dataShift;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace::Data TracePolar::dataAddDx(Trace::Data d)
|
||||||
|
{
|
||||||
|
d.y = dataAddDx(d.y);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::complex<double> TracePolar::pixelToData(QPoint p)
|
||||||
|
{
|
||||||
|
auto data = transform.inverted().map(QPointF(p));
|
||||||
|
return complex<double>(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<double>::max();
|
||||||
|
double closestXpos = 0;
|
||||||
|
unsigned int closestIndex = 0;
|
||||||
|
auto samples = t->size();
|
||||||
|
for(unsigned int i=0;i<samples;i++) {
|
||||||
|
auto data = t->sample(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
53
Software/PC_Application/Traces/tracepolar.h
Normal file
53
Software/PC_Application/Traces/tracepolar.h
Normal file
@ -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<double> dataAddDx(std::complex<double> d);
|
||||||
|
virtual Trace::Data dataAddDx(Trace::Data d);
|
||||||
|
|
||||||
|
QPoint dataToPixel(std::complex<double> d);
|
||||||
|
QPoint dataToPixel(Trace::Data d);
|
||||||
|
std::complex<double> 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
|
@ -2,10 +2,7 @@
|
|||||||
|
|
||||||
#include "ui_polarchartdialog.h"
|
#include "ui_polarchartdialog.h"
|
||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
#include "tracesmithchart.h"
|
|
||||||
#include "unit.h"
|
#include "unit.h"
|
||||||
#include "Marker/marker.h"
|
|
||||||
#include "Util/util.h"
|
|
||||||
#include "appwindow.h"
|
#include "appwindow.h"
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
@ -14,29 +11,8 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
TracePolarChart::TracePolarChart(TraceModel &model, QWidget *parent)
|
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()
|
void TracePolarChart::axisSetupDialog()
|
||||||
@ -82,35 +58,6 @@ void TracePolarChart::axisSetupDialog()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPoint TracePolarChart::dataToPixel(std::complex<double> 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<double> TracePolarChart::dataAddDx(std::complex<double> d)
|
|
||||||
{
|
|
||||||
auto dataShift = complex<double>(dx, 0);
|
|
||||||
d = d + dataShift;
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace::Data TracePolarChart::dataAddDx(Trace::Data d)
|
|
||||||
{
|
|
||||||
d.y = dataAddDx(d.y);
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::complex<double> TracePolarChart::pixelToData(QPoint p)
|
|
||||||
{
|
|
||||||
auto data = transform.inverted().map(QPointF(p));
|
|
||||||
return complex<double>(data.x() / polarCoordMax * edgeReflection, -data.y() / polarCoordMax * edgeReflection);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TracePolarChart::draw(QPainter &p) {
|
void TracePolarChart::draw(QPainter &p) {
|
||||||
auto pref = Preferences::getInstance();
|
auto pref = Preferences::getInstance();
|
||||||
|
|
||||||
@ -124,7 +71,7 @@ void TracePolarChart::draw(QPainter &p) {
|
|||||||
transform = p.transform();
|
transform = p.transform();
|
||||||
p.restore();
|
p.restore();
|
||||||
|
|
||||||
auto drawArc = [&](SmithChartArc a) {
|
auto drawArc = [&](PolarArc a) {
|
||||||
a.constrainToCircle(QPointF(0,0), edgeReflection);
|
a.constrainToCircle(QPointF(0,0), edgeReflection);
|
||||||
auto topleft = dataToPixel(complex<double>(a.center.x() - a.radius, a.center.y() - a.radius));
|
auto topleft = dataToPixel(complex<double>(a.center.x() - a.radius, a.center.y() - a.radius));
|
||||||
auto bottomright = dataToPixel(complex<double>(a.center.x() + a.radius, a.center.y() + a.radius));
|
auto bottomright = dataToPixel(complex<double>(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);
|
auto pen = QPen(pref.Graphs.Color.axis);
|
||||||
pen.setCosmetic(true);
|
pen.setCosmetic(true);
|
||||||
p.setPen(pen);
|
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;
|
constexpr int Circles = 6;
|
||||||
pen = QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine);
|
pen = QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine);
|
||||||
@ -145,10 +92,10 @@ void TracePolarChart::draw(QPainter &p) {
|
|||||||
p.setPen(pen);
|
p.setPen(pen);
|
||||||
for(int i=1;i<Circles;i++) {
|
for(int i=1;i<Circles;i++) {
|
||||||
auto radius = (double) i / Circles;
|
auto radius = (double) i / Circles;
|
||||||
drawArc(SmithChartArc(QPointF(0.0 + dx,0), radius, 0, 2*M_PI));
|
drawArc(PolarArc(QPointF(0.0 + dx,0), radius, 0, 2*M_PI));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto constraintLineToCircle = [&](PolarChartCircle cir) {
|
auto constraintLineToCircle = [&](PolarArc cir) { // PolarArc
|
||||||
if ( (cir.spanAngle == 90 )&& (dx != 0.0)) {
|
if ( (cir.spanAngle == 90 )&& (dx != 0.0)) {
|
||||||
auto angle = acos(dx/cir.radius);
|
auto angle = acos(dx/cir.radius);
|
||||||
auto p1 = complex<double>(dx, cir.center.y() + cir.radius*sin(angle));
|
auto p1 = complex<double>(dx, cir.center.y() + cir.radius*sin(angle));
|
||||||
@ -180,7 +127,7 @@ void TracePolarChart::draw(QPainter &p) {
|
|||||||
constexpr int Lines = 6;
|
constexpr int Lines = 6;
|
||||||
for(int i=0;i<Lines;i++) {
|
for(int i=0;i<Lines;i++) {
|
||||||
auto angle = (double) i * 30;
|
auto angle = (double) i * 30;
|
||||||
constraintLineToCircle(PolarChartCircle(QPointF(0,0), edgeReflection, 0, angle));
|
constraintLineToCircle(PolarArc(QPointF(0,0), edgeReflection, 0, angle)); // PolarArc
|
||||||
}
|
}
|
||||||
|
|
||||||
for(auto t : traces) {
|
for(auto t : traces) {
|
||||||
@ -214,7 +161,7 @@ void TracePolarChart::draw(QPainter &p) {
|
|||||||
// outside of visible area
|
// outside of visible area
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// scale to size of smith diagram
|
// scale to size of diagram
|
||||||
auto p1 = dataToPixel(last);
|
auto p1 = dataToPixel(last);
|
||||||
auto p2 = dataToPixel(now);
|
auto p2 = dataToPixel(now);
|
||||||
// draw line
|
// draw line
|
||||||
@ -266,95 +213,6 @@ void TracePolarChart::draw(QPainter &p) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TracePolarChart::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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<double>::max();
|
|
||||||
double closestXpos = 0;
|
|
||||||
unsigned int closestIndex = 0;
|
|
||||||
auto samples = t->size();
|
|
||||||
for(unsigned int i=0;i<samples;i++) {
|
|
||||||
auto data = t->sample(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)
|
bool TracePolarChart::dropSupported(Trace *t)
|
||||||
{
|
{
|
||||||
if(!t->isReflection()) {
|
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)
|
bool TracePolarChart::supported(Trace *t)
|
||||||
{
|
{
|
||||||
return dropSupported(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)
|
QString TracePolarChart::mouseText(QPoint pos)
|
||||||
{
|
{
|
||||||
auto dataDx = pixelToData(pos);
|
auto dataDx = pixelToData(pos);
|
||||||
@ -480,12 +254,3 @@ QString TracePolarChart::mouseText(QPoint pos)
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PolarChartCircle::PolarChartCircle(QPointF center, double radius, double startAngle, double spanAngle)
|
|
||||||
: center(center),
|
|
||||||
radius(radius),
|
|
||||||
startAngle(startAngle),
|
|
||||||
spanAngle(spanAngle)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,19 +1,9 @@
|
|||||||
#ifndef TRACEPOLARCHART_H
|
#ifndef TRACEPOLARCHART_H
|
||||||
#define TRACEPOLARCHART_H
|
#define TRACEPOLARCHART_H
|
||||||
|
|
||||||
#include "traceplot.h"
|
#include "tracepolar.h"
|
||||||
|
|
||||||
class PolarChartCircle
|
class TracePolarChart : public TracePolar
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@ -21,36 +11,14 @@ public:
|
|||||||
|
|
||||||
virtual Type getType() override { return Type::PolarChart;};
|
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:
|
public slots:
|
||||||
void axisSetupDialog();
|
virtual void axisSetupDialog() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr double polarCoordMax = 4096;
|
|
||||||
|
|
||||||
std::complex<double> dataAddDx(std::complex<double> d);
|
|
||||||
Trace::Data dataAddDx(Trace::Data d);
|
|
||||||
|
|
||||||
QPoint dataToPixel(std::complex<double> d);
|
|
||||||
QPoint dataToPixel(Trace::Data d);
|
|
||||||
std::complex<double> 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;
|
bool supported(Trace *t) override;
|
||||||
virtual void draw(QPainter& painter) override;
|
virtual void draw(QPainter& painter) override;
|
||||||
virtual bool dropSupported(Trace *t) override;
|
virtual bool dropSupported(Trace *t) override;
|
||||||
QString mouseText(QPoint pos) override;
|
QString mouseText(QPoint pos) override;
|
||||||
bool limitToSpan;
|
|
||||||
bool limitToEdge;
|
|
||||||
double edgeReflection;
|
|
||||||
double dx;
|
|
||||||
QTransform transform;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TRACEPOLARCHART_H
|
#endif // TRACEPOLARCHART_H
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
#include "tracesmithchart.h"
|
#include "tracesmithchart.h"
|
||||||
|
|
||||||
#include "Marker/marker.h"
|
|
||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
#include "ui_smithchartdialog.h"
|
#include "ui_smithchartdialog.h"
|
||||||
#include "unit.h"
|
#include "unit.h"
|
||||||
#include "QFileDialog"
|
#include "QFileDialog"
|
||||||
#include "Util/util.h"
|
|
||||||
#include "appwindow.h"
|
#include "appwindow.h"
|
||||||
#include "CustomWidgets/informationbox.h"
|
#include "CustomWidgets/informationbox.h"
|
||||||
|
|
||||||
@ -18,29 +16,16 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
TraceSmithChart::TraceSmithChart(TraceModel &model, QWidget *parent)
|
TraceSmithChart::TraceSmithChart(TraceModel &model, QWidget *parent)
|
||||||
: TracePlot(model, parent)
|
: TracePolar(model, parent)
|
||||||
{
|
{
|
||||||
limitToSpan = true;
|
|
||||||
limitToEdge = true;
|
|
||||||
edgeReflection = 1.0;
|
|
||||||
Z0 = 50.0;
|
Z0 = 50.0;
|
||||||
initializeTraceInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json TraceSmithChart::toJSON()
|
nlohmann::json TraceSmithChart::toJSON()
|
||||||
{
|
{
|
||||||
nlohmann::json j;
|
nlohmann::json j;
|
||||||
j["limit_to_span"] = limitToSpan;
|
j = TracePolar::toJSON();
|
||||||
j["limit_to_edge"] = limitToEdge;
|
|
||||||
j["edge_reflection"] = edgeReflection;
|
|
||||||
j["Z0"] = Z0;
|
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;
|
nlohmann::json jlines;
|
||||||
for(auto line : constantLines) {
|
for(auto line : constantLines) {
|
||||||
jlines.push_back(line.toJSON());
|
jlines.push_back(line.toJSON());
|
||||||
@ -51,24 +36,8 @@ nlohmann::json TraceSmithChart::toJSON()
|
|||||||
|
|
||||||
void TraceSmithChart::fromJSON(nlohmann::json j)
|
void TraceSmithChart::fromJSON(nlohmann::json j)
|
||||||
{
|
{
|
||||||
limitToSpan = j.value("limit_to_span", true);
|
TracePolar::fromJSON(j);
|
||||||
limitToEdge = j.value("limit_to_edge", false);
|
|
||||||
edgeReflection = j.value("edge_reflection", 1.0);
|
|
||||||
Z0 = j.value("Z0", 50.0);
|
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")) {
|
if(j.contains("constantLines")) {
|
||||||
for(auto jline : j["constantLines"]) {
|
for(auto jline : j["constantLines"]) {
|
||||||
SmithChartConstantLine line;
|
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()
|
void TraceSmithChart::axisSetupDialog()
|
||||||
{
|
{
|
||||||
auto dialog = new QDialog();
|
auto dialog = new QDialog();
|
||||||
@ -110,6 +66,7 @@ void TraceSmithChart::axisSetupDialog()
|
|||||||
ui->zoomFactor->setPrecision(3);
|
ui->zoomFactor->setPrecision(3);
|
||||||
ui->zoomReflection->setValue(edgeReflection);
|
ui->zoomReflection->setValue(edgeReflection);
|
||||||
ui->zoomFactor->setValue(1.0/edgeReflection);
|
ui->zoomFactor->setValue(1.0/edgeReflection);
|
||||||
|
ui->offsetRealAxis->setValue(dx);
|
||||||
|
|
||||||
ui->impedance->setUnit("Ω");
|
ui->impedance->setUnit("Ω");
|
||||||
ui->impedance->setPrecision(3);
|
ui->impedance->setPrecision(3);
|
||||||
@ -133,6 +90,9 @@ void TraceSmithChart::axisSetupDialog()
|
|||||||
edgeReflection = ui->zoomReflection->value();
|
edgeReflection = ui->zoomReflection->value();
|
||||||
ui->zoomFactor->setValueQuiet(1.0 / edgeReflection);
|
ui->zoomFactor->setValueQuiet(1.0 / edgeReflection);
|
||||||
});
|
});
|
||||||
|
connect(ui->offsetRealAxis, &SIUnitEdit::valueChanged, [=](){
|
||||||
|
dx = ui->offsetRealAxis->value();
|
||||||
|
});
|
||||||
connect(ui->impedance, &SIUnitEdit::valueChanged, [=](){
|
connect(ui->impedance, &SIUnitEdit::valueChanged, [=](){
|
||||||
Z0 = ui->impedance->value();
|
Z0 = ui->impedance->value();
|
||||||
for(auto t : traces) {
|
for(auto t : traces) {
|
||||||
@ -189,95 +149,6 @@ void TraceSmithChart::axisSetupDialog()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPoint TraceSmithChart::dataToPixel(std::complex<double> 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<double> TraceSmithChart::pixelToData(QPoint p)
|
|
||||||
{
|
|
||||||
auto data = transform.inverted().map(QPointF(p));
|
|
||||||
return complex<double>(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<double>::max();
|
|
||||||
double closestXpos = 0;
|
|
||||||
unsigned int closestIndex = 0;
|
|
||||||
auto samples = t->size();
|
|
||||||
for(unsigned int i=0;i<samples;i++) {
|
|
||||||
auto data = t->sample(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)
|
bool TraceSmithChart::configureForTrace(Trace *t)
|
||||||
{
|
{
|
||||||
if(dropSupported(t)) {
|
if(dropSupported(t)) {
|
||||||
@ -299,7 +170,7 @@ void TraceSmithChart::draw(QPainter &p) {
|
|||||||
auto w = p.window();
|
auto w = p.window();
|
||||||
p.save();
|
p.save();
|
||||||
p.translate(w.width()/2, w.height()/2);
|
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);
|
p.scale(scale, scale);
|
||||||
|
|
||||||
transform = p.transform();
|
transform = p.transform();
|
||||||
@ -326,8 +197,8 @@ void TraceSmithChart::draw(QPainter &p) {
|
|||||||
p.setPen(pen);
|
p.setPen(pen);
|
||||||
for(int i=1;i<Circles * 2;i++) {
|
for(int i=1;i<Circles * 2;i++) {
|
||||||
auto radius = (double) i / Circles;
|
auto radius = (double) i / Circles;
|
||||||
drawArc(SmithChartArc(QPointF(1.0 - radius, 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, 0.0), radius, 0, 2*M_PI));
|
drawArc(SmithChartArc(QPointF(1.0 + radius+dx, 0.0), radius, 0, 2*M_PI));
|
||||||
}
|
}
|
||||||
|
|
||||||
p.drawLine(dataToPixel(complex<double>(edgeReflection,0)),dataToPixel(complex<double>(-edgeReflection,0)));
|
p.drawLine(dataToPixel(complex<double>(edgeReflection,0)),dataToPixel(complex<double>(-edgeReflection,0)));
|
||||||
@ -335,8 +206,8 @@ void TraceSmithChart::draw(QPainter &p) {
|
|||||||
for(auto z : impedanceLines) {
|
for(auto z : impedanceLines) {
|
||||||
z /= Z0;
|
z /= Z0;
|
||||||
auto radius = 1.0/z;
|
auto radius = 1.0/z;
|
||||||
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, -radius), radius, 0, 2*M_PI));
|
drawArc(SmithChartArc(QPointF(1.0+dx, -radius), radius, 0, 2*M_PI));
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw custom constant parameter lines
|
// draw custom constant parameter lines
|
||||||
@ -372,6 +243,10 @@ void TraceSmithChart::draw(QPainter &p) {
|
|||||||
if(isnan(now.y.real())) {
|
if(isnan(now.y.real())) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last = dataAddDx(last);
|
||||||
|
now = dataAddDx(now);
|
||||||
|
|
||||||
if (limitToEdge && (abs(last.y) > edgeReflection || abs(now.y) > edgeReflection)) {
|
if (limitToEdge && (abs(last.y) > edgeReflection || abs(now.y) > edgeReflection)) {
|
||||||
// outside of visible area
|
// outside of visible area
|
||||||
continue;
|
continue;
|
||||||
@ -400,6 +275,8 @@ void TraceSmithChart::draw(QPainter &p) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto coords = m->getData();
|
auto coords = m->getData();
|
||||||
|
coords = dataAddDx(coords);
|
||||||
|
|
||||||
if (limitToEdge && abs(coords) > edgeReflection) {
|
if (limitToEdge && abs(coords) > edgeReflection) {
|
||||||
// outside of visible area
|
// outside of visible area
|
||||||
continue;
|
continue;
|
||||||
@ -415,7 +292,7 @@ void TraceSmithChart::draw(QPainter &p) {
|
|||||||
p.setOpacity(0.5);
|
p.setOpacity(0.5);
|
||||||
p.setBrush(Qt::white);
|
p.setBrush(Qt::white);
|
||||||
p.setPen(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();
|
auto font = p.font();
|
||||||
font.setPixelSize(20);
|
font.setPixelSize(20);
|
||||||
p.setFont(font);
|
p.setFont(font);
|
||||||
@ -459,8 +336,9 @@ bool TraceSmithChart::dropSupported(Trace *t)
|
|||||||
|
|
||||||
QString TraceSmithChart::mouseText(QPoint pos)
|
QString TraceSmithChart::mouseText(QPoint pos)
|
||||||
{
|
{
|
||||||
auto data = pixelToData(pos);
|
auto dataDx = pixelToData(pos);
|
||||||
if(abs(data) <= edgeReflection) {
|
if(abs(dataDx) <= edgeReflection) {
|
||||||
|
auto data = complex<double>(dataDx.real()-dx, dataDx.imag());
|
||||||
data = Z0 * (1.0 + data) / (1.0 - data);
|
data = Z0 * (1.0 + data) / (1.0 - data);
|
||||||
auto ret = Unit::ToString(data.real(), "", " ", 3);
|
auto ret = Unit::ToString(data.real(), "", " ", 3);
|
||||||
if(data.imag() >= 0) {
|
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)
|
bool TraceSmithChart::supported(Trace *t)
|
||||||
{
|
{
|
||||||
if(t->getReferenceImpedance() != Z0) {
|
if(t->getReferenceImpedance() != Z0) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#ifndef TRACESMITHCHART_H
|
#ifndef TRACESMITHCHART_H
|
||||||
#define TRACESMITHCHART_H
|
#define TRACESMITHCHART_H
|
||||||
|
|
||||||
#include "traceplot.h"
|
#include "tracepolar.h"
|
||||||
|
|
||||||
#include <QPen>
|
#include <QPen>
|
||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
@ -98,7 +98,7 @@ private:
|
|||||||
TraceSmithChart &chart;
|
TraceSmithChart &chart;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TraceSmithChart : public TracePlot
|
class TraceSmithChart : public TracePolar
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
friend class SmithChartContantLineModel;
|
friend class SmithChartContantLineModel;
|
||||||
@ -110,34 +110,17 @@ public:
|
|||||||
virtual nlohmann::json toJSON() override;
|
virtual nlohmann::json toJSON() override;
|
||||||
virtual void fromJSON(nlohmann::json j) override;
|
virtual void fromJSON(nlohmann::json j) override;
|
||||||
|
|
||||||
void wheelEvent(QWheelEvent *event) override;
|
|
||||||
public slots:
|
public slots:
|
||||||
void axisSetupDialog();
|
virtual void axisSetupDialog() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static constexpr double screenUsage = 0.9;
|
virtual bool configureForTrace(Trace *t) override;
|
||||||
static constexpr double smithCoordMax = 4096;
|
|
||||||
|
|
||||||
QPoint dataToPixel(std::complex<double> d);
|
|
||||||
QPoint dataToPixel(Trace::Data d);
|
|
||||||
std::complex<double> 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;
|
|
||||||
bool supported(Trace *t) override;
|
bool supported(Trace *t) override;
|
||||||
virtual void draw(QPainter& painter) override;
|
virtual void draw(QPainter& painter) override;
|
||||||
virtual void traceDropped(Trace *t, QPoint position) override;
|
virtual void traceDropped(Trace *t, QPoint position) override;
|
||||||
virtual bool dropSupported(Trace *t) override;
|
virtual bool dropSupported(Trace *t) override;
|
||||||
QString mouseText(QPoint pos) 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;
|
double Z0;
|
||||||
QTransform transform;
|
|
||||||
std::vector<SmithChartConstantLine> constantLines;
|
std::vector<SmithChartConstantLine> constantLines;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user