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 @@ - +