IncrementalPlot.cc 12.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
/*=====================================================================

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>
 *
 */

31 32 33 34 35 36 37 38 39 40 41 42
#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>
43 44

#include <QDebug>
45 46

CurveData::CurveData():
47
    d_count(0)
48 49 50 51 52 53
{
}

void CurveData::append(double *x, double *y, int count)
{
    int newSize = ( (d_count + count) / 1000 + 1 ) * 1000;
54
    if ( newSize > size() ) {
55 56 57 58
        d_x.resize(newSize);
        d_y.resize(newSize);
    }

59
    for ( register int i = 0; i < count; i++ ) {
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
        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();
}

76
const double* CurveData::x() const
77 78 79 80
{
    return d_x.data();
}

81
const double* CurveData::y() const
82 83 84 85 86
{
    return d_y.data();
}

IncrementalPlot::IncrementalPlot(QWidget *parent):
87 88 89 90 91 92
    QwtPlot(parent),
    symbolWidth(1.2f),
    curveWidth(1.0f),
    gridWidth(0.8f),
    scaleWidth(1.0f),
    symmetric(false)
93 94 95 96 97
{
    setAutoReplot(false);

    setFrameStyle(QFrame::NoFrame);
    setLineWidth(0);
98
    setStyleText("solid crosses");
99 100 101 102
    setCanvasLineWidth(2);

    plotLayout()->setAlignCanvasToScales(true);

103 104
    grid = new QwtPlotGrid;
    grid->setMajPen(QPen(Qt::gray, 0.8f, Qt::DotLine));
105 106 107 108 109 110 111 112 113 114 115 116 117
    grid->attach(this);

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

    setAxisAutoScale(xBottom);
    setAxisAutoScale(yLeft);

    resetScaling();

    // enable zooming

    zoomer = new ScrollZoomer(canvas());
118
    zoomer->setRubberBandPen(QPen(Qt::red, 1.5f, Qt::DotLine));
119 120
    zoomer->setTrackerPen(QPen(Qt::red));
    //zoomer->setZoomBase(QwtDoubleRect());
121
    legend = NULL;
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147

    colors = QList<QColor>();
    nextColor = 0;

    ///> Color map for plots, includes 20 colors
    ///> Map will start from beginning when the first 20 colors are exceeded
    colors.append(QColor(242,255,128));
    colors.append(QColor(70,80,242));
    colors.append(QColor(232,33,47));
    colors.append(QColor(116,251,110));
    colors.append(QColor(81,183,244));
    colors.append(QColor(234,38,107));
    colors.append(QColor(92,247,217));
    colors.append(QColor(151,59,239));
    colors.append(QColor(231,72,28));
    colors.append(QColor(236,48,221));
    colors.append(QColor(75,133,243));
    colors.append(QColor(203,254,121));
    colors.append(QColor(104,64,240));
    colors.append(QColor(200,54,238));
    colors.append(QColor(104,250,138));
    colors.append(QColor(235,43,165));
    colors.append(QColor(98,248,176));
    colors.append(QColor(161,252,116));
    colors.append(QColor(87,231,246));
    colors.append(QColor(230,126,23));
148 149

    connect(this, SIGNAL(legendChecked(QwtPlotItem*,bool)), this, SLOT(handleLegendClick(QwtPlotItem*,bool)));
150 151 152 153 154 155 156
}

IncrementalPlot::~IncrementalPlot()
{

}

157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
/**
 * @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();
}

174 175
void IncrementalPlot::showLegend(bool show)
{
176 177
    if (show) {
        if (legend == NULL) {
178 179
            legend = new QwtLegend;
            legend->setFrameStyle(QFrame::Box);
180
            legend->setItemMode(QwtLegend::CheckableItem);
181 182
        }
        insertLegend(legend, QwtPlot::RightLegend);
183
    } else {
184 185 186
        delete legend;
        legend = NULL;
    }
187
    updateScale(); // Updates the scaling at replots
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
}

/**
 * Set datapoint and line style. This interface is intented
 * to be directly connected to the UI and allows to parse
 * human-readable, textual descriptions into plot specs.
 *
 * Data points: Either "circles", "crosses" or the default "dots"
 * Lines: Either "dotted", ("solid"/"line") or no lines if not used
 *
 * No special formatting is needed, as long as the keywords are contained
 * in the string. Lower/uppercase is ignored as well.
 *
 * @param style Formatting string for line/data point style
 */
void IncrementalPlot::setStyleText(QString style)
{
205
    foreach (QwtPlotCurve* curve, d_curve) {
206
        // Style of datapoints
207
        if (style.toLower().contains("circles")) {
208
            curve->setSymbol(QwtSymbol(QwtSymbol::Ellipse,
209
                                       Qt::NoBrush, QPen(QBrush(curve->symbol().pen().color()), symbolWidth), QSize(6, 6)) );
210
        } else if (style.toLower().contains("crosses")) {
211
            curve->setSymbol(QwtSymbol(QwtSymbol::XCross,
212
                                       Qt::NoBrush, QPen(QBrush(curve->symbol().pen().color()), symbolWidth), QSize(5, 5)) );
213
        } else if (style.toLower().contains("rect")) {
214
            curve->setSymbol(QwtSymbol(QwtSymbol::Rect,
215
                                       Qt::NoBrush, QPen(QBrush(curve->symbol().pen().color()), symbolWidth), QSize(6, 6)) );
216
        } else if (style.toLower().contains("line")) { // Show no symbol
217 218
            curve->setSymbol(QwtSymbol(QwtSymbol::NoSymbol,
                                       Qt::NoBrush, QPen(QBrush(curve->symbol().pen().color()), symbolWidth), QSize(6, 6)) );
219 220
        }

221
        curve->setPen(QPen(QBrush(curve->symbol().pen().color().darker()), curveWidth));
222
        // Style of lines
223
        if (style.toLower().contains("dotted")) {
224
            curve->setStyle(QwtPlotCurve::Dots);
225
        } else if (style.toLower().contains("line") || style.toLower().contains("solid")) {
226
            curve->setStyle(QwtPlotCurve::Lines);
227
        } else if (style.toLower().contains("dashed") || style.toLower().contains("solid")) {
228
            curve->setStyle(QwtPlotCurve::Steps);
229
        } else {
230 231
            curve->setStyle(QwtPlotCurve::NoCurve);
        }
232

233
    }
234
    replot();
235 236
}

237 238 239 240
void IncrementalPlot::resetScaling()
{
    xmin = 0;
    xmax = 500;
241 242
    ymin = xmin;
    ymax = xmax;
243 244 245 246 247 248 249 250 251 252 253 254 255

    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;
}

256 257 258 259 260 261
/**
 * Updates the scale calculation and re-plots the whole plot
 */
void IncrementalPlot::updateScale()
{
    const double margin = 0.05;
LM's avatar
LM committed
262 263 264 265
    double xMinRange = xmin-(qAbs(xmin*margin));
    double xMaxRange = xmax+(qAbs(xmax*margin));
    double yMinRange = ymin-(qAbs(ymin*margin));
    double yMaxRange = ymax+(qAbs(ymax*margin));
266
    if (symmetric) {
267 268 269 270 271 272 273 274 275 276
        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;

277
        if (xRange > yRange) {
278
            double yCenter = yMinRange + yRange/2.0;
279
            double xCenter = xMinRange + xRange/2.0;
280 281
            yMinRange = yCenter - xRange/2.0;
            yMaxRange = yCenter + xRange/2.0;
282 283
            xMinRange = xCenter - (xRange*aspectRatio)/2.0;
            xMaxRange = xCenter + (xRange*aspectRatio)/2.0;
284
        } else {
285
            double xCenter = xMinRange + xRange/2.0;
286 287
            xMinRange = xCenter - (yRange*aspectRatio)/2.0;
            xMaxRange = xCenter + (yRange*aspectRatio)/2.0;
288 289 290 291 292 293 294
        }
    }
    setAxisScale(xBottom, xMinRange, xMaxRange);
    setAxisScale(yLeft, yMinRange, yMaxRange);
    zoomer->setZoomBase(true);
}

295 296 297 298 299 300 301 302 303
void IncrementalPlot::appendData(QString key, double x, double y)
{
    appendData(key, &x, &y, 1);
}

void IncrementalPlot::appendData(QString key, double *x, double *y, int size)
{
    CurveData* data;
    QwtPlotCurve* curve;
304
    if (!d_data.contains(key)) {
305 306
        data = new CurveData;
        d_data.insert(key, data);
307
    } else {
308 309 310
        data = d_data.value(key);
    }

311
    if (!d_curve.contains(key)) {
312 313 314 315 316 317 318
        curve = new QwtPlotCurve(key);
        d_curve.insert(key, curve);
        curve->setStyle(QwtPlotCurve::NoCurve);
        curve->setPaintAttribute(QwtPlotCurve::PaintFiltered);

        const QColor &c = getNextColor();
        curve->setSymbol(QwtSymbol(QwtSymbol::XCross,
319
                                   QBrush(c), QPen(c, 1.2f), QSize(5, 5)) );
320 321

        curve->attach(this);
322
    } else {
323 324 325 326 327 328 329 330 331
        curve = d_curve.value(key);
    }

    data->append(x, y, size);
    curve->setRawData(data->x(), data->y(), data->count());

    bool scaleChanged = false;

    // Update scales
332 333
    for (int i = 0; i<size; i++) {
        if (x[i] < xmin) {
334 335 336
            xmin = x[i];
            scaleChanged = true;
        }
337
        if (x[i] > xmax) {
338 339 340 341
            xmax = x[i];
            scaleChanged = true;
        }

342
        if (y[i] < ymin) {
343 344 345
            ymin = y[i];
            scaleChanged = true;
        }
346
        if (y[i] > ymax) {
347 348 349 350 351 352 353 354 355 356 357 358 359
            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();

360
    if(scaleChanged) {
361
        updateScale();
362
    } else {
363 364

        const bool cacheMode =
365
            canvas()->testPaintAttribute(QwtPlotCanvas::PaintCached);
366 367 368 369 370 371 372 373 374 375

#if QT_VERSION >= 0x040000 && defined(Q_WS_X11)
        // Even if not recommended by TrollTech, Qt::WA_PaintOutsidePaintEvent
        // works on X11. This has an tremendous effect on the performance..

        canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true);
#endif

        canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false);
        // FIXME Check if here all curves should be drawn
376 377 378 379 380
        //        QwtPlotCurve* plotCurve;
        //        foreach(plotCurve, d_curve)
        //        {
        //            plotCurve->draw(0, curve->dataSize()-1);
        //        }
381 382 383 384 385 386 387

        curve->draw(curve->dataSize() - size, curve->dataSize() - 1);
        canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, cacheMode);

#if QT_VERSION >= 0x040000 && defined(Q_WS_X11)
        canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, false);
#endif
388 389
    }
}
390

391 392 393 394 395 396
/**
 * @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;
397
    if (d_data.contains(key)) {
398
        CurveData* d = d_data.value(key);
399
        if (maxSize >= d->count()) {
400 401 402
            result = d->count();
            memcpy(r_x, d->x(), sizeof(double) * d->count());
            memcpy(r_y, d->y(), sizeof(double) * d->count());
403
        } else {
404 405
            result = 0;
        }
406
    }
407 408 409 410 411 412 413 414 415 416 417
    return result;
}

/**
 * @param show true to show the grid, false else
 */
void IncrementalPlot::showGrid(bool show)
{
    grid->setVisible(show);
    replot();
}
418

419 420 421
bool IncrementalPlot::gridEnabled()
{
    return grid->isVisible();
422 423 424 425 426 427 428 429 430 431 432
}

QList<QColor> IncrementalPlot::getColorMap()
{
    return colors;
}

QColor IncrementalPlot::getNextColor()
{
    /* Return current color and increment counter for next round */
    nextColor++;
433
    if(nextColor >= colors.count()) nextColor = 0;
434 435 436 437 438 439 440 441 442 443
    return colors[nextColor++];
}

QColor IncrementalPlot::getColorForCurve(QString id)
{
    return d_curve.value(id)->pen().color();
}

void IncrementalPlot::removeData()
{
444
    foreach (QwtPlotCurve* curve, d_curve) {
pixhawk's avatar
pixhawk committed
445 446
        delete curve;
    }
447
    d_curve.clear();
pixhawk's avatar
pixhawk committed
448

449
    foreach (CurveData* data, d_data) {
pixhawk's avatar
pixhawk committed
450 451
        delete data;
    }
452 453 454 455
    d_data.clear();
    resetScaling();
    replot();
}