IncrementalPlot.cc 10 KB
Newer Older
1 2 3 4 5 6 7 8
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
9 10 11 12 13 14 15 16 17


/**
 * @file
 *   @brief Implementation of class IncrementalPlot
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

18 19 20 21 22 23 24 25 26 27 28 29
#include <qwt_plot.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_curve.h>
#include <qwt_symbol.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_grid.h>
#include <qwt_scale_engine.h>
#include "IncrementalPlot.h"
#include <Scrollbar.h>
#include <ScrollZoomer.h>
#include <float.h>
#include <qpaintengine.h>
30 31

#include <QDebug>
32 33

CurveData::CurveData():
34
    d_count(0)
35 36 37 38 39 40
{
}

void CurveData::append(double *x, double *y, int count)
{
    int newSize = ( (d_count + count) / 1000 + 1 ) * 1000;
41
    if ( newSize > size() ) {
42 43 44 45
        d_x.resize(newSize);
        d_y.resize(newSize);
    }

dogmaphobic's avatar
dogmaphobic committed
46
    for ( int i = 0; i < count; i++ ) {
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
        d_x[d_count + i] = x[i];
        d_y[d_count + i] = y[i];
    }
    d_count += count;
}

int CurveData::count() const
{
    return d_count;
}

int CurveData::size() const
{
    return d_x.size();
}

63
const double* CurveData::x() const
64 65 66 67
{
    return d_x.data();
}

68
const double* CurveData::y() const
69 70 71 72 73
{
    return d_y.data();
}

IncrementalPlot::IncrementalPlot(QWidget *parent):
74
    ChartPlot(parent),
75
    symmetric(false)
76
{
77
    setStyleText("solid crosses");
78 79 80 81 82 83 84 85 86 87 88

    plotLayout()->setAlignCanvasToScales(true);

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

    setAxisAutoScale(xBottom);
    setAxisAutoScale(yLeft);

    resetScaling();

89
    legend = NULL;
90 91 92 93 94 95 96
}

IncrementalPlot::~IncrementalPlot()
{

}

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
/**
 * @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();
}

114 115
void IncrementalPlot::showLegend(bool show)
{
116 117
    if (show) {
        if (legend == NULL) {
118 119
            legend = new QwtLegend;
            legend->setFrameStyle(QFrame::Box);
120
            legend->setDefaultItemMode(QwtLegendData::Checkable);
121 122
        }
        insertLegend(legend, QwtPlot::RightLegend);
123
    } else {
124 125 126
        delete legend;
        legend = NULL;
    }
127
    updateScale(); // Updates the scaling at replots
128 129 130
}

/**
Ricardo de Almeida Gonzaga's avatar
Ricardo de Almeida Gonzaga committed
131
 * Set datapoint and line style. This interface is intended
132 133 134 135 136 137 138 139 140 141 142
 * to be directly connected to the UI and allows to parse
 * human-readable, textual descriptions into plot specs.
 *
 * Data points: Either "circles", "crosses" or the default "dots"
 * Lines: Either "dotted", ("solid"/"line") or no lines if not used
 *
 * No special formatting is needed, as long as the keywords are contained
 * in the string. Lower/uppercase is ignored as well.
 *
 * @param style Formatting string for line/data point style
 */
143
void IncrementalPlot::setStyleText(const QString &style)
144
{
145
    styleText = style.toLower();
Gus Grubba's avatar
Gus Grubba committed
146
    foreach (QwtPlotCurve* curve, _curves) {
147 148 149 150
        updateStyle(curve);
    }
    replot();
}
151

152 153 154 155
void IncrementalPlot::updateStyle(QwtPlotCurve *curve)
{
    if(styleText.isNull())
        return;
156 157 158 159 160 161 162

    // Since the symbols always use the same color as the curve line, we just use that color.
    // This saves us from having to deal with cases where the symbol is NULL.
    QColor oldColor = curve->pen().color();

    // Update the symbol style
    QwtSymbol *newSymbol = NULL;
163
    if (styleText.contains("circles")) {
Gus Grubba's avatar
Gus Grubba committed
164
        newSymbol = new QwtSymbol(QwtSymbol::Ellipse, Qt::NoBrush, QPen(oldColor, _symbolWidth), QSize(6, 6));
165
    } else if (styleText.contains("crosses")) {
Gus Grubba's avatar
Gus Grubba committed
166
        newSymbol = new QwtSymbol(QwtSymbol::XCross, Qt::NoBrush, QPen(oldColor, _symbolWidth), QSize(5, 5));
167
    } else if (styleText.contains("rect")) {
Gus Grubba's avatar
Gus Grubba committed
168
        newSymbol = new QwtSymbol(QwtSymbol::Rect, Qt::NoBrush, QPen(oldColor, _symbolWidth), QSize(6, 6));
169
    }
170 171
    // Else-case already handled by NULL value, which indicates no symbol
    curve->setSymbol(newSymbol);
172

173
    // Update the line style
174
    if (styleText.contains("dotted")) {
Gus Grubba's avatar
Gus Grubba committed
175
        curve->setPen(QPen(oldColor, _curveWidth, Qt::DotLine));
176
    } else if (styleText.contains("dashed")) {
Gus Grubba's avatar
Gus Grubba committed
177
        curve->setPen(QPen(oldColor, _curveWidth, Qt::DashLine));
178
    } else if (styleText.contains("line") || styleText.contains("solid")) {
Gus Grubba's avatar
Gus Grubba committed
179
        curve->setPen(QPen(oldColor, _curveWidth, Qt::SolidLine));
180
    } else {
Gus Grubba's avatar
Gus Grubba committed
181
        curve->setPen(QPen(oldColor, _curveWidth, Qt::NoPen));
182
    }
183
    curve->setStyle(QwtPlotCurve::Lines);
184 185
}

186 187 188 189
void IncrementalPlot::resetScaling()
{
    xmin = 0;
    xmax = 500;
190 191
    ymin = xmin;
    ymax = xmax;
192 193 194 195 196 197 198 199 200 201 202 203 204

    setAxisScale(xBottom, xmin+xmin*0.05, xmax+xmax*0.05);
    setAxisScale(yLeft, ymin+ymin*0.05, ymax+ymax*0.05);

    replot();

    // Make sure the first data access hits these
    xmin = DBL_MAX;
    xmax = DBL_MIN;
    ymin = DBL_MAX;
    ymax = DBL_MIN;
}

205 206 207 208 209 210
/**
 * Updates the scale calculation and re-plots the whole plot
 */
void IncrementalPlot::updateScale()
{
    const double margin = 0.05;
211 212 213
    if(xmin == DBL_MAX)
        return;

LM's avatar
LM committed
214 215 216 217
    double xMinRange = xmin-(qAbs(xmin*margin));
    double xMaxRange = xmax+(qAbs(xmax*margin));
    double yMinRange = ymin-(qAbs(ymin*margin));
    double yMaxRange = ymax+(qAbs(ymax*margin));
218
    if (symmetric) {
219 220 221 222 223 224 225 226 227 228
        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;

229
        if (xRange > yRange) {
230
            double yCenter = yMinRange + yRange/2.0;
231
            double xCenter = xMinRange + xRange/2.0;
232 233
            yMinRange = yCenter - xRange/2.0;
            yMaxRange = yCenter + xRange/2.0;
234 235
            xMinRange = xCenter - (xRange*aspectRatio)/2.0;
            xMaxRange = xCenter + (xRange*aspectRatio)/2.0;
236
        } else {
237
            double xCenter = xMinRange + xRange/2.0;
238 239
            xMinRange = xCenter - (yRange*aspectRatio)/2.0;
            xMaxRange = xCenter + (yRange*aspectRatio)/2.0;
240 241 242 243 244 245
        }
    }
    setAxisScale(xBottom, xMinRange, xMaxRange);
    setAxisScale(yLeft, yMinRange, yMaxRange);
}

246
void IncrementalPlot::appendData(const QString &key, double x, double y)
247 248 249 250
{
    appendData(key, &x, &y, 1);
}

251
void IncrementalPlot::appendData(const QString &key, double *x, double *y, int size)
252 253 254
{
    CurveData* data;
    QwtPlotCurve* curve;
255
    if (!d_data.contains(key)) {
256 257
        data = new CurveData;
        d_data.insert(key, data);
258
    } else {
259 260 261
        data = d_data.value(key);
    }

262
    // If this is a new curve, create it.
Gus Grubba's avatar
Gus Grubba committed
263
    if (!_curves.contains(key)) {
264
        curve = new QwtPlotCurve(key);
Gus Grubba's avatar
Gus Grubba committed
265
        _curves.insert(key, curve);
266
        curve->setStyle(QwtPlotCurve::NoCurve);
267
        curve->setPaintAttribute(QwtPlotCurve::FilterPoints);
268

269
        // Set the color. Only the pen needs to be set
270
        const QColor &c = getNextColor();
Gus Grubba's avatar
Gus Grubba committed
271
        curve->setPen(c, _symbolWidth);
272 273 274 275

        qDebug() << "Creating curve" << key << "with color" << c;

        updateStyle(curve);
276
        curve->attach(this);
277
    } else {
Gus Grubba's avatar
Gus Grubba committed
278
        curve = _curves.value(key);
279 280 281
    }

    data->append(x, y, size);
282
    curve->setRawSamples(data->x(), data->y(), data->count());
283 284 285 286

    bool scaleChanged = false;

    // Update scales
287 288
    for (int i = 0; i<size; i++) {
        if (x[i] < xmin) {
289 290 291
            xmin = x[i];
            scaleChanged = true;
        }
292
        if (x[i] > xmax) {
293 294 295 296
            xmax = x[i];
            scaleChanged = true;
        }

297
        if (y[i] < ymin) {
298 299 300
            ymin = y[i];
            scaleChanged = true;
        }
301
        if (y[i] > ymax) {
302 303 304 305 306 307 308 309 310 311 312 313 314
            ymax = y[i];
            scaleChanged = true;
        }
    }
    //    setAxisScale(xBottom, xmin+xmin*0.05, xmax+xmax*0.05);
    //    setAxisScale(yLeft, ymin+ymin*0.05, ymax+ymax*0.05);

    //#ifdef __GNUC__
    //#warning better use QwtData
    //#endif

    //replot();

315
    if(scaleChanged) {
316
        updateScale();
317
    } else {
318

319 320
        QwtPlotCanvas *c = static_cast<QwtPlotCanvas*>(canvas());
        const bool cacheMode = c->testPaintAttribute(QwtPlotCanvas::BackingStore);
321

322
        c->setPaintAttribute(QwtPlotCanvas::BackingStore, false);
323
        // FIXME Check if here all curves should be drawn
324
        //        QwtPlotCurve* plotCurve;
325
        //        foreach(plotCurve, curves)
326 327 328
        //        {
        //            plotCurve->draw(0, curve->dataSize()-1);
        //        }
329

330 331 332 333
        // FIXME: Unsure what this call should be now.
        //curve->draw(curve->dataSize() - size, curve->dataSize() - 1);
        replot();
        c->setPaintAttribute(QwtPlotCanvas::BackingStore, cacheMode);
334

335 336
    }
}
337

338 339 340
/**
 * @return Number of copied data points, 0 on failure
 */
341
int IncrementalPlot::data(const QString &key, double* r_x, double* r_y, int maxSize)
342 343
{
    int result = 0;
344
    if (d_data.contains(key)) {
345
        CurveData* d = d_data.value(key);
346
        if (maxSize >= d->count()) {
347 348 349
            result = d->count();
            memcpy(r_x, d->x(), sizeof(double) * d->count());
            memcpy(r_y, d->y(), sizeof(double) * d->count());
350
        } else {
351 352
            result = 0;
        }
353
    }
354 355 356 357 358 359 360 361
    return result;
}

/**
 * @param show true to show the grid, false else
 */
void IncrementalPlot::showGrid(bool show)
{
Gus Grubba's avatar
Gus Grubba committed
362
    _grid->setVisible(show);
363 364
    replot();
}
365

366
bool IncrementalPlot::gridEnabled() const
367
{
Gus Grubba's avatar
Gus Grubba committed
368
    return _grid->isVisible();
369 370 371 372
}

void IncrementalPlot::removeData()
{
Gus Grubba's avatar
Gus Grubba committed
373
    foreach (QwtPlotCurve* curve, _curves) {
pixhawk's avatar
pixhawk committed
374 375
        delete curve;
    }
Gus Grubba's avatar
Gus Grubba committed
376
    _curves.clear();
pixhawk's avatar
pixhawk committed
377

378
    foreach (CurveData* data, d_data) {
pixhawk's avatar
pixhawk committed
379 380
        delete data;
    }
381 382 383 384
    d_data.clear();
    resetScaling();
    replot();
}