IncrementalPlot.cc 10.6 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
    ChartPlot(parent),
88
    symmetric(false)
89
{
90
    setStyleText("solid crosses");
91 92 93 94 95 96 97 98 99 100 101

    plotLayout()->setAlignCanvasToScales(true);

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

    setAxisAutoScale(xBottom);
    setAxisAutoScale(yLeft);

    resetScaling();

102
    legend = NULL;
103 104 105 106 107 108 109
}

IncrementalPlot::~IncrementalPlot()
{

}

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
/**
 * @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();
}

127 128
void IncrementalPlot::showLegend(bool show)
{
129 130
    if (show) {
        if (legend == NULL) {
131 132
            legend = new QwtLegend;
            legend->setFrameStyle(QFrame::Box);
133
            legend->setDefaultItemMode(QwtLegendData::Checkable);
134 135
        }
        insertLegend(legend, QwtPlot::RightLegend);
136
    } else {
137 138 139
        delete legend;
        legend = NULL;
    }
140
    updateScale(); // Updates the scaling at replots
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
}

/**
 * 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
 */
156
void IncrementalPlot::setStyleText(const QString &style)
157
{
158
    styleText = style.toLower();
159
    foreach (QwtPlotCurve* curve, curves) {
160 161 162 163
        updateStyle(curve);
    }
    replot();
}
164

165 166 167 168
void IncrementalPlot::updateStyle(QwtPlotCurve *curve)
{
    if(styleText.isNull())
        return;
169 170 171 172 173 174 175

    // 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;
176
    if (styleText.contains("circles")) {
177
        newSymbol = new QwtSymbol(QwtSymbol::Ellipse, Qt::NoBrush, QPen(oldColor, symbolWidth), QSize(6, 6));
178
    } else if (styleText.contains("crosses")) {
179
        newSymbol = new QwtSymbol(QwtSymbol::XCross, Qt::NoBrush, QPen(oldColor, symbolWidth), QSize(5, 5));
180
    } else if (styleText.contains("rect")) {
181
        newSymbol = new QwtSymbol(QwtSymbol::Rect, Qt::NoBrush, QPen(oldColor, symbolWidth), QSize(6, 6));
182
    }
183 184
    // Else-case already handled by NULL value, which indicates no symbol
    curve->setSymbol(newSymbol);
185

186
    // Update the line style
187
    if (styleText.contains("dotted")) {
188
        curve->setPen(QPen(oldColor, curveWidth, Qt::DotLine));
189
    } else if (styleText.contains("dashed")) {
190
        curve->setPen(QPen(oldColor, curveWidth, Qt::DashLine));
191
    } else if (styleText.contains("line") || styleText.contains("solid")) {
192
        curve->setPen(QPen(oldColor, curveWidth, Qt::SolidLine));
193
    } else {
194
        curve->setPen(QPen(oldColor, curveWidth, Qt::NoPen));
195
    }
196
    curve->setStyle(QwtPlotCurve::Lines);
197 198
}

199 200 201 202
void IncrementalPlot::resetScaling()
{
    xmin = 0;
    xmax = 500;
203 204
    ymin = xmin;
    ymax = xmax;
205 206 207 208 209 210 211 212 213 214 215 216 217

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

218 219 220 221 222 223
/**
 * Updates the scale calculation and re-plots the whole plot
 */
void IncrementalPlot::updateScale()
{
    const double margin = 0.05;
224 225 226
    if(xmin == DBL_MAX)
        return;

LM's avatar
LM committed
227 228 229 230
    double xMinRange = xmin-(qAbs(xmin*margin));
    double xMaxRange = xmax+(qAbs(xmax*margin));
    double yMinRange = ymin-(qAbs(ymin*margin));
    double yMaxRange = ymax+(qAbs(ymax*margin));
231
    if (symmetric) {
232 233 234 235 236 237 238 239 240 241
        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;

242
        if (xRange > yRange) {
243
            double yCenter = yMinRange + yRange/2.0;
244
            double xCenter = xMinRange + xRange/2.0;
245 246
            yMinRange = yCenter - xRange/2.0;
            yMaxRange = yCenter + xRange/2.0;
247 248
            xMinRange = xCenter - (xRange*aspectRatio)/2.0;
            xMaxRange = xCenter + (xRange*aspectRatio)/2.0;
249
        } else {
250
            double xCenter = xMinRange + xRange/2.0;
251 252
            xMinRange = xCenter - (yRange*aspectRatio)/2.0;
            xMaxRange = xCenter + (yRange*aspectRatio)/2.0;
253 254 255 256 257 258 259
        }
    }
    setAxisScale(xBottom, xMinRange, xMaxRange);
    setAxisScale(yLeft, yMinRange, yMaxRange);
    zoomer->setZoomBase(true);
}

260
void IncrementalPlot::appendData(const QString &key, double x, double y)
261 262 263 264
{
    appendData(key, &x, &y, 1);
}

265
void IncrementalPlot::appendData(const QString &key, double *x, double *y, int size)
266 267 268
{
    CurveData* data;
    QwtPlotCurve* curve;
269
    if (!d_data.contains(key)) {
270 271
        data = new CurveData;
        d_data.insert(key, data);
272
    } else {
273 274 275
        data = d_data.value(key);
    }

276
    // If this is a new curve, create it.
277
    if (!curves.contains(key)) {
278
        curve = new QwtPlotCurve(key);
279
        curves.insert(key, curve);
280
        curve->setStyle(QwtPlotCurve::NoCurve);
281
        curve->setPaintAttribute(QwtPlotCurve::FilterPoints);
282

283
        // Set the color. Only the pen needs to be set
284
        const QColor &c = getNextColor();
285 286 287 288 289
        curve->setPen(c, symbolWidth);

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

        updateStyle(curve);
290
        curve->attach(this);
291
    } else {
292
        curve = curves.value(key);
293 294 295
    }

    data->append(x, y, size);
296
    curve->setRawSamples(data->x(), data->y(), data->count());
297 298 299 300

    bool scaleChanged = false;

    // Update scales
301 302
    for (int i = 0; i<size; i++) {
        if (x[i] < xmin) {
303 304 305
            xmin = x[i];
            scaleChanged = true;
        }
306
        if (x[i] > xmax) {
307 308 309 310
            xmax = x[i];
            scaleChanged = true;
        }

311
        if (y[i] < ymin) {
312 313 314
            ymin = y[i];
            scaleChanged = true;
        }
315
        if (y[i] > ymax) {
316 317 318 319 320 321 322 323 324 325 326 327 328
            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();

329
    if(scaleChanged) {
330
        updateScale();
331
    } else {
332

333 334
        QwtPlotCanvas *c = static_cast<QwtPlotCanvas*>(canvas());
        const bool cacheMode = c->testPaintAttribute(QwtPlotCanvas::BackingStore);
335

336
        c->setPaintAttribute(QwtPlotCanvas::BackingStore, false);
337
        // FIXME Check if here all curves should be drawn
338
        //        QwtPlotCurve* plotCurve;
339
        //        foreach(plotCurve, curves)
340 341 342
        //        {
        //            plotCurve->draw(0, curve->dataSize()-1);
        //        }
343

344 345 346 347
        // FIXME: Unsure what this call should be now.
        //curve->draw(curve->dataSize() - size, curve->dataSize() - 1);
        replot();
        c->setPaintAttribute(QwtPlotCanvas::BackingStore, cacheMode);
348

349 350
    }
}
351

352 353 354
/**
 * @return Number of copied data points, 0 on failure
 */
355
int IncrementalPlot::data(const QString &key, double* r_x, double* r_y, int maxSize)
356 357
{
    int result = 0;
358
    if (d_data.contains(key)) {
359
        CurveData* d = d_data.value(key);
360
        if (maxSize >= d->count()) {
361 362 363
            result = d->count();
            memcpy(r_x, d->x(), sizeof(double) * d->count());
            memcpy(r_y, d->y(), sizeof(double) * d->count());
364
        } else {
365 366
            result = 0;
        }
367
    }
368 369 370 371 372 373 374 375 376 377 378
    return result;
}

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

380
bool IncrementalPlot::gridEnabled() const
381 382
{
    return grid->isVisible();
383 384 385 386
}

void IncrementalPlot::removeData()
{
387
    foreach (QwtPlotCurve* curve, curves) {
pixhawk's avatar
pixhawk committed
388 389
        delete curve;
    }
390
    curves.clear();
pixhawk's avatar
pixhawk committed
391

392
    foreach (CurveData* data, d_data) {
pixhawk's avatar
pixhawk committed
393 394
        delete data;
    }
395 396 397 398
    d_data.clear();
    resetScaling();
    replot();
}