QGCXYPlot.cc 16.4 KB
Newer Older
John Tapsell's avatar
John Tapsell committed
1 2 3 4 5 6 7 8
#include <QDockWidget>

#include "QGCXYPlot.h"
#include "ui_QGCXYPlot.h"

#include "MAVLinkProtocol.h"
#include "UASManager.h"
#include "IncrementalPlot.h"
9 10
#include "QGCApplication.h"

John Tapsell's avatar
John Tapsell committed
11 12 13 14 15 16 17 18 19
#include <float.h>
#include <qwt_plot.h>
#include <qwt_plot_layout.h>
#include <qwt_scale_engine.h>

class XYPlotCurve : public QwtPlotItem
{
public:
    XYPlotCurve() {
20 21
        m_maxStorePoints = 10000;
        m_maxShowPoints = 15;
John Tapsell's avatar
John Tapsell committed
22 23 24
        setItemAttribute(QwtPlotItem::AutoScale);
        minMaxSet = false;
        m_color = Qt::white;
25
        m_smoothPoints = 1;
26
        m_startIndex = -1; //Disable
John Tapsell's avatar
John Tapsell committed
27 28
    }

29 30
    void setMaxDataStorePoints(int max) { m_maxStorePoints = max; itemChanged(); }
    void setMaxDataShowPoints(int max) { m_maxShowPoints = max; itemChanged(); }
31
    void setSmoothPoints(int smoothPoints) { m_smoothPoints = smoothPoints; itemChanged(); }
32 33
    int maxDataStorePoints() const { return m_maxStorePoints; }
    int maxDataShowPoints() const { return m_maxShowPoints; }
34
    int smoothPoints() const { return m_smoothPoints; }
John Tapsell's avatar
John Tapsell committed
35

36 37
    /** Append data, returning the number of removed items */
    int appendData(const QPointF &data) {
38 39 40 41 42 43 44
        m_data.append(data);
        int removed = 0;
        while (m_data.size() > m_maxShowPoints) {
            ++removed;
            m_data.removeFirst();
        }
        if (!minMaxSet) {
John Tapsell's avatar
John Tapsell committed
45 46 47
            xmin = xmax = data.x();
            ymin = ymax = data.y();
            minMaxSet = true;
48 49 50 51 52 53 54 55
            previousTime =xmin;
        } else if (m_autoScale) {
            if (m_autoScaleTime) {
                xmin += removed * (data.x() - previousTime);
                previousTime = data.x();
            } else {
                xmin = qMin(qreal(xmin), data.x());
            }
Koen Kooi's avatar
Koen Kooi committed
56 57 58
            xmax = qMax(qreal(xmax), data.x());
            ymin = qMin(qreal(ymin), data.y());
            ymax = qMax(qreal(ymax), data.y());
John Tapsell's avatar
John Tapsell committed
59 60
        }
        itemChanged();
61
        return removed;
John Tapsell's avatar
John Tapsell committed
62 63 64 65 66 67 68 69 70
    }
    void clear() {
        minMaxSet = false;
        m_data.clear();
        itemChanged();
    }
    void setColor(const QColor &color) {
        m_color = color;
    }
71 72 73 74
    void setTimeSerie(bool state) {
         clear();
         m_autoScaleTime = state;
    }
John Tapsell's avatar
John Tapsell committed
75 76 77 78
    void unsetMinMax() {
        if(m_autoScale)
            return;
        m_autoScale = true;
79 80 81 82 83
        //Recalculate the automatic scale
        if(m_data.isEmpty())
            minMaxSet = false;
        else {
            minMaxSet = true;
84
            previousTime = xmax = xmin = m_data.at(0).x();
85 86
            ymax = ymin = m_data.at(0).y();
            for(int i = 1; i < m_data.size(); i++) {
Koen Kooi's avatar
Koen Kooi committed
87 88 89 90
                xmin = qMin(qreal(xmin), m_data.at(i).x());
                xmax = qMax(qreal(xmax), m_data.at(i).x());
                ymin = qMin(qreal(ymin), m_data.at(i).y());
                ymax = qMax(qreal(ymax), m_data.at(i).y());
91 92
            }
        }
John Tapsell's avatar
John Tapsell committed
93 94 95 96 97 98 99 100 101 102 103
    }
    void setMinMax(double xmin, double xmax, double ymin, double ymax )
    {
        this->xmin = xmin;
        this->xmax = xmax;
        this->ymin = ymin;
        this->ymax = ymax;
        m_autoScale = false;
        minMaxSet = true;
        itemChanged();
    }
104 105 106 107 108
    void setStartIndex(int time) {  /** Set to -1 to just use latest */
        m_startIndex = time;
        itemChanged();
    }
    int dataSize() const { return m_data.size(); }
John Tapsell's avatar
John Tapsell committed
109 110 111 112 113 114

    double xMin() const { return xmin; }
    double xMax() const { return xmax; }
    double yMin() const { return ymin; }
    double yMax() const { return ymax; }

115
    virtual QRectF boundingRect() const {
John Tapsell's avatar
John Tapsell committed
116
        if(!minMaxSet)
117 118
            return QRectF(1,1,-2,-2);
        return QRectF(xmin,ymin,xmax-xmin,ymax-ymin);
John Tapsell's avatar
John Tapsell committed
119 120 121
    }

    /* From QwtPlotItem.  Draw the complete series */
122
    virtual void draw (QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect) const
John Tapsell's avatar
John Tapsell committed
123 124 125 126 127
    {
        Q_UNUSED(canvasRect);
        QPointF lastPoint;
        if(m_data.isEmpty())
            return;
128 129
        QPointF smoothTotal(0,0);
        int smoothCount = 0;
130 131 132 133 134 135 136 137 138 139
        int start;
        int count;
        if(m_startIndex >= 0) {
            int end = qMin(m_startIndex, m_data.size()-1);
            start = qBound(0, end - m_maxShowPoints, m_data.size()-1);
            count = end - start;
        } else {
            start = qMax(0,m_data.size() - m_maxShowPoints);
            count = qMin(m_data.size()-start, m_maxShowPoints);
        }
140 141 142 143
        for(int i = qMax(0,start - m_smoothPoints); i < start; ++i) {
            smoothTotal += m_data.at(i);
            ++smoothCount;
        }
144
        for(int i = 0; i < count; ++i) {
145 146 147 148 149 150 151 152 153 154
            QPointF point = m_data.at(i+start);
            if(m_smoothPoints > 1) {
                smoothTotal += point;
                if(smoothCount >= m_smoothPoints) {
                    Q_ASSERT(i + start - m_smoothPoints >= 0);
                    smoothTotal -= m_data.at(i + start - m_smoothPoints);
                } else
                    ++smoothCount;
                point = smoothTotal/smoothCount;
            }
155
            QPointF paintCoord = QPointF(xMap.transform(point.x()), yMap.transform(point.y()));
156
            m_color.setAlpha((m_maxShowPoints - count + i)*255/m_maxShowPoints);
John Tapsell's avatar
John Tapsell committed
157
            p->setPen(m_color);
158
            if(i != 0)
John Tapsell's avatar
John Tapsell committed
159
                p->drawLine(lastPoint, paintCoord);
160
            if(i == count-1) {
John Tapsell's avatar
John Tapsell committed
161 162 163 164 165 166 167 168 169 170 171
                //Draw marker for first point
                const int marker_radius = 2;
                QRectF marker = QRectF(paintCoord.x()-marker_radius, paintCoord.y()-marker_radius, marker_radius*2+1,marker_radius*2+1);
                p->fillRect(marker,QBrush(m_color));
            }
            lastPoint = paintCoord;
        }
    }

private:
    QList< QPointF > m_data;
172 173
    int m_maxStorePoints;
    int m_maxShowPoints;
174
    int m_smoothPoints; /** Number of points to average across */
John Tapsell's avatar
John Tapsell committed
175 176
    mutable QColor m_color;

177
    double previousTime;
John Tapsell's avatar
John Tapsell committed
178 179 180 181 182 183
    double xmin;
    double xmax;
    double ymin;
    double ymax;
    bool minMaxSet;
    bool m_autoScale;
184
    bool m_autoScaleTime;
185
    int m_startIndex;
John Tapsell's avatar
John Tapsell committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
};

QGCXYPlot::QGCXYPlot(QWidget *parent) :
    QGCToolWidgetItem("XY Plot", parent),
    ui(new Ui::QGCXYPlot),
    plot(0),
    xycurve(0),
    x(0),
    x_timestamp_us(0),
    x_valid(false),
    y(0),
    y_timestamp_us(0),
    y_valid(false),
    max_timestamp_diff_us(10000) /* Default to 10ms tolerance between x and y values */


{
    uas = 0;
    ui->setupUi(this);
    plot = new QwtPlot();

207
    ui->xyPlotLayout->addWidget(plot);
John Tapsell's avatar
John Tapsell committed
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226

    connect(ui->editFinishButton, SIGNAL(clicked()), this, SLOT(endEditMode()));

    connect(MainWindow::instance(), SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)),
            this, SLOT(appendData(int,QString,QString,QVariant,quint64)));

    connect(ui->editXParam, SIGNAL(editTextChanged(QString)), this, SLOT(clearPlot()));
    connect(ui->editYParam, SIGNAL(editTextChanged(QString)), this, SLOT(clearPlot()));

    plot->plotLayout()->setAlignCanvasToScales(true);

    QwtLinearScaleEngine* yScaleEngine = new QwtLinearScaleEngine();
    plot->setAxisScaleEngine(QwtPlot::yLeft, yScaleEngine);

    plot->setAxisAutoScale(QwtPlot::xBottom);
    plot->setAxisAutoScale(QwtPlot::yLeft);
    plot->setAutoReplot();
    xycurve = new XYPlotCurve();
    xycurve->attach(plot);
227 228
    styleChanged(qgcApp()->styleIsDark());
    connect(qgcApp(), &QGCApplication::styleChanged, this, &QGCXYPlot::styleChanged);
John Tapsell's avatar
John Tapsell committed
229 230 231 232 233
    connect(ui->minX, SIGNAL(valueChanged(double)),this, SLOT(updateMinMaxSettings()));
    connect(ui->maxX, SIGNAL(valueChanged(double)),this, SLOT(updateMinMaxSettings()));
    connect(ui->minY, SIGNAL(valueChanged(double)),this, SLOT(updateMinMaxSettings()));
    connect(ui->maxY, SIGNAL(valueChanged(double)),this, SLOT(updateMinMaxSettings()));
    connect(ui->automaticAxisRange, SIGNAL(toggled(bool)),this, SLOT(updateMinMaxSettings()));
234
    connect(ui->timeAxisRange, SIGNAL(toggled(bool)),this, SLOT(setTimeAxis()));
235 236
    connect(ui->maxDataShowSpinBox, SIGNAL(valueChanged(int)),this, SLOT(updateMinMaxSettings()));
    connect(ui->maxDataStoreSpinBox, SIGNAL(valueChanged(int)),this, SLOT(updateMinMaxSettings()));
237
    connect(ui->smoothSpinBox, SIGNAL(valueChanged(int)),this, SLOT(updateMinMaxSettings()));
John Tapsell's avatar
John Tapsell committed
238 239 240 241 242 243 244 245 246 247 248
    setEditMode(false);
}

QGCXYPlot::~QGCXYPlot()
{
    delete ui;
}

void QGCXYPlot::clearPlot()
{
    xycurve->clear();
249
    plot->detachItems();
250 251
    ui->timeScrollBar->setMaximum(xycurve->dataSize());
    ui->timeScrollBar->setValue(ui->timeScrollBar->maximum());
John Tapsell's avatar
John Tapsell committed
252 253 254 255 256 257 258 259 260 261 262
}

void QGCXYPlot::setEditMode(bool editMode)
{
    ui->lblXParam->setVisible(editMode);
    ui->lblYParam->setVisible(editMode);
    ui->editXParam->setVisible(editMode);
    ui->editYParam->setVisible(editMode);
    ui->editFinishButton->setVisible(editMode);
    ui->editLine1->setVisible(editMode);
    ui->editLine2->setVisible(editMode);
263 264
    ui->lblMaxDataStore->setVisible(editMode);
    ui->lblMaxDataShow->setVisible(editMode);
John Tapsell's avatar
John Tapsell committed
265 266 267 268 269 270 271 272
    ui->lblMaxX->setVisible(editMode);
    ui->lblMaxY->setVisible(editMode);
    ui->lblMinX->setVisible(editMode);
    ui->lblMinY->setVisible(editMode);
    ui->maxX->setVisible(editMode);
    ui->maxY->setVisible(editMode);
    ui->minX->setVisible(editMode);
    ui->minY->setVisible(editMode);
273 274
    ui->maxDataShowSpinBox->setVisible(editMode);
    ui->maxDataStoreSpinBox->setVisible(editMode);
John Tapsell's avatar
John Tapsell committed
275
    ui->automaticAxisRange->setVisible(editMode);
276
    ui->timeAxisRange->setVisible(editMode);
277 278
    ui->lblSmooth->setVisible(editMode);
    ui->smoothSpinBox->setVisible(editMode);
John Tapsell's avatar
John Tapsell committed
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297

    if(!editMode) {
        plot->setAxisTitle(QwtPlot::xBottom, ui->editXParam->currentText());
        plot->setAxisTitle(QwtPlot::yLeft, ui->editYParam->currentText());
    }

    QGCToolWidgetItem::setEditMode(editMode);
    updateMinMaxSettings(); //Do this after calling the parent
}

void QGCXYPlot::writeSettings(QSettings& settings)
{
    settings.setValue("TYPE", "XYPLOT");
    settings.setValue("QGC_XYPLOT_X", ui->editXParam->currentText());
    settings.setValue("QGC_XYPLOT_Y", ui->editYParam->currentText());
    settings.setValue("QGC_XYPLOT_MINX", ui->minX->value());
    settings.setValue("QGC_XYPLOT_MAXX", ui->maxX->value());
    settings.setValue("QGC_XYPLOT_MINY", ui->minY->value());
    settings.setValue("QGC_XYPLOT_MAXY", ui->maxY->value());
298 299
    settings.setValue("QGC_XYPLOT_MAXDATA_STORE", ui->maxDataStoreSpinBox->value());
    settings.setValue("QGC_XYPLOT_MAXDATA_SHOW", ui->maxDataShowSpinBox->value());
John Tapsell's avatar
John Tapsell committed
300
    settings.setValue("QGC_XYPLOT_AUTO", ui->automaticAxisRange->isChecked());
301
    settings.setValue("QGC_XYPLOT_SMOOTH", ui->smoothSpinBox->value());
302
    settings.setValue("QGC_XYPLOT_TIME", ui->timeAxisRange->isChecked());
John Tapsell's avatar
John Tapsell committed
303 304 305 306 307 308 309 310 311 312 313
    settings.sync();
}
void QGCXYPlot::readSettings(const QString& pre,const QVariantMap& settings)
{
    ui->editXParam->setEditText(settings.value(pre + "QGC_XYPLOT_X", "").toString());
    ui->editYParam->setEditText(settings.value(pre + "QGC_XYPLOT_Y", "").toString());
    ui->automaticAxisRange->setChecked(settings.value(pre + "QGC_XYPLOT_AUTO", true).toBool());
    ui->minX->setValue(settings.value(pre + "QGC_XYPLOT_MINX", 0).toDouble());
    ui->maxX->setValue(settings.value(pre + "QGC_XYPLOT_MAXX", 0).toDouble());
    ui->minY->setValue(settings.value(pre + "QGC_XYPLOT_MINY", 0).toDouble());
    ui->maxY->setValue(settings.value(pre + "QGC_XYPLOT_MAXY", 0).toDouble());
314 315
    ui->maxDataStoreSpinBox->setValue(settings.value(pre + "QGC_XYPLOT_MAXDATA_STORE", 10000).toInt());
    ui->maxDataShowSpinBox->setValue(settings.value(pre + "QGC_XYPLOT_MAXDATA_SHOW", 15).toInt());
316
    ui->smoothSpinBox->setValue(settings.value(pre + "QGC_XYPLOT_SMOOTH", 1).toInt());
317
    ui->timeAxisRange->setChecked(settings.value(pre + "QGC_XYPLOT_TIME", true).toBool());
John Tapsell's avatar
John Tapsell committed
318 319 320 321 322 323 324 325 326 327 328 329 330 331
    plot->setAxisTitle(QwtPlot::xBottom, ui->editXParam->currentText());
    plot->setAxisTitle(QwtPlot::yLeft, ui->editYParam->currentText());
    updateMinMaxSettings();
}

void QGCXYPlot::readSettings(const QSettings& settings)
{
    ui->editXParam->setEditText(settings.value("QGC_XYPLOT_X", "").toString());
    ui->editYParam->setEditText(settings.value("QGC_XYPLOT_Y", "").toString());
    ui->automaticAxisRange->setChecked(settings.value("QGC_XYPLOT_AUTO", true).toBool());
    ui->minX->setValue(settings.value("QGC_XYPLOT_MINX", 0).toDouble());
    ui->maxX->setValue(settings.value("QGC_XYPLOT_MAXX", 0).toDouble());
    ui->minY->setValue(settings.value("QGC_XYPLOT_MINY", 0).toDouble());
    ui->maxY->setValue(settings.value("QGC_XYPLOT_MAXY", 0).toDouble());
332 333
    ui->maxDataStoreSpinBox->setValue(settings.value("QGC_XYPLOT_MAXDATA_STORE", 10000).toInt());
    ui->maxDataShowSpinBox->setValue(settings.value("QGC_XYPLOT_MAXDATA_SHOW", 15).toInt());
334
    ui->smoothSpinBox->setValue(settings.value("QGC_XYPLOT_SMOOTH", 1).toInt());
335
    ui->timeAxisRange->setChecked(settings.value("QGC_XYPLOT_TIME", true).toBool());
John Tapsell's avatar
John Tapsell committed
336 337 338 339 340 341 342 343 344 345 346 347
    plot->setAxisTitle(QwtPlot::xBottom, ui->editXParam->currentText());
    plot->setAxisTitle(QwtPlot::yLeft, ui->editYParam->currentText());
    updateMinMaxSettings();
}

void QGCXYPlot::appendData(int uasId, const QString& curve, const QString& unit, const QVariant& variant, quint64 usec)
{
    Q_UNUSED(uasId);
    Q_UNUSED(unit);
    if(isEditMode()) {
        //When in edit mode, add all the items to the combo box
        if(ui->editXParam->findText(curve) == -1) {
348 349
            ui->editXParam->blockSignals(true);
            ui->editYParam->blockSignals(true);
John Tapsell's avatar
John Tapsell committed
350 351 352 353 354 355
            QString oldX = ui->editXParam->currentText();
            QString oldY = ui->editYParam->currentText();
            ui->editXParam->addItem(curve); //Annoyingly this can wipe out the current text
            ui->editYParam->addItem(curve);
            ui->editXParam->setEditText(oldX);
            ui->editYParam->setEditText(oldY);
356 357
            ui->editXParam->blockSignals(false);
            ui->editYParam->blockSignals(false);
John Tapsell's avatar
John Tapsell committed
358 359 360
        }
    }

361 362 363
    if(ui->stopStartButton->isChecked())
        return;

John Tapsell's avatar
John Tapsell committed
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
    bool ok;
    if(curve == ui->editXParam->currentText()) {
        x = variant.toDouble(&ok);
        if(!ok)
            return;
        x_timestamp_us = usec;
        x_valid = true;
    } else if(curve == ui->editYParam->currentText()) {
        y = variant.toDouble(&ok);
        if(!ok)
            return;
        y_timestamp_us = usec;
        y_valid = true;
    } else
        return;

Don Gagne's avatar
Don Gagne committed
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
    if(x_valid && y_valid) {
        quint64 difference;
        if (y_timestamp_us < x_timestamp_us) {
            difference = x_timestamp_us - y_timestamp_us;
        } else {
            difference = y_timestamp_us - x_timestamp_us;
        }
        if (difference <= max_timestamp_diff_us) {
            int removed = xycurve->appendData( QPointF(x,y) );
            x_valid = false;
            y_valid = false;
            bool atMaximum = (ui->timeScrollBar->value() == ui->timeScrollBar->maximum());
            if(ui->timeScrollBar->maximum() != xycurve->dataSize()) {
                ui->timeScrollBar->setMaximum(xycurve->dataSize());
                if(atMaximum)
                    ui->timeScrollBar->setValue(ui->timeScrollBar->maximum());
            } else if(!atMaximum) { //Move the scrollbar to keep current value selected
                int value = qMax(ui->timeScrollBar->minimum(), ui->timeScrollBar->value() - removed);
                ui->timeScrollBar->setValue(value);
                xycurve->setStartIndex(value);
            }
401
        }
John Tapsell's avatar
John Tapsell committed
402 403 404
    }
}

405
void QGCXYPlot::styleChanged(bool styleIsDark)
John Tapsell's avatar
John Tapsell committed
406
{
407
    xycurve->setColor(styleIsDark ? Qt::white : Qt::black);
John Tapsell's avatar
John Tapsell committed
408 409 410 411 412 413 414 415 416 417 418 419 420 421
}

void QGCXYPlot::updateMinMaxSettings()
{
    bool automatic = ui->automaticAxisRange->isChecked();
    ui->minX->setEnabled(!automatic);
    ui->maxX->setEnabled(!automatic);
    ui->minY->setEnabled(!automatic);
    ui->maxY->setEnabled(!automatic);
    if(automatic) {
        xycurve->unsetMinMax();
    } else {
        xycurve->setMinMax(ui->minX->value(), ui->maxX->value(), ui->minY->value(), ui->maxY->value());
    }
422 423
    xycurve->setMaxDataStorePoints(ui->maxDataStoreSpinBox->value());
    xycurve->setMaxDataShowPoints(ui->maxDataShowSpinBox->value());
424
    xycurve->setSmoothPoints(ui->smoothSpinBox->value());
425 426
}

427 428 429 430 431
void QGCXYPlot::setTimeAxis()
{
    xycurve->setTimeSerie(ui->timeAxisRange->isChecked());
}

432 433 434 435 436
void QGCXYPlot::on_maxDataShowSpinBox_valueChanged(int value)
{
    ui->maxDataStoreSpinBox->setMinimum(value);
    if(ui->maxDataStoreSpinBox->value() < value)
        ui->maxDataStoreSpinBox->setValue(value);
John Tapsell's avatar
John Tapsell committed
437
}
438 439 440 441 442 443 444 445 446 447 448 449 450 451

void QGCXYPlot::on_stopStartButton_toggled(bool checked)
{
    if(!checked)
        clearPlot();
}

void QGCXYPlot::on_timeScrollBar_valueChanged(int value)
{
    if(value == ui->timeScrollBar->maximum())
        xycurve->setStartIndex(-1);
    else
        xycurve->setStartIndex(value);
}