diff --git a/price.txt b/price.txt new file mode 100644 index 0000000000000000000000000000000000000000..3e4e1661ed67758d52eb04409464b355cd2aec58 --- /dev/null +++ b/price.txt @@ -0,0 +1,12 @@ +price quantity +210 81 +250 73 +280 64 +300 61 +320 50 +340 46 +360 45 +380 44 +400 43 +420 39 +440 36 diff --git a/src/LogCompressor.cc b/src/LogCompressor.cc index ceed32f62652af8718dac80947af56fe42cbef19..01c2f0201cffc804963ef53222dace6849c8b3e7 100644 --- a/src/LogCompressor.cc +++ b/src/LogCompressor.cc @@ -36,6 +36,9 @@ This file is part of the QGROUNDCONTROL project #include +/** + * It will only get active upon calling startCompression() + */ LogCompressor::LogCompressor(QString logFileName, QString outFileName, int uasid) : logFileName(logFileName), outFileName(outFileName), @@ -44,7 +47,6 @@ LogCompressor::LogCompressor(QString logFileName, QString outFileName, int uasid dataLines(1), uasid(uasid) { - start(); } void LogCompressor::run() @@ -174,9 +176,15 @@ void LogCompressor::run() dataLines = 1; delete keys; qDebug() << "Done with logfile processing"; + emit finishedFile(outfile.fileName()); running = false; } +void LogCompressor::startCompression() +{ + start(); +} + bool LogCompressor::isFinished() { return !running; diff --git a/src/LogCompressor.h b/src/LogCompressor.h index e5ccdecd938ef6fd4931ebf0e9af60674f9c8e78..fff3dee983fe889fe10941b0ef2aaaaeca2bf110 100644 --- a/src/LogCompressor.h +++ b/src/LogCompressor.h @@ -5,11 +5,15 @@ class LogCompressor : public QThread { + Q_OBJECT public: + /** @brief Create the log compressor. It will only get active upon calling startCompression() */ LogCompressor(QString logFileName, QString outFileName="", int uasid = 0); + void startCompression(); bool isFinished(); int getDataLines(); int getCurrentLine(); + protected: void run(); QString logFileName; @@ -18,6 +22,12 @@ protected: int currentDataLine; int dataLines; int uasid; + +signals: + /** @brief This signal is emitted once a logfile has been finished writing + * @param fileName The name out the output (CSV) file + */ + void finishedFile(QString fileName); }; #endif // LOGCOMPRESSOR_H diff --git a/src/ui/MainWindow.cc b/src/ui/MainWindow.cc index 5a1f2cc2a4cbc926b7b9f1e034f9640d6da2c50d..8c0e2470157457a7064049567dcf6fd8385b3dcb 100644 --- a/src/ui/MainWindow.cc +++ b/src/ui/MainWindow.cc @@ -145,6 +145,7 @@ void MainWindow::connectWidgets() { connect(UASManager::instance(), SIGNAL(UASCreated(UASInterface*)), linechart, SLOT(addSystem(UASInterface*))); connect(UASManager::instance(), SIGNAL(activeUASSet(int)), linechart, SLOT(selectSystem(int))); + connect(linechart, SIGNAL(logfileWritten(QString)), this, SLOT(loadDataView(QString))); connect(mavlink, SIGNAL(receiveLossChanged(int, float)), info, SLOT(updateSendLoss(int, float))); } @@ -544,6 +545,13 @@ void MainWindow::loadDataView() centerStack->setCurrentWidget(dataplot); } +void MainWindow::loadDataView(QString fileName) +{ + clearView(); + centerStack->setCurrentWidget(dataplot); + dataplot->loadFile(fileName); +} + void MainWindow::loadPilotView() { clearView(); diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h index 1a698d247a2d6f3a252cdfb13013ad185ed95187..79b0a28ecf8e73318f33c2b07cee3c5412d1571e 100644 --- a/src/ui/MainWindow.h +++ b/src/ui/MainWindow.h @@ -119,6 +119,8 @@ public slots: void loadMAVLinkView(); /** @brief Load data view, allowing to plot flight data */ void loadDataView(); + /** @brief Load data view, allowing to plot flight data */ + void loadDataView(QString fileName); /** @brief Show the online help for users */ void showHelp(); diff --git a/src/ui/QGCDataPlot2D.cc b/src/ui/QGCDataPlot2D.cc index 381c45b254ddbf0c720e4751879225c16f788562..a4160caaebc8a7e209f98f5f6790560001c7f29d 100644 --- a/src/ui/QGCDataPlot2D.cc +++ b/src/ui/QGCDataPlot2D.cc @@ -27,6 +27,7 @@ QGCDataPlot2D::QGCDataPlot2D(QWidget *parent) : QHBoxLayout* layout = new QHBoxLayout(ui->plotFrame); layout->addWidget(plot); ui->plotFrame->setLayout(layout); + ui->gridCheckBox->setChecked(plot->gridEnabled()); // Connect user actions connect(ui->selectFileButton, SIGNAL(clicked()), this, SLOT(selectFile())); @@ -35,6 +36,9 @@ QGCDataPlot2D::QGCDataPlot2D(QWidget *parent) : 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->symmetricCheckBox, SIGNAL(clicked(bool)), plot, SLOT(setSymmetric(bool))); + connect(ui->gridCheckBox, SIGNAL(clicked(bool)), plot, SLOT(showGrid(bool))); + connect(ui->regressionButton, SIGNAL(clicked()), this, SLOT(calculateRegression())); connect(ui->style, SIGNAL(currentIndexChanged(QString)), plot, SLOT(setStyleText(QString))); } @@ -68,31 +72,54 @@ void QGCDataPlot2D::loadFile() } } +void QGCDataPlot2D::loadFile(QString file) +{ + fileName = file; + if (QFileInfo(fileName).isReadable()) + { + if (fileName.contains(".raw") || fileName.contains(".imu")) + { + loadRawLog(fileName); + } + else if (fileName.contains(".txt") || fileName.contains(".csv")) + { + loadCsvLog(fileName); + } + } +} + +/** + * This function brings up a file name dialog and exports to either PDF or SVG, depending on the filename + */ void QGCDataPlot2D::savePlot() { QString fileName = "plot.svg"; fileName = QFileDialog::getSaveFileName( this, "Export File Name", QDesktopServices::storageLocation(QDesktopServices::DesktopLocation), - "SVG Documents (*.svg);;"); - while(!fileName.endsWith("svg")) + "PDF Documents (*.pdf);;SVG Images (*.svg)"); + while(!(fileName.endsWith(".svg") || fileName.endsWith(".pdf"))) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Critical); - msgBox.setText("Unsuitable file extension for SVG"); - msgBox.setInformativeText("Please choose .svg as file extension. Click OK to change the file extension, cancel to not save the file."); + msgBox.setText("Unsuitable file extension for PDF or SVG"); + msgBox.setInformativeText("Please choose .pdf or .svg as file extension. Click OK to change the file extension, cancel to not save the file."); msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Ok); - if(msgBox.exec() == QMessageBox::Cancel) break; + // Abort if cancelled + if(msgBox.exec() == QMessageBox::Cancel) return; fileName = QFileDialog::getSaveFileName( this, "Export File Name", QDesktopServices::storageLocation(QDesktopServices::DesktopLocation), - "SVG Documents (*.svg);;"); + "PDF Documents (*.pdf);;SVG Images (*.svg)"); } - exportSVG(fileName); - // else if (fileName.endsWith("pdf")) - // { - // print(fileName); - // } + if (fileName.endsWith(".pdf")) + { + exportPDF(fileName); + } + else if (fileName.endsWith(".svg")) + { + exportSVG(fileName); + } } @@ -137,10 +164,51 @@ void QGCDataPlot2D::print() } } +void QGCDataPlot2D::exportPDF(QString fileName) +{ + QPrinter printer; + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(fileName); + //printer.setFullPage(true); + printer.setPageMargins(10.0, 10.0, 10.0, 10.0, QPrinter::Millimeter); + printer.setPageSize(QPrinter::A4); + + QString docName = plot->title().text(); + if ( !docName.isEmpty() ) + { + docName.replace (QRegExp (QString::fromLatin1 ("\n")), tr (" -- ")); + printer.setDocName (docName); + } + + printer.setCreator("QGroundControl"); + printer.setOrientation(QPrinter::Landscape); + + plot->setStyleSheet("QWidget { background-color: #FFFFFF; color: #000000; background-clip: border; font-size: 10pt;}"); + // plot->setCanvasBackground(Qt::white); + // QwtPlotPrintFilter filter; + // filter.color(Qt::white, QwtPlotPrintFilter::CanvasBackground); + // filter.color(Qt::black, QwtPlotPrintFilter::AxisScale); + // filter.color(Qt::black, QwtPlotPrintFilter::AxisTitle); + // filter.color(Qt::black, QwtPlotPrintFilter::MajorGrid); + // filter.color(Qt::black, QwtPlotPrintFilter::MinorGrid); + // if ( printer.colorMode() == QPrinter::GrayScale ) + // { + // int options = QwtPlotPrintFilter::PrintAll; + // options &= ~QwtPlotPrintFilter::PrintBackground; + // options |= QwtPlotPrintFilter::PrintFrameWithScales; + // filter.setOptions(options); + // } + plot->print(printer);//, filter); + plot->setStyleSheet("QWidget { background-color: #050508; color: #DDDDDF; background-clip: border; font-size: 11pt;}"); + //plot->setCanvasBackground(QColor(5, 5, 8)); +} + void QGCDataPlot2D::exportSVG(QString fileName) { if ( !fileName.isEmpty() ) { + plot->setStyleSheet("QWidget { background-color: #FFFFFF; color: #000000; background-clip: border; font-size: 10pt;}"); + //plot->setCanvasBackground(Qt::white); QSvgGenerator generator; generator.setFileName(fileName); generator.setSize(QSize(800, 600)); @@ -153,6 +221,7 @@ void QGCDataPlot2D::exportSVG(QString fileName) filter.color(Qt::black, QwtPlotPrintFilter::MinorGrid); plot->print(generator, filter); + plot->setStyleSheet("QWidget { background-color: #050508; color: #DDDDDF; background-clip: border; font-size: 11pt;}"); } } @@ -199,6 +268,7 @@ void QGCDataPlot2D::loadRawLog(QString file, QString xAxisName, QString yAxisFil // Postprocess log file logFile = new QTemporaryFile(); compressor = new LogCompressor(file, logFile->fileName()); + compressor->startCompression(); // Block UI QProgressDialog progress("Transforming RAW log file to CSV", "Abort Transformation", 0, 1, this); @@ -242,6 +312,7 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil { logFile->close(); delete logFile; + curveNames.clear(); } logFile = new QFile(file); @@ -249,6 +320,11 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil if (!logFile->open(QIODevice::ReadOnly | QIODevice::Text)) return; + // Set plot title + if (ui->plotTitle->text() != "") plot->setTitle(ui->plotTitle->text()); + if (ui->plotXAxisLabel->text() != "") plot->setAxisTitle(QwtPlot::xBottom, ui->plotXAxisLabel->text()); + if (ui->plotYAxisLabel->text() != "") plot->setAxisTitle(QwtPlot::yLeft, ui->plotYAxisLabel->text()); + // Extract header // Read in values @@ -302,12 +378,15 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil QVector xValues; QMap* > yValues; - QStringList curveNames = header.split(separator, QString::SkipEmptyParts); + curveNames.append(header.split(separator, QString::SkipEmptyParts)); QString curveName; // Clear UI elements ui->xAxis->clear(); ui->yAxis->clear(); + ui->xRegressionComboBox->clear(); + ui->yRegressionComboBox->clear(); + ui->regressionOutput->clear(); int curveNameIndex = 0; @@ -325,14 +404,18 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil foreach(curveName, curveNames) { + // Add to plot x axis selection ui->xAxis->addItem(curveName); + // Add to regression selection + ui->xRegressionComboBox->addItem(curveName); + ui->yRegressionComboBox->addItem(curveName); if (curveName != xAxisFilter) { if ((yAxisFilter == "") || yAxisFilter.contains(curveName)) { yValues.insert(curveName, new QVector()); // Add separator starting with second item - if (curveNameIndex > 0 && curveNameIndex < curveNames.size()) + if (curveNameIndex > 0 && curveNameIndex < curveNames.count()) { ui->yAxis->setText(ui->yAxis->text()+"|"); } @@ -364,8 +447,7 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil // 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; + xValues.append(x);// - 1270125570000ULL); } else { @@ -374,11 +456,11 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil if(yAxisFilter == "" || yAxisFilter.contains(curveName)) { // Only append y values where a valid x value is present - if (yValues.value(curveName)->size() == xValues.size() - 1) + if (yValues.value(curveName)->count() == xValues.count() - 1) { bool oky; int curveNameIndex = curveNames.indexOf(curveName); - if (values.size() > curveNameIndex) + if (values.count() > curveNameIndex) { y = values.at(curveNameIndex).toDouble(&oky); yValues.value(curveName)->append(y); @@ -391,10 +473,87 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil // 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++) + for (int i = 0; i < yValues.count(); i++) + { + plot->appendData(yValues.keys().at(i), xValues.data(), yValues.values().at(i)->data(), xValues.count()); + } + plot->setStyleText(ui->style->currentText()); +} + +bool QGCDataPlot2D::calculateRegression() +{ + // TODO Add support for quadratic / cubic curve fitting + return calculateRegression(ui->xRegressionComboBox->currentText(), ui->yRegressionComboBox->currentText(), "linear"); +} + +/** + * @param xName Name of the x dimension + * @param yName Name of the y dimension + * @param method Regression method, either "linear", "quadratic" or "cubic". Only linear is supported at this point + */ +bool QGCDataPlot2D::calculateRegression(QString xName, QString yName, QString method) +{ + bool result = false; + QString function; + if (xName != yName) + { + if (QFileInfo(fileName).isReadable()) + { + loadCsvLog(fileName, xName, yName); + ui->xRegressionComboBox->setCurrentIndex(curveNames.indexOf(xName)); + ui->yRegressionComboBox->setCurrentIndex(curveNames.indexOf(yName)); + } + const int size = 100000; + double x[size]; + double y[size]; + int copied = plot->data(yName, x, y, size); + + if (method == "linear") + { + double a; // Y-axis crossing + double b; // Slope + double r; // Regression coefficient + int lin = linearRegression(x, y, copied, &a, &b, &r); + if(lin == 1) + { + function = tr("%1 = %2 * %3 + %4 | R-coefficient: %5").arg(yName, QString::number(b), xName, QString::number(a), QString::number(r)); + + // Plot curve + // y-axis crossing (x = 0) + // Set plotting to lines only + plot->appendData(tr("regression %1-%2").arg(xName, yName), 0.0, a); + plot->setStyleText("lines"); + // x-value of the current rightmost x position in the plot + plot->appendData(tr("regression %1-%2").arg(xName, yName), plot->invTransform(QwtPlot::xBottom, plot->width() - plot->width()*0.08f), (a + b*plot->invTransform(QwtPlot::xBottom, plot->width() - plot->width() * 0.08f))); + result = true; + } + else + { + function = tr("Linear regression failed. (Limit: %1 data points. Try with less)").arg(size); + result = false; + } + } + else if (method == "quadratic") + { + + } + else if (method == "cubic") + { + + } + else + { + function = tr("Regression method %1 not found").arg(method); + result = false; + } + } + else { - plot->appendData(yValues.keys().at(i), xValues.data(), yValues.values().at(i)->data(), xValues.size()); + // xName == yName + function = tr("Please select different X and Y dimensions, not %1 = %2").arg(xName, yName); } + ui->regressionOutput->setText(function); + return result; } /** diff --git a/src/ui/QGCDataPlot2D.h b/src/ui/QGCDataPlot2D.h index 5c89ff071e99ed144ec074bda59ef02be69b9963..629798f4e93869fc767c67847509e6928d44604b 100644 --- a/src/ui/QGCDataPlot2D.h +++ b/src/ui/QGCDataPlot2D.h @@ -16,11 +16,17 @@ public: QGCDataPlot2D(QWidget *parent = 0); ~QGCDataPlot2D(); + /** @brief Calculate and display regression function*/ + bool calculateRegression(QString xName, QString yName, QString method="linear"); + /** @brief Linear regression over data points */ - int linearRegression(double *x,double *y,int n,double *a,double *b,double *r); + int linearRegression(double* x,double* y,int n,double* a,double* b,double* r); public slots: + /** @brief Load previously selected file */ void loadFile(); + /** @brief Load file with this name */ + void loadFile(QString file); /** @brief Reload a file, with filtering enabled */ void reloadFile(); void selectFile(); @@ -29,10 +35,14 @@ public slots: void saveCsvLog(); /** @brief Save plot to PDF or SVG */ void savePlot(); + /** @brief Export PDF file */ + void exportPDF(QString fileName); /** @brief Export SVG file */ void exportSVG(QString file); /** @brief Print or save PDF file (MacOS/Linux) */ void print(); + /** @brief Calculate and display regression function*/ + bool calculateRegression(); protected: void changeEvent(QEvent *e); @@ -40,6 +50,7 @@ protected: LogCompressor* compressor; QFile* logFile; QString fileName; + QStringList curveNames; private: Ui::QGCDataPlot2D *ui; diff --git a/src/ui/QGCDataPlot2D.ui b/src/ui/QGCDataPlot2D.ui index 92eb55ef1e28843cf972c2a2bfd238bf2c905641..04861aebb8bf5716b31f66b2171458b2250d5b86 100644 --- a/src/ui/QGCDataPlot2D.ui +++ b/src/ui/QGCDataPlot2D.ui @@ -6,22 +6,22 @@ 0 0 - 807 + 1073 308 Form - - + + X - + @@ -53,7 +53,12 @@ - Only dots + Only rectangles + + + + + Only symbols @@ -68,7 +73,12 @@ - Lines and dots + Lines and rects + + + + + Lines and symbols @@ -83,7 +93,7 @@ - Dotted lines and dots + Dotted lines and rects @@ -98,39 +108,91 @@ - Dashed lines and dots + Dashed lines and rects - + - Reload + Replot - - + + - Qt::Horizontal + Qt::Vertical - - - 40 - 20 - - - + - + - Save Plot + Save Image + + + + + + + Print + + + + + + + Title + + + + + + + + + + X label + + + + + + + + + + Y label + + + + + + + + + + Symmetric + + + + + + + Legend + + + + + + + Grid - + QFrame::StyledPanel @@ -140,14 +202,14 @@ - + File - + @@ -166,14 +228,21 @@ - + Please select input file.. - + + + + Select file + + + + Qt::Horizontal @@ -186,41 +255,60 @@ - - - - Print + + + + Qt::Vertical - - + + - Save CSV + Regression - - + + + + + + + + - Select file + Calculate - - - - Symmetric + + + + true - - + + - Legend + Save Data + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/src/ui/linechart/IncrementalPlot.cc b/src/ui/linechart/IncrementalPlot.cc index 0af9bc1f3a9eb9ceea43e378a2fe82a159f24325..da65cd6d787d7ff6942264ba120ecf7a32c52b1f 100644 --- a/src/ui/linechart/IncrementalPlot.cc +++ b/src/ui/linechart/IncrementalPlot.cc @@ -1,3 +1,33 @@ +/*===================================================================== + +QGroundControl Open Source Ground Control Station + +(c) 2009, 2010 QGROUNDCONTROL PROJECT + +This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + +======================================================================*/ + +/** + * @file + * @brief Implementation of class IncrementalPlot + * @author Lorenz Meier + * + */ + #include #include #include @@ -9,9 +39,9 @@ #include #include #include -#if QT_VERSION >= 0x040000 #include -#endif + +#include CurveData::CurveData(): d_count(0) @@ -45,18 +75,23 @@ int CurveData::size() const return d_x.size(); } -const double *CurveData::x() const +const double* CurveData::x() const { return d_x.data(); } -const double *CurveData::y() const +const double* CurveData::y() const { return d_y.data(); } IncrementalPlot::IncrementalPlot(QWidget *parent): - QwtPlot(parent) + QwtPlot(parent), + symbolWidth(1.2f), + curveWidth(1.0f), + gridWidth(0.8f), + scaleWidth(1.0f), + symmetric(false) { setAutoReplot(false); @@ -67,8 +102,8 @@ IncrementalPlot::IncrementalPlot(QWidget *parent): plotLayout()->setAlignCanvasToScales(true); - QwtPlotGrid *grid = new QwtPlotGrid; - grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine)); + grid = new QwtPlotGrid; + grid->setMajPen(QPen(Qt::gray, 0.8f, Qt::DotLine)); grid->attach(this); QwtLinearScaleEngine* yScaleEngine = new QwtLinearScaleEngine(); @@ -82,7 +117,7 @@ IncrementalPlot::IncrementalPlot(QWidget *parent): // enable zooming zoomer = new ScrollZoomer(canvas()); - zoomer->setRubberBandPen(QPen(Qt::red, 2, Qt::DotLine)); + zoomer->setRubberBandPen(QPen(Qt::red, 1.5f, Qt::DotLine)); zoomer->setTrackerPen(QPen(Qt::red)); //zoomer->setZoomBase(QwtDoubleRect()); legend = NULL; @@ -112,6 +147,8 @@ IncrementalPlot::IncrementalPlot(QWidget *parent): colors.append(QColor(161,252,116)); colors.append(QColor(87,231,246)); colors.append(QColor(230,126,23)); + + connect(this, SIGNAL(legendChecked(QwtPlotItem*,bool)), this, SLOT(handleLegendClick(QwtPlotItem*,bool))); } IncrementalPlot::~IncrementalPlot() @@ -119,6 +156,23 @@ IncrementalPlot::~IncrementalPlot() } +/** + * @param symmetric true will enforce that both axes have the same interval, + * centered around the data plot. A circle will thus remain a circle if true, + * if set to false it might become an ellipse because of axis scaling. + */ +void IncrementalPlot::setSymmetric(bool symmetric) +{ + this->symmetric = symmetric; + updateScale(); // Updates the scaling at replots +} + +void IncrementalPlot::handleLegendClick(QwtPlotItem* item, bool on) +{ + item->setVisible(!on); + replot(); +} + void IncrementalPlot::showLegend(bool show) { if (show) @@ -127,6 +181,7 @@ void IncrementalPlot::showLegend(bool show) { legend = new QwtLegend; legend->setFrameStyle(QFrame::Box); + legend->setItemMode(QwtLegend::CheckableItem); } insertLegend(legend, QwtPlot::RightLegend); } @@ -135,7 +190,7 @@ void IncrementalPlot::showLegend(bool show) delete legend; legend = NULL; } - replot(); + updateScale(); // Updates the scaling at replots } /** @@ -159,19 +214,25 @@ void IncrementalPlot::setStyleText(QString style) if (style.toLower().contains("circles")) { curve->setSymbol(QwtSymbol(QwtSymbol::Ellipse, - QBrush(curve->pen().color()), curve->pen(), QSize(5, 5)) ); + Qt::NoBrush, QPen(QBrush(curve->symbol().pen().color()), symbolWidth), QSize(6, 6)) ); } else if (style.toLower().contains("crosses")) { curve->setSymbol(QwtSymbol(QwtSymbol::XCross, - QBrush(curve->pen().color()), curve->pen(), QSize(5, 5)) ); + Qt::NoBrush, QPen(QBrush(curve->symbol().pen().color()), symbolWidth), QSize(5, 5)) ); } - else // Always show dots (style.toLower().contains("dots")) + else if (style.toLower().contains("rect")) { curve->setSymbol(QwtSymbol(QwtSymbol::Rect, - QBrush(curve->pen().color()), curve->pen(), QSize(1, 1)) ); + Qt::NoBrush, QPen(QBrush(curve->symbol().pen().color()), symbolWidth), QSize(6, 6)) ); + } + else if (style.toLower().contains("line")) // Show no symbol + { + curve->setSymbol(QwtSymbol(QwtSymbol::NoSymbol, + Qt::NoBrush, QPen(QBrush(curve->symbol().pen().color()), symbolWidth), QSize(6, 6)) ); } + curve->setPen(QPen(QBrush(curve->symbol().pen().color().darker()), curveWidth)); // Style of lines if (style.toLower().contains("dotted")) { @@ -181,19 +242,25 @@ void IncrementalPlot::setStyleText(QString style) { curve->setStyle(QwtPlotCurve::Lines); } + else if (style.toLower().contains("dashed") || style.toLower().contains("solid")) + { + curve->setStyle(QwtPlotCurve::Steps); + } else { curve->setStyle(QwtPlotCurve::NoCurve); } + } + replot(); } void IncrementalPlot::resetScaling() { xmin = 0; xmax = 500; - ymin = 0; - ymax = 500; + ymin = xmin; + ymax = xmax; setAxisScale(xBottom, xmin+xmin*0.05, xmax+xmax*0.05); setAxisScale(yLeft, ymin+ymin*0.05, ymax+ymax*0.05); @@ -207,6 +274,46 @@ void IncrementalPlot::resetScaling() ymax = DBL_MIN; } +/** + * Updates the scale calculation and re-plots the whole plot + */ +void IncrementalPlot::updateScale() +{ + const double margin = 0.05; + double xMinRange = xmin+(xmin*margin); + double xMaxRange = xmax+(xmax*margin); + double yMinRange = ymin+(ymin*margin); + double yMaxRange = ymax+(ymax*margin); + if (symmetric) + { + double xRange = xMaxRange - xMinRange; + double yRange = yMaxRange - yMinRange; + + // Get the aspect ratio of the plot + float xSize = width(); + if (legend != NULL) xSize -= legend->width(); + float ySize = height(); + + float aspectRatio = xSize / ySize; + + if (xRange > yRange) + { + double yCenter = yMinRange + yRange/2.0; + yMinRange = yCenter - xRange/2.0; + yMaxRange = yCenter + xRange/2.0; + } + else + { + double xCenter = xMinRange + xRange/2.0; + xMinRange = xCenter - yRange/2.0; + xMaxRange = xCenter + yRange/2.0; + } + } + setAxisScale(xBottom, xMinRange, xMaxRange); + setAxisScale(yLeft, yMinRange, yMaxRange); + zoomer->setZoomBase(true); +} + void IncrementalPlot::appendData(QString key, double x, double y) { appendData(key, &x, &y, 1); @@ -235,7 +342,7 @@ void IncrementalPlot::appendData(QString key, double *x, double *y, int size) const QColor &c = getNextColor(); curve->setSymbol(QwtSymbol(QwtSymbol::XCross, - QBrush(c), QPen(c), QSize(5, 5)) ); + QBrush(c), QPen(c, 1.2f), QSize(5, 5)) ); curve->attach(this); } @@ -285,9 +392,7 @@ void IncrementalPlot::appendData(QString key, double *x, double *y, int size) if(scaleChanged) { - setAxisScale(xBottom, xmin+xmin*0.05, xmax+xmax*0.05); - setAxisScale(yLeft, ymin+ymin*0.05, ymax+ymax*0.05); - zoomer->setZoomBase(true); + updateScale(); } else { @@ -316,9 +421,44 @@ void IncrementalPlot::appendData(QString key, double *x, double *y, int size) #if QT_VERSION >= 0x040000 && defined(Q_WS_X11) canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, false); #endif + } +} +/** + * @return Number of copied data points, 0 on failure + */ +int IncrementalPlot::data(QString key, double* r_x, double* r_y, int maxSize) +{ + int result = 0; + if (d_data.contains(key)) + { + CurveData* d = d_data.value(key); + if (maxSize >= d->count()) + { + result = d->count(); + memcpy(r_x, d->x(), sizeof(double) * d->count()); + memcpy(r_y, d->y(), sizeof(double) * d->count()); + } + else + { + result = 0; + } } + return result; +} + +/** + * @param show true to show the grid, false else + */ +void IncrementalPlot::showGrid(bool show) +{ + grid->setVisible(show); + replot(); +} +bool IncrementalPlot::gridEnabled() +{ + return grid->isVisible(); } QList IncrementalPlot::getColorMap() @@ -330,7 +470,7 @@ QColor IncrementalPlot::getNextColor() { /* Return current color and increment counter for next round */ nextColor++; - if(nextColor >= colors.size()) nextColor = 0; + if(nextColor >= colors.count()) nextColor = 0; return colors[nextColor++]; } diff --git a/src/ui/linechart/IncrementalPlot.h b/src/ui/linechart/IncrementalPlot.h index 53df82de5acf8236f51107c543e20d97af176cd0..ea656a9fa1e22a06446c2eef9af5d19c7e658e25 100644 --- a/src/ui/linechart/IncrementalPlot.h +++ b/src/ui/linechart/IncrementalPlot.h @@ -1,3 +1,33 @@ +/*===================================================================== + +QGroundControl Open Source Ground Control Station + +(c) 2009, 2010 QGROUNDCONTROL PROJECT + +This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + +======================================================================*/ + +/** + * @file + * @brief Defition of class IncrementalPlot + * @author Lorenz Meier + * + */ + #ifndef INCREMENTALPLOT_H #define INCREMENTALPLOT_H @@ -5,21 +35,25 @@ #include #include #include +#include #include #include "ScrollZoomer.h" class QwtPlotCurve; +/** + * @brief Plot data container for growing data + */ class CurveData { - // A container class for growing data public: - CurveData(); void append(double *x, double *y, int count); + /** @brief The number of datasets held in the data structure */ int count() const; + /** @brief The reserved size of the data structure in units */ int size() const; const double *x() const; const double *y() const; @@ -32,44 +66,89 @@ private: int d_timerCount; }; +/** + * @brief Incremental plotting widget + * + * This widget plots data incrementally when new data arrives. + * It will only repaint the minimum screen content necessary to avoid + * a too high CPU consumption. It auto-scales the plot to new data. + */ class IncrementalPlot : public QwtPlot { Q_OBJECT public: + /** @brief Create a new, empty incremental plot */ IncrementalPlot(QWidget *parent = NULL); virtual ~IncrementalPlot(); /** @brief Get color map of this plot */ QList getColorMap(); + /** @brief Get next color of color map */ QColor getNextColor(); + /** @brief Get color for curve id */ QColor getColorForCurve(QString id); + /** @brief Get the state of the grid */ + bool gridEnabled(); + + /** @brief Read out data from a curve */ + int data(QString key, double* r_x, double* r_y, int maxSize); + + float symbolWidth; + float curveWidth; + float gridWidth; + float scaleWidth; + public slots: + /** @brief Append one data point */ void appendData(QString key, double x, double y); - void appendData(QString key, double *x, double *y, int size); + /** @brief Append multiple data points */ + void appendData(QString key, double* x, double* y, int size); + + /** @brief Reset the plot scaling to the default value */ void resetScaling(); + + /** @brief Update the plot scale based on current data/symmetric mode */ + void updateScale(); + + /** @brief Remove all data from the plot and repaint */ void removeData(); + /** @brief Show the plot legend */ void showLegend(bool show); + + /** @brief Show the plot grid */ + void showGrid(bool show); + /** @brief Set new plot style */ void setStyleText(QString style); + /** @brief Set symmetric axis scaling mode */ + void setSymmetric(bool symmetric); + +protected slots: + /** @brief Handle the click on a legend item */ + void handleLegendClick(QwtPlotItem* item, bool on); + protected: - QList colors; - int nextColor; - ScrollZoomer* zoomer; - QwtLegend* legend; - double xmin; - double xmax; - double ymin; - double ymax; + bool symmetric; ///< Enable symmetric plotting + QList colors; ///< Colormap for curves + int nextColor; ///< Next index in color map + ScrollZoomer* zoomer; ///< Zoomer class for widget + QwtLegend* legend; ///< Plot legend + QwtPlotGrid* grid; ///< Plot grid + double xmin; ///< Minimum x value seen + double xmax; ///< Maximum x value seen + double ymin; ///< Minimum y value seen + double ymax; ///< Maximum y value seen + private: - QMap d_data; - QMap d_curve; + QMap d_data; ///< Data points + QMap d_curve; ///< Plot curves }; #endif /* INCREMENTALPLOT_H */ diff --git a/src/ui/linechart/LinechartPlot.cc b/src/ui/linechart/LinechartPlot.cc index 96203ae3e687cbafa349b185246b4b64e701873d..7d1613845034a908f3978e9db2db6af1a78cd213 100644 --- a/src/ui/linechart/LinechartPlot.cc +++ b/src/ui/linechart/LinechartPlot.cc @@ -136,7 +136,7 @@ d_curve(NULL) // Enable zooming //zoomer = new Zoomer(canvas()); zoomer = new ScrollZoomer(canvas()); - zoomer->setRubberBandPen(QPen(Qt::blue, 2, Qt::DotLine)); + zoomer->setRubberBandPen(QPen(Qt::blue, 1.2, Qt::DotLine)); zoomer->setTrackerPen(QPen(Qt::blue)); // Start QTimer for plot update @@ -314,7 +314,7 @@ QColor LinechartPlot::getNextColor() { /* Return current color and increment counter for next round */ nextColor++; - if(nextColor >= colors.size()) nextColor = 0; + if(nextColor >= colors.count()) nextColor = 0; return colors[nextColor++]; } @@ -762,15 +762,15 @@ void TimeSeriesData::append(quint64 ms, double value) mean = mean / static_cast(qMin(averageWindow,static_cast(count))); qSort(medianList); - if (medianList.size() > 2) + if (medianList.count() > 2) { - if (medianList.size() % 2 == 0) + if (medianList.count() % 2 == 0) { - median = (medianList.at(medianList.size()/2) + medianList.at(medianList.size()/2+1)) / 2.0; + median = (medianList.at(medianList.count()/2) + medianList.at(medianList.count()/2+1)) / 2.0; } else { - median = medianList.at(medianList.size()/2+1); + median = medianList.at(medianList.count()/2+1); } } diff --git a/src/ui/linechart/LinechartWidget.cc b/src/ui/linechart/LinechartWidget.cc index d5ae0c6f720e5676b333b29bf6a8fb6975d700dd..f4649daa4c4a3197c25e66a8b8ac57b8e2e78494 100644 --- a/src/ui/linechart/LinechartWidget.cc +++ b/src/ui/linechart/LinechartWidget.cc @@ -141,8 +141,9 @@ void LinechartWidget::createLayout() // Averaging spin box averageSpinBox = new QSpinBox(this); - averageSpinBox->setValue(2); + averageSpinBox->setValue(200); averageSpinBox->setMinimum(2); + averageSpinBox->setMaximum(9999); layout->addWidget(averageSpinBox, 1, 2); layout->setColumnStretch(2, 0); connect(averageSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setAverageWindow(int))); @@ -306,6 +307,8 @@ void LinechartWidget::stopLogging() logFile->close(); // Postprocess log file compressor = new LogCompressor(logFile->fileName()); + connect(compressor, SIGNAL(finishedFile(QString)), this, SIGNAL(logfileWritten(QString))); + compressor->startCompression(); } logButton->setText(tr("Start logging")); disconnect(logButton, SIGNAL(clicked()), this, SLOT(stopLogging())); diff --git a/src/ui/linechart/LinechartWidget.h b/src/ui/linechart/LinechartWidget.h index cd97c1a6d6e9508c11df6629b606e04f1e6f7fcf..26644f755d2ae76e3e15f10e7cf6dd939ca9cb4a 100644 --- a/src/ui/linechart/LinechartWidget.h +++ b/src/ui/linechart/LinechartWidget.h @@ -163,6 +163,9 @@ signals: void plotWindowPositionUpdated(quint64 position); void plotWindowPositionUpdated(int position); + /** @brief This signal is emitted once a logfile has been finished writing */ + void logfileWritten(QString fileName); + }; #endif // LINECHARTWIDGET_H diff --git a/src/ui/linechart/Linecharts.cc b/src/ui/linechart/Linecharts.cc index d904a1973da4aaba9b1dc465e85fccc73bbf32e2..004242451f1d7bdaa369adcf1225b4967007e926 100644 --- a/src/ui/linechart/Linecharts.cc +++ b/src/ui/linechart/Linecharts.cc @@ -55,6 +55,7 @@ void Linecharts::addSystem(UASInterface* uas) addWidget(widget); plots.insert(uas->getUASID(), widget); connect(uas, SIGNAL(valueChanged(int,QString,double,quint64)), widget, SLOT(appendData(int,QString,double,quint64))); + connect(widget, SIGNAL(logfileWritten(QString)), this, SIGNAL(logfileWritten(QString))); // Set system active if this is the only system if (active) { diff --git a/src/ui/linechart/Linecharts.h b/src/ui/linechart/Linecharts.h index a7342046240ddb99051cfbffd2cd405a0324655a..8bf9d602e8d705d9f03a3ddbd3460e935ee75489 100644 --- a/src/ui/linechart/Linecharts.h +++ b/src/ui/linechart/Linecharts.h @@ -14,6 +14,8 @@ public: explicit Linecharts(QWidget *parent = 0); signals: + /** @brief This signal is emitted once a logfile has been finished writing */ + void logfileWritten(QString fileName); public slots: /** @brief Set all plots active/inactive */