diff --git a/Software/PC_Application/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI.pro
index 29ce2de..ccf04e4 100644
--- a/Software/PC_Application/LibreVNA-GUI.pro
+++ b/Software/PC_Application/LibreVNA-GUI.pro
@@ -287,6 +287,7 @@ FORMS += \
Traces/Math/timedomaingatingexplanationwidget.ui \
Traces/Math/timegatedialog.ui \
Traces/Math/timegateexplanationwidget.ui \
+ Traces/XYPlotConstantLineEditDialog.ui \
Traces/smithchartdialog.ui \
Traces/tracecsvexport.ui \
Traces/traceeditdialog.ui \
diff --git a/Software/PC_Application/Traces/XYPlotConstantLineEditDialog.ui b/Software/PC_Application/Traces/XYPlotConstantLineEditDialog.ui
new file mode 100644
index 0000000..fb7fbab
--- /dev/null
+++ b/Software/PC_Application/Traces/XYPlotConstantLineEditDialog.ui
@@ -0,0 +1,206 @@
+
+
+ XYPlotConstantLineEditDialog
+
+
+
+ 0
+ 0
+ 657
+ 288
+
+
+
+ Dialog
+
+
+ true
+
+
+ -
+
+
-
+
+
-
+
+
-
+
+
+ Name:
+
+
+
+ -
+
+
+ -
+
+
+ Color:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+ -
+
+
+ Axis:
+
+
+
+ -
+
+
+ -
+
+
+ Pass/Fail:
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+ -
+
+
+ Points
+
+
+
-
+
+
+ QAbstractItemView::SingleSelection
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+ :/icons/add.png:/icons/add.png
+
+
+
+ -
+
+
+
+
+
+
+ :/icons/remove.png:/icons/remove.png
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Ok
+
+
+
+
+
+
+
+ ColorPickerButton
+ QPushButton
+ CustomWidgets/colorpickerbutton.h
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ XYPlotConstantLineEditDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ XYPlotConstantLineEditDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/Software/PC_Application/Traces/tracexyplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp
index 226bc1e..332b239 100644
--- a/Software/PC_Application/Traces/tracexyplot.cpp
+++ b/Software/PC_Application/Traces/tracexyplot.cpp
@@ -8,6 +8,7 @@
#include "unit.h"
#include "preferences.h"
#include "appwindow.h"
+#include "ui_XYPlotConstantLineEditDialog.h"
#include
#include
@@ -32,6 +33,13 @@ TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent)
initializeTraceInfo();
}
+TraceXYPlot::~TraceXYPlot()
+{
+ for(auto l : constantLines) {
+ delete l;
+ }
+}
+
void TraceXYPlot::setYAxis(int axis, YAxis::Type type, bool log, bool autorange, double min, double max, double div)
{
if(yAxis[axis].getType() != type) {
@@ -119,6 +127,11 @@ nlohmann::json TraceXYPlot::toJSON()
j["YSecondary"] = jY;
}
}
+ nlohmann::json jlines;
+ for(auto l : constantLines) {
+ jlines.push_back(l->toJSON());
+ }
+ j["limitLines"] = jlines;
return j;
}
@@ -174,6 +187,13 @@ void TraceXYPlot::fromJSON(nlohmann::json j)
}
}
}
+ if(j.contains("limitLines")) {
+ for(auto jline : j["limitLines"]) {
+ auto line = new XYPlotConstantLine;
+ line->fromJSON(jline);
+ constantLines.push_back(line);
+ }
+ }
}
bool TraceXYPlot::isTDRtype(YAxis::Type type)
@@ -330,6 +350,8 @@ void TraceXYPlot::draw(QPainter &p)
{
auto pref = Preferences::getInstance();
+ bool limitPassing = true;
+
auto w = p.window();
auto pen = QPen(pref.Graphs.Color.axis, 0);
pen.setCosmetic(true);
@@ -465,6 +487,19 @@ void TraceXYPlot::draw(QPainter &p)
}
// draw line
p.drawLine(p1, p2);
+
+ // checking limits
+ for(auto limit : constantLines) {
+ if(i == 0 && limit->getAxis() != XYPlotConstantLine::Axis::Primary) {
+ continue;
+ }
+ if(i == 1 && limit->getAxis() != XYPlotConstantLine::Axis::Secondary) {
+ continue;
+ }
+ if(!limit->pass(now)) {
+ limitPassing = false;
+ }
+ }
}
if(i == 0 && nPoints > 0) {
// only draw markers on primary YAxis and if the trace has at least one point
@@ -502,6 +537,35 @@ void TraceXYPlot::draw(QPainter &p)
}
}
}
+ // plot constant lines
+ for(auto line : constantLines) {
+ // skip lines on wrong axis
+ if(i == 0 && line->getAxis() != XYPlotConstantLine::Axis::Primary) {
+ continue;
+ }
+ if(i == 1 && line->getAxis() != XYPlotConstantLine::Axis::Secondary) {
+ continue;
+ }
+ pen = QPen(line->getColor(), pref.Graphs.lineWidth);
+ pen.setCosmetic(true);
+ if(i == 1) {
+ pen.setStyle(Qt::DotLine);
+ } else {
+ pen.setStyle(Qt::SolidLine);
+ }
+ p.setPen(pen);
+ for(unsigned int j=1;jgetPoints().size();j++) {
+ // scale to plot coordinates
+ auto p1 = plotValueToPixel(line->getPoints()[j-1], i);
+ auto p2 = plotValueToPixel(line->getPoints()[j], i);
+ if(!plotRect.contains(p1) && !plotRect.contains(p2)) {
+ // completely out of frame
+ continue;
+ }
+ // draw line
+ p.drawLine(p1, p2);
+ }
+ }
p.setClipping(false);
}
@@ -583,6 +647,8 @@ void TraceXYPlot::draw(QPainter &p)
}
}
+ // TODO check limitPassing
+
if(dropPending) {
p.setOpacity(0.5);
p.setBrush(Qt::white);
@@ -967,3 +1033,204 @@ QString TraceXYPlot::mouseText(QPoint pos)
}
return ret;
}
+
+XYPlotConstantLine::XYPlotConstantLine()
+ : name("Name"),
+ color(Qt::red),
+ axis(Axis::Primary),
+ passFail(PassFail::DontCare),
+ points()
+{
+
+}
+
+QColor XYPlotConstantLine::getColor() const
+{
+ return color;
+}
+
+void XYPlotConstantLine::setColor(const QColor &value)
+{
+ color = value;
+}
+
+void XYPlotConstantLine::fromJSON(nlohmann::json j)
+{
+ name = QString::fromStdString(j.value("name", "Name"));
+ color = QColor(QString::fromStdString(j.value("color", "red")));
+ axis = AxisFromString(QString::fromStdString(j.value("axis", AxisToString(Axis::Primary).toStdString())));
+ if(axis == Axis::Last) {
+ axis = Axis::Primary;
+ }
+ passFail = PassFailFromString(QString::fromStdString(j.value("passfail", PassFailToString(PassFail::DontCare).toStdString())));
+ if(passFail == PassFail::Last) {
+ passFail = PassFail::DontCare;
+ }
+ points.clear();
+ if(j.contains("points")) {
+ for(auto jp : j["points"]) {
+ QPointF p;
+ p.setX(jp.value("x", 0.0));
+ p.setY(jp.value("y", 0.0));
+ points.push_back(p);
+ }
+ }
+}
+
+nlohmann::json XYPlotConstantLine::toJSON()
+{
+ nlohmann::json j;
+ j["name"] = name.toStdString();
+ j["color"] = color.name().toStdString();
+ j["axis"] = AxisToString(axis).toStdString();
+ j["passfail"] = PassFailToString(passFail).toStdString();
+ nlohmann::json jpoints;
+ for(auto p : points) {
+ nlohmann::json jp;
+ jp["x"] = p.x();
+ jp["y"] = p.y();
+ jpoints.push_back(jp);
+ }
+ j["points"] = jpoints;
+ return j;
+}
+
+bool XYPlotConstantLine::pass(QPointF testPoint)
+{
+ if(passFail == PassFail::DontCare) {
+ // always passes
+ return true;
+ }
+ if(points.size() < 2) {
+ // no lines, always passes
+ return true;
+ }
+ if(testPoint.x() < points.front().x() || testPoint.x() > points.back().x()) {
+ // out of range, always passes
+ return true;
+ }
+ auto p = lower_bound(points.begin(), points.end(), testPoint.x(), [](QPointF p, double x) -> bool {
+ return p.x() < x;
+ });
+ double compareY = 0.0;
+ if(p->x() == testPoint.x()) {
+ // Exact match
+ compareY = p->y();
+ } else {
+ // need to interpolate
+ auto high = p;
+ p--;
+ auto low = p;
+ double alpha = (testPoint.x() - low->x()) / (high->x() - low->x());
+ compareY = low->y() * (1 - alpha) + high->y() * alpha;
+ }
+ if (compareY < testPoint.y() && passFail == PassFail::HighLimit) {
+ // high limit exceeded
+ return false;
+ }
+ if (compareY > testPoint.y() && passFail == PassFail::LowLimit) {
+ // low limit exceeded
+ return false;
+ }
+ return true;
+}
+
+QString XYPlotConstantLine::AxisToString(Axis axis)
+{
+ switch(axis) {
+ case Axis::Primary: return "Primary";
+ case Axis::Secondary: return "Secondary";
+ default: return "Invalid";
+ }
+}
+
+XYPlotConstantLine::Axis XYPlotConstantLine::AxisFromString(QString s)
+{
+ for(unsigned int i=0;i<(unsigned int)Axis::Last;i++) {
+ if(AxisToString((Axis) i) == s) {
+ return (Axis) i;
+ }
+ }
+ return Axis::Last;
+}
+
+QString XYPlotConstantLine::PassFailToString(PassFail pf)
+{
+ switch(pf) {
+ case PassFail::DontCare: return "Dont Care";
+ case PassFail::HighLimit: return "High limit";
+ case PassFail::LowLimit: return "Low limit";
+ default: return "Invalid";
+ }
+}
+
+XYPlotConstantLine::PassFail XYPlotConstantLine::PassFailFromString(QString s)
+{
+ for(unsigned int i=0;i<(unsigned int)PassFail::Last;i++) {
+ if(PassFailToString((PassFail) i) == s) {
+ return (PassFail) i;
+ }
+ }
+ return PassFail::Last;
+
+}
+
+void XYPlotConstantLine::editDialog(QString xUnit, QString yUnitPrimary, QString yUnitSecondary)
+{
+ auto d = new QDialog();
+ auto ui = new Ui_XYPlotConstantLineEditDialog;
+ ui->setupUi(d);
+
+ ui->name->setText(name);
+ ui->color->setColor(color);
+
+ for(unsigned int i=0;i<(unsigned int)Axis::Last;i++) {
+ ui->axis->addItem(AxisToString((Axis) i));
+ }
+ ui->axis->setCurrentIndex((int) axis);
+
+ for(unsigned int i=0;i<(unsigned int)PassFail::Last;i++) {
+ ui->passFail->addItem(PassFailToString((PassFail) i));
+ }
+ ui->passFail->setCurrentIndex((int) passFail);
+
+ connect(ui->name, &QLineEdit::textChanged, [=](){
+ name = ui->name->text();
+ });
+ connect(ui->color, &ColorPickerButton::colorChanged, [=](){
+ color = ui->color->getColor();
+ });
+ connect(ui->axis, qOverload(&QComboBox::currentIndexChanged), [=](){
+ axis = (Axis) ui->axis->currentIndex();
+ // TODO apply unit change
+ });
+ connect(ui->passFail, qOverload(&QComboBox::currentIndexChanged), [=](){
+ passFail = (PassFail) ui->passFail->currentIndex();
+ });
+
+ // TODO handle adding/removing of points
+
+ connect(d, &QDialog::finished, this, &XYPlotConstantLine::editingFinished);
+
+ if(AppWindow::showGUI()) {
+ d->show();
+ }
+}
+
+QString XYPlotConstantLine::getDescription()
+{
+ QString ret;
+ ret += name;
+ ret += ", " + QString::number(points.size()) + " points, limit: "+PassFailToString(passFail);
+ return ret;
+}
+
+XYPlotConstantLine::Axis XYPlotConstantLine::getAxis() const
+{
+ return axis;
+}
+
+const std::vector &XYPlotConstantLine::getPoints() const
+{
+ return points;
+}
diff --git a/Software/PC_Application/Traces/tracexyplot.h b/Software/PC_Application/Traces/tracexyplot.h
index 1491184..422f968 100644
--- a/Software/PC_Application/Traces/tracexyplot.h
+++ b/Software/PC_Application/Traces/tracexyplot.h
@@ -6,12 +6,60 @@
#include
+class XYPlotConstantLine : public QObject, public Savable
+{
+ Q_OBJECT
+public:
+ enum class Axis {
+ Primary,
+ Secondary,
+ Last,
+ };
+ enum class PassFail {
+ DontCare,
+ HighLimit,
+ LowLimit,
+ Last,
+ };
+
+ XYPlotConstantLine();
+ QColor getColor() const;
+ void setColor(const QColor &value);
+
+ void fromJSON(nlohmann::json j) override;
+ nlohmann::json toJSON() override;
+
+ bool pass(QPointF testPoint);
+
+ static QString AxisToString(Axis axis);
+ static Axis AxisFromString(QString s);
+
+ static QString PassFailToString(PassFail pf);
+ static PassFail PassFailFromString(QString s);
+
+ void editDialog(QString xUnit, QString yUnitPrimary, QString yUnitSecondary);
+ QString getDescription();
+ Axis getAxis() const;
+
+ const std::vector &getPoints() const;
+
+signals:
+ void editingFinished();
+private:
+ QString name;
+ QColor color;
+ Axis axis;
+ PassFail passFail;
+ std::vector points;
+};
+
class TraceXYPlot : public TracePlot
{
friend class XYplotAxisDialog;
Q_OBJECT
public:
TraceXYPlot(TraceModel &model, QWidget *parent = nullptr);
+ ~TraceXYPlot();
enum class XAxisMode {
UseSpan,
@@ -69,6 +117,8 @@ private:
XAxisMode xAxisMode;
int plotAreaLeft, plotAreaWidth, plotAreaBottom, plotAreaTop;
+
+ std::vector constantLines;
};
#endif // TRACEXYPLOT_H
diff --git a/Software/PC_Application/Traces/xyplotaxisdialog.cpp b/Software/PC_Application/Traces/xyplotaxisdialog.cpp
index a543164..bd8e647 100644
--- a/Software/PC_Application/Traces/xyplotaxisdialog.cpp
+++ b/Software/PC_Application/Traces/xyplotaxisdialog.cpp
@@ -156,6 +156,47 @@ XYplotAxisDialog::XYplotAxisDialog(TraceXYPlot *plot) :
ui->Xmin->setValueQuiet(plot->xAxis.getRangeMin());
ui->Xmax->setValueQuiet(plot->xAxis.getRangeMax());
ui->Xdivs->setValueQuiet(plot->xAxis.getRangeDiv());
+
+ // Constant line list handling
+ auto editLine = [&](XYPlotConstantLine *line) {
+ line->editDialog(XAxis::Unit((XAxis::Type) ui->XType->currentIndex()),
+ YAxis::Unit((YAxis::Type) ui->Y1type->currentIndex()),
+ YAxis::Unit((YAxis::Type) ui->Y2type->currentIndex()));
+ };
+
+ for(auto l : plot->constantLines) {
+ ui->lineList->addItem(l->getDescription());
+ }
+ if(plot->constantLines.size() > 0) {
+ ui->removeLine->setEnabled(true);
+ }
+ connect(ui->addLine, &QPushButton::clicked, [=](){
+ auto line = new XYPlotConstantLine();
+ plot->constantLines.push_back(line);
+ auto item = new QListWidgetItem(line->getDescription());
+ ui->lineList->addItem(item);
+ ui->lineList->setCurrentItem(item);
+ ui->removeLine->setEnabled(true);
+ editLine(line);
+ connect(line, &XYPlotConstantLine::editingFinished, [=](){
+ item->setText(line->getDescription());
+ });
+ });
+ connect(ui->removeLine, &QPushButton::clicked, [=](){
+ auto index = ui->lineList->currentRow();
+ delete ui->lineList->takeItem(index);
+ delete plot->constantLines[index];
+ plot->constantLines.erase(plot->constantLines.begin() + index);
+ if(plot->constantLines.size() == 0) {
+ ui->removeLine->setEnabled(false);
+ }
+ });
+
+ connect(ui->lineList, &QListWidget::itemDoubleClicked, [=](QListWidgetItem *item){
+ ui->lineList->setCurrentItem(item);
+ editLine(plot->constantLines[ui->lineList->currentRow()]);
+ });
+
}
XYplotAxisDialog::~XYplotAxisDialog()
diff --git a/Software/PC_Application/Traces/xyplotaxisdialog.ui b/Software/PC_Application/Traces/xyplotaxisdialog.ui
index 7e56d49..76147d5 100644
--- a/Software/PC_Application/Traces/xyplotaxisdialog.ui
+++ b/Software/PC_Application/Traces/xyplotaxisdialog.ui
@@ -9,8 +9,8 @@
0
0
- 773
- 282
+ 814
+ 439
@@ -19,7 +19,7 @@
true
-
+
-
-
@@ -431,6 +431,82 @@
+ -
+
+
+ Constant Lines
+
+
+
-
+
+
+ QAbstractItemView::SingleSelection
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Add
+
+
+
+
+
+
+ :/icons/add.png:/icons/add.png
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+ Delete
+
+
+
+
+
+
+ :/icons/remove.png:/icons/remove.png
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 18
+ 186
+
+
+
+
+
+
+
+
+
-
@@ -450,7 +526,9 @@
CustomWidgets/siunitedit.h
-
+
+
+
buttonBox
@@ -486,8 +564,8 @@
-
+