Browse Source

Significantly improved plotting, now capturing data in linechart allows limmediate processing / analysis of the whole flight

QGC4.4
pixhawk 15 years ago
parent
commit
b135d3219d
  1. 12
      price.txt
  2. 10
      src/LogCompressor.cc
  3. 10
      src/LogCompressor.h
  4. 8
      src/ui/MainWindow.cc
  5. 2
      src/ui/MainWindow.h
  6. 197
      src/ui/QGCDataPlot2D.cc
  7. 13
      src/ui/QGCDataPlot2D.h
  8. 176
      src/ui/QGCDataPlot2D.ui
  9. 180
      src/ui/linechart/IncrementalPlot.cc
  10. 105
      src/ui/linechart/IncrementalPlot.h
  11. 12
      src/ui/linechart/LinechartPlot.cc
  12. 5
      src/ui/linechart/LinechartWidget.cc
  13. 3
      src/ui/linechart/LinechartWidget.h
  14. 1
      src/ui/linechart/Linecharts.cc
  15. 2
      src/ui/linechart/Linecharts.h

12
price.txt

@ -0,0 +1,12 @@ @@ -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

10
src/LogCompressor.cc

@ -36,6 +36,9 @@ This file is part of the QGROUNDCONTROL project @@ -36,6 +36,9 @@ This file is part of the QGROUNDCONTROL project
#include <QDebug>
/**
* 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 @@ -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() @@ -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;

10
src/LogCompressor.h

@ -5,11 +5,15 @@ @@ -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: @@ -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

8
src/ui/MainWindow.cc

@ -145,6 +145,7 @@ void MainWindow::connectWidgets() @@ -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() @@ -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();

2
src/ui/MainWindow.h

@ -119,6 +119,8 @@ public slots: @@ -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();

197
src/ui/QGCDataPlot2D.cc

@ -27,6 +27,7 @@ QGCDataPlot2D::QGCDataPlot2D(QWidget *parent) : @@ -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) : @@ -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() @@ -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() @@ -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) @@ -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 @@ -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 @@ -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 @@ -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 @@ -302,12 +378,15 @@ void QGCDataPlot2D::loadCsvLog(QString file, QString xAxisName, QString yAxisFil
QVector<double> xValues;
QMap<QString, QVector<double>* > 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 @@ -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<double>());
// 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 @@ -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 @@ -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 @@ -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;
}
/**

13
src/ui/QGCDataPlot2D.h

@ -16,11 +16,17 @@ public: @@ -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: @@ -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: @@ -40,6 +50,7 @@ protected:
LogCompressor* compressor;
QFile* logFile;
QString fileName;
QStringList curveNames;
private:
Ui::QGCDataPlot2D *ui;

176
src/ui/QGCDataPlot2D.ui

@ -6,22 +6,22 @@ @@ -6,22 +6,22 @@
<rect>
<x>0</x>
<y>0</y>
<width>807</width>
<width>1073</width>
<height>308</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="2,10,2,0,0,0,0,0,0,0,0,0,0">
<item row="0" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>X</string>
</property>
</widget>
</item>
<item row="0" column="2">
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="xAxis"/>
</item>
<item row="0" column="3">
@ -53,7 +53,12 @@ @@ -53,7 +53,12 @@
</item>
<item>
<property name="text">
<string>Only dots</string>
<string>Only rectangles</string>
</property>
</item>
<item>
<property name="text">
<string>Only symbols</string>
</property>
</item>
<item>
@ -68,7 +73,12 @@ @@ -68,7 +73,12 @@
</item>
<item>
<property name="text">
<string>Lines and dots</string>
<string>Lines and rects</string>
</property>
</item>
<item>
<property name="text">
<string>Lines and symbols</string>
</property>
</item>
<item>
@ -83,7 +93,7 @@ @@ -83,7 +93,7 @@
</item>
<item>
<property name="text">
<string>Dotted lines and dots</string>
<string>Dotted lines and rects</string>
</property>
</item>
<item>
@ -98,39 +108,91 @@ @@ -98,39 +108,91 @@
</item>
<item>
<property name="text">
<string>Dashed lines and dots</string>
<string>Dashed lines and rects</string>
</property>
</item>
</widget>
</item>
<item row="0" column="9">
<item row="0" column="7" colspan="2">
<widget class="QPushButton" name="reloadButton">
<property name="text">
<string>Reload</string>
<string>Replot</string>
</property>
</widget>
</item>
<item row="0" column="10">
<spacer name="horizontalSpacer_2">
<item row="0" column="11">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</widget>
</item>
<item row="0" column="11">
<item row="0" column="12" colspan="2">
<widget class="QPushButton" name="savePlotButton">
<property name="text">
<string>Save Plot</string>
<string>Save Image</string>
</property>
</widget>
</item>
<item row="0" column="19">
<widget class="QPushButton" name="printButton">
<property name="text">
<string>Print</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Title</string>
</property>
</widget>
</item>
<item row="1" column="2" colspan="3">
<widget class="QLineEdit" name="plotTitle"/>
</item>
<item row="1" column="5">
<widget class="QLabel" name="label_5">
<property name="text">
<string>X label</string>
</property>
</widget>
</item>
<item row="1" column="6" colspan="2">
<widget class="QLineEdit" name="plotXAxisLabel"/>
</item>
<item row="1" column="8">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Y label</string>
</property>
</widget>
</item>
<item row="1" column="9" colspan="3">
<widget class="QLineEdit" name="plotYAxisLabel"/>
</item>
<item row="1" column="12" colspan="2">
<widget class="QCheckBox" name="symmetricCheckBox">
<property name="text">
<string>Symmetric</string>
</property>
</widget>
</item>
<item row="1" column="14" colspan="2">
<widget class="QCheckBox" name="legendCheckBox">
<property name="text">
<string>Legend</string>
</property>
</widget>
</item>
<item row="1" column="16">
<widget class="QCheckBox" name="gridCheckBox">
<property name="text">
<string>Grid</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="13">
<item row="2" column="0" colspan="20">
<widget class="QFrame" name="plotFrame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
@ -140,14 +202,14 @@ @@ -140,14 +202,14 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>File</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<item row="3" column="1" colspan="2">
<widget class="QComboBox" name="inputFileType">
<item>
<property name="text">
@ -166,14 +228,21 @@ @@ -166,14 +228,21 @@
</item>
</widget>
</item>
<item row="2" column="3" colspan="4">
<item row="3" column="3" colspan="4">
<widget class="QLabel" name="filenameLabel">
<property name="text">
<string>Please select input file..</string>
</property>
</widget>
</item>
<item row="2" column="10">
<item row="3" column="8" colspan="2">
<widget class="QPushButton" name="selectFileButton">
<property name="text">
<string>Select file</string>
</property>
</widget>
</item>
<item row="3" column="10" colspan="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -186,41 +255,60 @@ @@ -186,41 +255,60 @@
</property>
</spacer>
</item>
<item row="0" column="12">
<widget class="QPushButton" name="printButton">
<property name="text">
<string>Print</string>
<item row="3" column="12">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="2" column="12">
<widget class="QPushButton" name="saveCsvButton">
<item row="3" column="13">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Save CSV</string>
<string>Regression</string>
</property>
</widget>
</item>
<item row="2" column="9">
<widget class="QPushButton" name="selectFileButton">
<item row="3" column="14" colspan="2">
<widget class="QComboBox" name="xRegressionComboBox"/>
</item>
<item row="3" column="16" colspan="2">
<widget class="QComboBox" name="yRegressionComboBox"/>
</item>
<item row="3" column="19">
<widget class="QPushButton" name="regressionButton">
<property name="text">
<string>Select file</string>
<string>Calculate</string>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QCheckBox" name="symmetricCheckBox">
<property name="text">
<string>Symmetric</string>
<item row="3" column="18">
<widget class="QLineEdit" name="regressionOutput">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="8">
<widget class="QCheckBox" name="legendCheckBox">
<item row="0" column="14">
<widget class="QPushButton" name="saveCsvButton">
<property name="text">
<string>Legend</string>
<string>Save Data</string>
</property>
</widget>
</item>
<item row="0" column="9" colspan="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>

180
src/ui/linechart/IncrementalPlot.cc

@ -1,3 +1,33 @@ @@ -1,3 +1,33 @@
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
/**
* @file
* @brief Implementation of class IncrementalPlot
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#include <qwt_plot.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_curve.h>
@ -9,9 +39,9 @@ @@ -9,9 +39,9 @@
#include <Scrollbar.h>
#include <ScrollZoomer.h>
#include <float.h>
#if QT_VERSION >= 0x040000
#include <qpaintengine.h>
#endif
#include <QDebug>
CurveData::CurveData():
d_count(0)
@ -45,18 +75,23 @@ int CurveData::size() const @@ -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): @@ -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): @@ -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): @@ -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() @@ -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) @@ -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) @@ -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) @@ -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) @@ -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() @@ -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) @@ -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) @@ -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) @@ -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<QColor> IncrementalPlot::getColorMap()
@ -330,7 +470,7 @@ QColor IncrementalPlot::getNextColor() @@ -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++];
}

105
src/ui/linechart/IncrementalPlot.h

@ -1,3 +1,33 @@ @@ -1,3 +1,33 @@
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
/**
* @file
* @brief Defition of class IncrementalPlot
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#ifndef INCREMENTALPLOT_H
#define INCREMENTALPLOT_H
@ -5,21 +35,25 @@ @@ -5,21 +35,25 @@
#include <qwt_array.h>
#include <qwt_plot.h>
#include <qwt_legend.h>
#include <qwt_plot_grid.h>
#include <QMap>
#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: @@ -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<QColor> 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<QColor> colors;
int nextColor;
ScrollZoomer* zoomer;
QwtLegend* legend;
double xmin;
double xmax;
double ymin;
double ymax;
bool symmetric; ///< Enable symmetric plotting
QList<QColor> 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<QString, CurveData* > d_data;
QMap<QString, QwtPlotCurve* > d_curve;
QMap<QString, CurveData* > d_data; ///< Data points
QMap<QString, QwtPlotCurve* > d_curve; ///< Plot curves
};
#endif /* INCREMENTALPLOT_H */

12
src/ui/linechart/LinechartPlot.cc

@ -136,7 +136,7 @@ d_curve(NULL) @@ -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() @@ -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) @@ -762,15 +762,15 @@ void TimeSeriesData::append(quint64 ms, double value)
mean = mean / static_cast<double>(qMin(averageWindow,static_cast<unsigned int>(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);
}
}

5
src/ui/linechart/LinechartWidget.cc

@ -141,8 +141,9 @@ void LinechartWidget::createLayout() @@ -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() @@ -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()));

3
src/ui/linechart/LinechartWidget.h

@ -163,6 +163,9 @@ signals: @@ -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

1
src/ui/linechart/Linecharts.cc

@ -55,6 +55,7 @@ void Linecharts::addSystem(UASInterface* uas) @@ -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)
{

2
src/ui/linechart/Linecharts.h

@ -14,6 +14,8 @@ public: @@ -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 */

Loading…
Cancel
Save