From cd785b3de23a0ec5ccafed72317c22cbfa8c57b1 Mon Sep 17 00:00:00 2001 From: pixhawk Date: Wed, 18 Aug 2010 08:56:14 +0200 Subject: [PATCH] Almost finished flexible data view, only minor improvements needed --- src/ui/QGCDataPlot2D.cc | 147 ++++++++++++++++++++++++++---------- src/ui/QGCDataPlot2D.ui | 81 +++++++++++++++++--- src/ui/linechart/IncrementalPlot.cc | 81 ++++++++++++++++++-- src/ui/linechart/IncrementalPlot.h | 19 +++-- 4 files changed, 267 insertions(+), 61 deletions(-) diff --git a/src/ui/QGCDataPlot2D.cc b/src/ui/QGCDataPlot2D.cc index 406ae1c..303d5a1 100644 --- a/src/ui/QGCDataPlot2D.cc +++ b/src/ui/QGCDataPlot2D.cc @@ -34,6 +34,8 @@ QGCDataPlot2D::QGCDataPlot2D(QWidget *parent) : connect(ui->reloadButton, SIGNAL(clicked()), this, SLOT(reloadFile())); connect(ui->savePlotButton, SIGNAL(clicked()), this, SLOT(savePlot())); connect(ui->printButton, SIGNAL(clicked()), this, SLOT(print())); + connect(ui->legendCheckBox, SIGNAL(clicked(bool)), plot, SLOT(showLegend(bool))); + connect(ui->style, SIGNAL(currentIndexChanged(QString)), plot, SLOT(setStyleText(QString))); } void QGCDataPlot2D::reloadFile() @@ -214,6 +216,24 @@ void QGCDataPlot2D::loadRawLog(QString file, QString xAxisName, QString yAxisFil loadCsvLog(logFile->fileName(), xAxisName, yAxisFilter); } +/** + * This function loads a CSV file into the plot. It tries to assign the dimension names + * based on the first data row and tries to guess the separator char. + * + * @param file Name of the file to open + * @param xAxisName Optional paramater. If given, the x axis dimension will be selected to match this string + * @param yAxisFilter Optional parameter. If given, only data dimension names present in the filter string will be + * plotted + * + * @code + * + * QString file = "/home/user/datalog.txt"; // With header: xyz + * QString xAxis = "x"; + * QString yAxis = "z"; + * + * // Plotted result will be x vs z with y ignored. + * @endcode + */ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFilter) { if (logFile != NULL) @@ -235,9 +255,44 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil // First line is header QString header = in.readLine(); - QString separator = "\t"; - qDebug() << "READING CSV:" << header; + bool charRead = false; + QString separator = ""; + QList sepCandidates; + sepCandidates << '\t'; + sepCandidates << ','; + sepCandidates << ';'; + sepCandidates << ' '; + sepCandidates << '~'; + sepCandidates << '|'; + + // Iterate until separator is found + // or full header is parsed + for (int i = 0; i < header.length(); i++) + { + if (sepCandidates.contains(header.at(i))) + { + // Separator found + if (charRead) + { + separator += header[i]; + } + } + else + { + // Char found + charRead = true; + // If the separator is not empty, this char + // has been read after a separator, so detection + // is now complete + if (separator != "") break; + } + } + + QString out = separator; + out.replace("\t", ""); + ui->filenameLabel->setText(file.split("/").last().split("\\").last()+" Separator: \""+out+"\""); + //qDebug() << "READING CSV:" << header; // Clear plot plot->removeData(); @@ -245,7 +300,7 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil QVector xValues; QMap* > yValues; - QStringList curveNames = header.split(separator); + QStringList curveNames = header.split(separator, QString::SkipEmptyParts); QString curveName; // Clear UI elements @@ -254,73 +309,89 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil int curveNameIndex = 0; - int xValueIndex = curveNames.indexOf(xAxisName); - if (xValueIndex < 0 || xValueIndex > (curveNames.size() - 1)) xValueIndex = 0; + //int xValueIndex = curveNames.indexOf(xAxisName); + + QString xAxisFilter; + if (xAxisName == "") + { + xAxisFilter = curveNames.first(); + } + else + { + xAxisFilter = xAxisName; + } foreach(curveName, curveNames) { - if (curveNameIndex != xValueIndex) + ui->xAxis->addItem(curveName); + if (curveName != xAxisFilter) { - // FIXME Add check for y value filter - if ((ui->yAxis->text() == "") && yValues.contains(curveName)) + if ((yAxisFilter == "") || yAxisFilter.contains(curveName)) { yValues.insert(curveName, new QVector()); - ui->xAxis->addItem(curveName); // Add separator starting with second item if (curveNameIndex > 0 && curveNameIndex < curveNames.size()) { ui->yAxis->setText(ui->yAxis->text()+"|"); } ui->yAxis->setText(ui->yAxis->text()+curveName); + curveNameIndex++; } } - curveNameIndex++; } + // Select current axis in UI + ui->xAxis->setCurrentIndex(curveNames.indexOf(xAxisFilter)); + // Read data double x,y; - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine(); - QStringList values = line.split(separator); + QStringList values = line.split(separator, QString::SkipEmptyParts); - bool okx; - - x = values.at(xValueIndex).toDouble(&okx); - - if(okx) + foreach(curveName, curveNames) { - // Append X value - xValues.append(x); - QString yStr; - bool oky; + bool okx; + if (curveName == xAxisFilter) + { + // X AXIS HANDLING - int yCount = 0; - foreach(yStr, values) + // Take this value as x if it is selected + x = values.at(curveNames.indexOf(curveName)).toDouble(&okx); + xValues.append(x - 1270125570000LL); + qDebug() << "x" << x - 1270125570000LL; + } + else { - // We have already x, so only handle - // true y values - if (yCount != xValueIndex && yCount < curveNames.size()) + // Y AXIS HANDLING + + if(yAxisFilter == "" || yAxisFilter.contains(curveName)) { - y = yStr.toDouble(&oky); - // Append one of the Y values - yValues.value(curveNames.at(yCount))->append(y); + // Only append y values where a valid x value is present + if (yValues.value(curveName)->size() == xValues.size() - 1) + { + bool oky; + int curveNameIndex = curveNames.indexOf(curveName); + if (values.size() > curveNameIndex) + { + y = values.at(curveNameIndex).toDouble(&oky); + yValues.value(curveName)->append(y); + } + } } - - yCount++; } } } - - QVector* yCurve; - int yCurveIndex = 0; - foreach(yCurve, yValues) + // Add data array of each curve to the plot at once (fast) + // Iterates through all x-y curve combinations + for (int i = 0; i < yValues.size(); i++) { - plot->appendData(yValues.keys().at(yCurveIndex), xValues.data(), yCurve->data(), xValues.size()); - yCurveIndex++; + plot->appendData(yValues.keys().at(i), xValues.data(), yValues.values().at(i)->data(), xValues.size()); } } @@ -339,7 +410,7 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil * the match of the regression. * @return 1 on success, 0 on failure (e.g. because of infinite slope) */ -int QGCDataPlot2D::linearRegression(double *x,double *y,int n,double *a,double *b,double *r) +int QGCDataPlot2D::linearRegression(double* x,double* y,int n,double* a,double* b,double* r) { int i; double sumx=0,sumy=0,sumx2=0,sumy2=0,sumxy=0; diff --git a/src/ui/QGCDataPlot2D.ui b/src/ui/QGCDataPlot2D.ui index cad78db..92eb55e 100644 --- a/src/ui/QGCDataPlot2D.ui +++ b/src/ui/QGCDataPlot2D.ui @@ -6,14 +6,14 @@ 0 0 - 736 + 807 308 Form - + @@ -43,24 +43,74 @@ - Dots + Only crosses - Lines + Only circles + + + + + Only dots + + + + + Lines and crosses + + + + + Lines and circles + + + + + Lines and dots + + + + + Dotted lines and crosses + + + + + Dotted lines and circles + + + + + Dotted lines and dots + + + + + Dashed lines and crosses + + + + + Dashed lines and circles + + + + + Dashed lines and dots - + Reload - + Qt::Horizontal @@ -73,14 +123,14 @@ - + Save Plot - + QFrame::StyledPanel @@ -123,7 +173,7 @@ - + Qt::Horizontal @@ -136,21 +186,21 @@ - + Print - + Save CSV - + Select file @@ -164,6 +214,13 @@ + + + + Legend + + + diff --git a/src/ui/linechart/IncrementalPlot.cc b/src/ui/linechart/IncrementalPlot.cc index 2711b2b..0af9bc1 100644 --- a/src/ui/linechart/IncrementalPlot.cc +++ b/src/ui/linechart/IncrementalPlot.cc @@ -62,6 +62,7 @@ IncrementalPlot::IncrementalPlot(QWidget *parent): setFrameStyle(QFrame::NoFrame); setLineWidth(0); + setStyleText("solid crosses"); setCanvasLineWidth(2); plotLayout()->setAlignCanvasToScales(true); @@ -84,6 +85,7 @@ IncrementalPlot::IncrementalPlot(QWidget *parent): zoomer->setRubberBandPen(QPen(Qt::red, 2, Qt::DotLine)); zoomer->setTrackerPen(QPen(Qt::red)); //zoomer->setZoomBase(QwtDoubleRect()); + legend = NULL; colors = QList(); nextColor = 0; @@ -117,6 +119,75 @@ IncrementalPlot::~IncrementalPlot() } +void IncrementalPlot::showLegend(bool show) +{ + if (show) + { + if (legend == NULL) + { + legend = new QwtLegend; + legend->setFrameStyle(QFrame::Box); + } + insertLegend(legend, QwtPlot::RightLegend); + } + else + { + delete legend; + legend = NULL; + } + replot(); +} + +/** + * Set datapoint and line style. This interface is intented + * to be directly connected to the UI and allows to parse + * human-readable, textual descriptions into plot specs. + * + * Data points: Either "circles", "crosses" or the default "dots" + * Lines: Either "dotted", ("solid"/"line") or no lines if not used + * + * No special formatting is needed, as long as the keywords are contained + * in the string. Lower/uppercase is ignored as well. + * + * @param style Formatting string for line/data point style + */ +void IncrementalPlot::setStyleText(QString style) +{ + foreach (QwtPlotCurve* curve, d_curve) + { + // Style of datapoints + if (style.toLower().contains("circles")) + { + curve->setSymbol(QwtSymbol(QwtSymbol::Ellipse, + QBrush(curve->pen().color()), curve->pen(), QSize(5, 5)) ); + } + else if (style.toLower().contains("crosses")) + { + curve->setSymbol(QwtSymbol(QwtSymbol::XCross, + QBrush(curve->pen().color()), curve->pen(), QSize(5, 5)) ); + } + else // Always show dots (style.toLower().contains("dots")) + { + curve->setSymbol(QwtSymbol(QwtSymbol::Rect, + QBrush(curve->pen().color()), curve->pen(), QSize(1, 1)) ); + } + + // Style of lines + if (style.toLower().contains("dotted")) + { + curve->setStyle(QwtPlotCurve::Dots); + } + else if (style.toLower().contains("line") || style.toLower().contains("solid")) + { + curve->setStyle(QwtPlotCurve::Lines); + } + else + { + curve->setStyle(QwtPlotCurve::NoCurve); + } + } +} + void IncrementalPlot::resetScaling() { xmin = 0; @@ -233,11 +304,11 @@ void IncrementalPlot::appendData(QString key, double *x, double *y, int size) canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false); // FIXME Check if here all curves should be drawn -// QwtPlotCurve* plotCurve; -// foreach(plotCurve, d_curve) -// { -// plotCurve->draw(0, curve->dataSize()-1); -// } + // QwtPlotCurve* plotCurve; + // foreach(plotCurve, d_curve) + // { + // plotCurve->draw(0, curve->dataSize()-1); + // } curve->draw(curve->dataSize() - size, curve->dataSize() - 1); canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, cacheMode); diff --git a/src/ui/linechart/IncrementalPlot.h b/src/ui/linechart/IncrementalPlot.h index 14cd194..9ef97dc 100644 --- a/src/ui/linechart/IncrementalPlot.h +++ b/src/ui/linechart/IncrementalPlot.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "ScrollZoomer.h" @@ -38,12 +39,6 @@ public: IncrementalPlot(QWidget *parent = NULL); virtual ~IncrementalPlot(); - void appendData(QString key, double x, double y); - void appendData(QString key, double *x, double *y, int size); - - void resetScaling(); - void removeData(); - /** @brief Get color map of this plot */ QList IncrementalPlot::getColorMap(); /** @brief Get next color of color map */ @@ -51,10 +46,22 @@ public: /** @brief Get color for curve id */ QColor IncrementalPlot::getColorForCurve(QString id); +public slots: + void appendData(QString key, double x, double y); + void appendData(QString key, double *x, double *y, int size); + + void resetScaling(); + void removeData(); + /** @brief Show the plot legend */ + void showLegend(bool show); + /** @brief Set new plot style */ + void setStyleText(QString style); + protected: QList colors; int nextColor; ScrollZoomer* zoomer; + QwtLegend* legend; double xmin; double xmax; double ymin;