Skip to content
Snippets Groups Projects
LinechartPlot.cc 25.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • lm's avatar
    lm committed
     /*=====================================================================
    
    pixhawk's avatar
    pixhawk committed
    ======================================================================*/
    
    /**
     * @file
     *   @brief Line chart for vehicle data
     *
     *   @author Lorenz Meier <mavteam@student.ethz.ch>
     *
     */
    
    #include "float.h"
    #include <QDebug>
    #include <QTimer>
    #include <qwt_plot.h>
    #include <qwt_plot_canvas.h>
    #include <qwt_plot_curve.h>
    #include <qwt_plot_grid.h>
    #include <qwt_plot_layout.h>
    #include <qwt_plot_zoomer.h>
    #include <qwt_symbol.h>
    #include <LinechartPlot.h>
    #include <MG.h>
    #include <QPaintEngine>
    
    pixhawk's avatar
    pixhawk committed
    
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @brief The default constructor
     *
     * @param parent The parent widget
     * @param interval The maximum interval for which data is stored (default: 30 minutes) in milliseconds
     **/
    
    LinechartPlot::LinechartPlot(QWidget *parent, int plotid, quint64 interval):
        ChartPlot(parent),
    
        minTime(0),
        lastTime(0),
        maxTime(100),
    
        maxInterval(MAX_STORAGE_INTERVAL),
    
    Lorenz Meier's avatar
    Lorenz Meier committed
        plotPosition(0),
    
        timeScaleStep(DEFAULT_SCALE_INTERVAL), // 10 seconds
        automaticScrollActive(false),
        m_active(false),
        m_groundTime(true),
        d_data(NULL),
        d_curve(NULL)
    
    pixhawk's avatar
    pixhawk committed
    {
        this->plotid = plotid;
        this->plotInterval = interval;
    
    lm's avatar
    lm committed
    
    
    Lorenz Meier's avatar
    Lorenz Meier committed
        maxValue = -DBL_MAX;
    
    pixhawk's avatar
    pixhawk committed
        minValue = DBL_MAX;
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        //lastMaxTimeAdded = QTime();
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        data = QMap<QString, TimeSeriesData*>();
        scaleMaps = QMap<QString, QwtScaleMap*>();
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        yScaleEngine = new QwtLinearScaleEngine();
        setAxisScaleEngine(QwtPlot::yLeft, yScaleEngine);
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        // Set left scale
        //setAxisOptions(QwtPlot::yLeft, QwtAutoScale::Logarithmic);
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        // Set bottom scale
        setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());
        setAxisLabelRotation(QwtPlot::xBottom, -25.0);
        setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom);
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        // Add some space on the left and right side of the scale to prevent flickering
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        QwtScaleWidget* bottomScaleWidget = axisWidget(QwtPlot::xBottom);
        const int fontMetricsX = QFontMetrics(bottomScaleWidget->font()).height();
        bottomScaleWidget->setMinBorderDist(fontMetricsX * 2, fontMetricsX / 2);
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        plotLayout()->setAlignCanvasToScales(true);
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        // Start QTimer for plot update
        updateTimer = new QTimer(this);
        connect(updateTimer, SIGNAL(timeout()), this, SLOT(paintRealtime()));
    
        //updateTimer->start(DEFAULT_REFRESH_RATE);
    
    lm's avatar
    lm committed
    
    
        connect(&timeoutTimer, SIGNAL(timeout()), this, SLOT(removeTimedOutCurves()));
    
        //timeoutTimer.start(5000);
    
    pixhawk's avatar
    pixhawk committed
    }
    
    LinechartPlot::~LinechartPlot()
    {
    
    //    datalock.lock();
    //    // Delete curves
    //    QMap<QString, QwtPlotCurve*>::iterator i;
    //    for(i = curves.begin(); i != curves.end(); ++i) {
    //        // Remove from curve list
    //        QwtPlotCurve* curve = curves.take(i.key());
    //        // Delete the object
    //        delete curve;
    //        // Set the pointer null
    //        curve = NULL;
    //    }
    
    //    // Delete data
    //    QMap<QString, TimeSeriesData*>::iterator j;
    //    for(j = data.begin(); j != data.end(); ++j) {
    //        // Remove from data list
    //        TimeSeriesData* d = data.take(j.key());
    //        // Delete the object
    //        delete d;
    //        // Set the pointer null
    //        d = NULL;
    //    }
    //    datalock.unlock();
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void LinechartPlot::showEvent(QShowEvent* event)
    {
        Q_UNUSED(event);
        updateTimer->start(DEFAULT_REFRESH_RATE);
    }
    
    void LinechartPlot::hideEvent(QHideEvent* event)
    {
        Q_UNUSED(event);
        updateTimer->stop();
    }
    
    
    pixhawk's avatar
    pixhawk committed
    int LinechartPlot::getPlotId()
    {
        return this->plotid;
    }
    
    
    /**
     * @param id curve identifier
     */
    double LinechartPlot::getCurrentValue(QString id)
    {
        return data.value(id)->getCurrentValue();
    }
    
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @param id curve identifier
     */
    double LinechartPlot::getMean(QString id)
    {
        return data.value(id)->getMean();
    }
    
    /**
     * @param id curve identifier
     */
    double LinechartPlot::getMedian(QString id)
    {
        return data.value(id)->getMedian();
    }
    
    
    /**
     * @param id curve identifier
     */
    double LinechartPlot::getVariance(QString id)
    {
        return data.value(id)->getVariance();
    }
    
    
    pixhawk's avatar
    pixhawk committed
    int LinechartPlot::getAverageWindow()
    {
        return averageWindowSize;
    }
    
    /**
     * @brief Set the plot refresh rate
     * The default refresh rate is defined by LinechartPlot::DEFAULT_REFRESH_RATE.
     * @param ms The refresh rate in milliseconds
     **/
    void LinechartPlot::setRefreshRate(int ms)
    {
        updateTimer->setInterval(ms);
    }
    
    
    void LinechartPlot::setActive(bool active)
    {
        m_active = active;
    }
    
    void LinechartPlot::removeTimedOutCurves()
    {
        foreach(QString key, lastUpdate.keys())
        {
            quint64 time = lastUpdate.value(key);
    
            if (QGC::groundTimeMilliseconds() - time > 10000)
    
            {
                // Remove this curve
                // Delete curves
                QwtPlotCurve* curve = curves.take(key);
                // Delete the object
                delete curve;
                // Set the pointer null
                curve = NULL;
    
                // Notify connected components about the removal
                emit curveRemoved(key);
    
                // Remove from data list
                TimeSeriesData* d = data.take(key);
                // Delete the object
                delete d;
                // Set the pointer null
                d = NULL;
    
                emit curveRemoved(key);
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @brief Set the zero (center line) value
     * The zero value defines the centerline of the plot.
     *
     * @param id The id of the curve
     * @param zeroValue The zero value
     **/
    void LinechartPlot::setZeroValue(QString id, double zeroValue)
    {
        if(data.contains(id)) {
            data.value(id)->setZeroValue(zeroValue);
        } else {
            data.insert(id, new TimeSeriesData(this, id, maxInterval, zeroValue));
        }
    }
    
    void LinechartPlot::appendData(QString dataname, quint64 ms, double value)
    {
        /* Lock resource to ensure data integrity */
        datalock.lock();
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        /* Check if dataset identifier already exists */
    
        if(!data.contains(dataname)) {
    
    pixhawk's avatar
    pixhawk committed
            addCurve(dataname);
    
            enforceGroundTime(m_groundTime);
    
    //        qDebug() << "ADDING CURVE WITH" << dataname << ms << value;
    //        qDebug() << "MINTIME:" << minTime << "MAXTIME:" << maxTime;
    //        qDebug() << "LASTTIME:" << lastTime;
    
    pixhawk's avatar
    pixhawk committed
        }
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        // Add new value
        TimeSeriesData* dataset = data.value(dataname);
    
    lm's avatar
    lm committed
    
    
        quint64 time;
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        // Append data
    
            // Use timestamp from dataset
    
        else
        {
            time = QGC::groundTimeMilliseconds();
        }
    
    pixhawk's avatar
    pixhawk committed
    
    
    pixhawk's avatar
    pixhawk committed
        // Scaling values
        if(ms < minTime) minTime = ms;
        if(ms > maxTime) maxTime = ms;
        storageInterval = maxTime - minTime;
    
    pixhawk's avatar
    pixhawk committed
            //qDebug() << "UPDATED LAST TIME!" << dataname << time << lastTime;
    
    pixhawk's avatar
    pixhawk committed
    
        //
        if (value < minValue) minValue = value;
        if (value > maxValue) maxValue = value;
        valueInterval = maxValue - minValue;
    
        // Assign dataset to curve
        QwtPlotCurve* curve = curves.value(dataname);
    
        curve->setRawSamples(dataset->getPlotX(), dataset->getPlotY(), dataset->getPlotCount());
    
    pixhawk's avatar
    pixhawk committed
    
        //    qDebug() << "mintime" << minTime << "maxtime" << maxTime << "last max time" << "window position" << getWindowPosition();
    
        datalock.unlock();
    }
    
    
    /**
     * @param enforce true to reset the data timestamp with the receive / ground timestamp
     */
    void LinechartPlot::enforceGroundTime(bool enforce)
    {
        m_groundTime = enforce;
    
        if (enforce)
        {
            lastTime = QGC::groundTimeMilliseconds();
            plotPosition = lastTime;
            maxTime = lastTime;
        }
        else
        {
            lastTime = 0;
            plotPosition = 0;
            minTime = 0;
            maxTime = 100;
        }
    
    lm's avatar
    lm committed
    /**
     * @return True if the data points are stamped with the packet receive time
     */
    bool LinechartPlot::groundTime()
    {
        return m_groundTime;
    }
    
    
    pixhawk's avatar
    pixhawk committed
    void LinechartPlot::addCurve(QString id)
    {
        QColor currentColor = getNextColor();
    
        // Create new curve and set style
        QwtPlotCurve* curve = new QwtPlotCurve(id);
        // Add curve to list
        curves.insert(id, curve);
    
        curve->setStyle(QwtPlotCurve::Lines);
    
        curve->setPaintAttribute(QwtPlotCurve::FilterPoints, true);
    
    pixhawk's avatar
    pixhawk committed
        setCurveColor(id, currentColor);
        //curve->setBrush(currentColor); Leads to a filled curve
        //    curve->setRenderHint(QwtPlotItem::RenderAntialiased);
    
        curve->attach(this);
        //@TODO Color differentiation between the curves will be necessary
    
        /* Create symbol for datapoints on curve */
        /*
             * Symbols have significant performance penalty, better avoid them
             *
            QwtSymbol sym = QwtSymbol();
            sym.setStyle(QwtSymbol::Ellipse);
            sym.setPen(currentColor);
            sym.setSize(3);
            curve->setSymbol(sym);*/
    
        // Create dataset
        TimeSeriesData* dataset = new TimeSeriesData(this, id, this->plotInterval, maxInterval);
    
        // Add dataset to list
        data.insert(id, dataset);
    
        // Notify connected components about new curve
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Set the time window for the plot
     * The time window defines which data is shown in the plot.
     *
     * @param end The end of the interval in milliseconds
     * */
    void LinechartPlot::setWindowPosition(quint64 end)
    {
        windowLock.lock();
        if(end <= this->getMaxTime() && end >= (this->getMinTime() + this->getPlotInterval())) {
            plotPosition = end;
            setAxisScale(QwtPlot::xBottom, (plotPosition - getPlotInterval()), plotPosition, timeScaleStep);
        }
        //@TODO Update the rest of the plot and update drawing
        windowLock.unlock();
    }
    
    /**
     * @brief Get the time window position
     * The position marks the right edge of the plot window
     *
     * @return The position of the plot window, in milliseconds
     **/
    quint64 LinechartPlot::getWindowPosition()
    {
        return plotPosition;
    }
    
    /**
     * @brief Set the scaling of the (vertical) y axis
     * The mapping of the variable values on the drawing pane can be
     * adjusted with this method. The default is that the y axis will the chosen
     * to fit all curves in their normal base units. This can however hide all
     * details if large differences in the data values exist.
     *
     * The scaling can be changed to best fit, which fits all curves in a +100 to -100 interval.
     * The logarithmic scaling does not fit the variables, but instead applies a log10
     * scaling to all variables.
     *
     * @param scaling LinechartPlot::SCALE_ABSOLUTE for linear scaling, LinechartPlot::SCALE_BEST_FIT for the best fit scaling and LinechartPlot::SCALE_LOGARITHMIC for the logarithmic scaling.
     **/
    void LinechartPlot::setScaling(int scaling)
    {
        this->scaling = scaling;
        switch (scaling) {
        case LinechartPlot::SCALE_ABSOLUTE:
            setLinearScaling();
            break;
        case LinechartPlot::SCALE_LOGARITHMIC:
            setLogarithmicScaling();
            break;
        }
    }
    
    /**
     * @brief Change the visibility of a curve
     *
     * @param id The string id of the curve
     * @param visible The visibility: True to make it visible
     **/
    
    void LinechartPlot::setVisibleById(QString id, bool visible)
    
    pixhawk's avatar
    pixhawk committed
    {
        if(curves.contains(id)) {
            curves.value(id)->setVisible(visible);
    
    pixhawk's avatar
    pixhawk committed
                curves.value(id)->attach(this);
    
    pixhawk's avatar
    pixhawk committed
                curves.value(id)->detach();
            }
        }
    }
    
    /**
     * @brief Hide a curve.
     *
     * This is a convenience method and maps to setVisible(id, false).
     *
     * @param id The curve to hide
     * @see setVisible() For the implementation
     **/
    void LinechartPlot::hideCurve(QString id)
    {
    
        setVisibleById(id, false);
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Show a curve.
     *
     * This is a convenience method and maps to setVisible(id, true);
     *
     * @param id The curve to show
     * @see setVisible() For the implementation
     **/
    void LinechartPlot::showCurve(QString id)
    {
    
        setVisibleById(id, true);
    
    pixhawk's avatar
    pixhawk committed
    }
    
    //void LinechartPlot::showCurve(QString id, int position)
    //{
    //    //@TODO Implement this position-dependent
    //    curves.value(id)->show();
    //}
    
    
     * @brief Set the color of a curve and its symbols.
    
     *
     * @param id The id-string of the curve
     * @param color The newly assigned color
     **/
    void LinechartPlot::setCurveColor(QString id, QColor color)
    {
        QwtPlotCurve* curve = curves.value(id);
    
        // Change the color of the curve.
    
        curve->setPen(QPen(QBrush(color), curveWidth));
    
    
        qDebug() << "Setting curve" << id << "to" << color;
    
        // And change the color of the symbol, making sure to preserve the symbol style
        const QwtSymbol *oldSymbol = curve->symbol();
        QwtSymbol *newSymbol = NULL;
        if (oldSymbol) {
            newSymbol = new QwtSymbol(oldSymbol->style(), QBrush(color), QPen(color, symbolWidth), QSize(symbolWidth, symbolWidth));
        }
        curve->setSymbol(newSymbol);
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @brief Check the visibility of a curve
     *
     * @param id The id of the curve
     * @return The visibility, true if it is visible, false otherwise
     **/
    bool LinechartPlot::isVisible(QString id)
    {
        return curves.value(id)->isVisible();
    }
    
    
    lm's avatar
    lm committed
    /**
     * @return The visibility, true if it is visible, false otherwise
     **/
    bool LinechartPlot::anyCurveVisible()
    {
        bool visible = false;
    
        foreach (QString key, curves.keys())
        {
            if (curves.value(key)->isVisible())
            {
    
    lm's avatar
    lm committed
                visible = true;
            }
        }
    
        return visible;
    }
    
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @brief Allows to block interference of the automatic scrolling with user interaction
     * When the plot is updated very fast (at 1 ms for example) with new data, it might
     * get impossible for an user to interact. Therefore the automatic scrolling must be
     * explicitly activated.
     *
     * @param active The status of automatic scrolling, true to turn it on
     **/
    void LinechartPlot::setAutoScroll(bool active)
    {
        automaticScrollActive = active;
    }
    
    /**
     * @brief Get a list of all curves (visible and not visible curves)
     *
     * @return The list of curves
     **/
    QList<QwtPlotCurve*> LinechartPlot::getCurves()
    {
        return curves.values();
    }
    
    /**
     * @brief Get the smallest time value in all datasets
     *
     * @return The smallest time value
     **/
    quint64 LinechartPlot::getMinTime()
    {
        return minTime;
    }
    
    /**
     * @brief Get the biggest time value in all datasets
     *
     * @return The biggest time value
     **/
    quint64 LinechartPlot::getMaxTime()
    {
        return maxTime;
    }
    
    /**
     * @brief Get the plot interval
     * The plot interval is the time interval which is displayed on the plot
     *
     * @return The plot inteval in milliseconds
     * @see setPlotInterval()
     * @see getDataInterval() To get the interval for which data is available
     **/
    quint64 LinechartPlot::getPlotInterval()
    {
        return plotInterval;
    }
    
    /**
     * @brief Set the plot interval
     *
     * @param interval The time interval to plot, in milliseconds
     * @see getPlotInterval()
     **/
    void LinechartPlot::setPlotInterval(int interval)
    {
    
        //Only ever increase the amount of stored data,
        // so that we allow the user to change between
        // different intervals without constantly losing
        // data points
        if((unsigned)interval > plotInterval) {
    
            QMap<QString, TimeSeriesData*>::iterator j;
            for(j = data.begin(); j != data.end(); ++j)
            {
                TimeSeriesData* d = data.value(j.key());
                d->setInterval(interval);
            }
    
    pixhawk's avatar
    pixhawk committed
        }
    
        plotInterval = interval;
        if(plotInterval > 5*60*1000) //If the interval is longer than 4 minutes, change the time scale step to 2 minutes
            timeScaleStep = 2*60*1000;
        else if(plotInterval >= 4*60*1000) //If the interval is longer than 4 minutes, change the time scale step to 1 minutes
            timeScaleStep = 1*60*1000;
        else if(plotInterval >= 60*1000) //If the interval is longer than a minute, change the time scale step to 30 seconds
            timeScaleStep = 30*1000;
        else
            timeScaleStep = DEFAULT_SCALE_INTERVAL;
    
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Get the data interval
     * The data interval is defined by the time interval for which data
     * values are available.
     *
     * @return The data interval
     * @see getPlotInterval() To get the time interval which is currently displayed by the plot
     **/
    quint64 LinechartPlot::getDataInterval()
    {
        return storageInterval;
    }
    
    /**
     * @brief Set logarithmic scaling for the curve
     **/
    void LinechartPlot::setLogarithmicScaling()
    {
    
        yScaleEngine = new QwtLogScaleEngine();
    
    pixhawk's avatar
    pixhawk committed
        setAxisScaleEngine(QwtPlot::yLeft, yScaleEngine);
    }
    
    /**
     * @brief Set linear scaling for the curve
     **/
    void LinechartPlot::setLinearScaling()
    {
        yScaleEngine = new QwtLinearScaleEngine();
        setAxisScaleEngine(QwtPlot::yLeft, yScaleEngine);
    }
    
    void LinechartPlot::setAverageWindow(int windowSize)
    {
        this->averageWindowSize = windowSize;
    
        foreach(TimeSeriesData* series, data)
        {
    
    pixhawk's avatar
    pixhawk committed
            series->setAverageWindowSize(windowSize);
        }
    }
    
    /**
     * @brief Paint immediately the plot
     * This method is a replacement for replot(). In contrast to replot(), it takes the
     * time window size and eventual zoom interaction into account.
     **/
    void LinechartPlot::paintRealtime()
    {
    
        if (m_active) {
    
            static quint64 timestamp = 0;
            qDebug() << "EVENTLOOP: (" << MG::TIME::getGroundTimeNow() - timestamp << ")" << __FILE__ << __LINE__;
            timestamp = MG::TIME::getGroundTimeNow();
    
            // Update plot window value to new max time if the last time was also the max time
            windowLock.lock();
    
    
                // FIXME Check, but commenting this out should have been
                // beneficial (does only add complexity)
    
                //            if (MG::TIME::getGroundTimeNow() > maxTime && abs(MG::TIME::getGroundTimeNow() - maxTime) < 5000000)
                //            {
                //                plotPosition = MG::TIME::getGroundTimeNow();
                //            }
                //            else
                //            {
                plotPosition = lastTime;// + lastMaxTimeAdded.msec();
                //            }
    
                setAxisScale(QwtPlot::xBottom, plotPosition - plotInterval, plotPosition, timeScaleStep);
    
    
                // FIXME Last fix for scroll zoomer is here
                //setAxisScale(QwtPlot::yLeft, minValue + minValue * 0.05, maxValue + maxValue * 0.05f, (maxValue - minValue) / 10.0);
    
                /* Notify about change. Even if the window position was not changed
    
                         * itself, the relative position of the window to the interval must
                         * have changed, as the interval likely increased in length */
    
                emit windowPositionChanged(getWindowPosition());
            }
    
    pixhawk's avatar
    pixhawk committed
    
    
    pixhawk's avatar
    pixhawk committed
    
    
            // Only set current view as zoombase if zoomer is not active
            // else we could not zoom out any more
    
            if(zoomer->zoomStack().size() < 2) {
    
                zoomer->setZoomBase(true);
    
    pixhawk's avatar
    pixhawk committed
    
    
    
    pixhawk's avatar
    pixhawk committed
            QMap<QString, QwtPlotCurve*>::iterator i;
            for(i = curves.begin(); i != curves.end(); ++i) {
                    const bool cacheMode = canvas()->testPaintAttribute(QwtPlotCanvas::PaintCached);
                    canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false);
                    i.value()->drawItems();
                    canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, cacheMode);
            }*/
    
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Removes all data and curves from the plot
     **/
    void LinechartPlot::removeAllData()
    {
        datalock.lock();
        // Delete curves
        QMap<QString, QwtPlotCurve*>::iterator i;
    
        for(i = curves.begin(); i != curves.end(); ++i)
        {
    
    pixhawk's avatar
    pixhawk committed
            // Remove from curve list
            QwtPlotCurve* curve = curves.take(i.key());
            // Delete the object
            delete curve;
            // Set the pointer null
            curve = NULL;
    
            // Notify connected components about the removal
    
            emit curveRemoved(i.key());
    
    pixhawk's avatar
    pixhawk committed
        }
    
        // Delete data
        QMap<QString, TimeSeriesData*>::iterator j;
    
        for(j = data.begin(); j != data.end(); ++j)
        {
    
    pixhawk's avatar
    pixhawk committed
            // Remove from data list
            TimeSeriesData* d = data.take(j.key());
            // Delete the object
            delete d;
            // Set the pointer null
            d = NULL;
        }
        datalock.unlock();
        replot();
    }
    
    
    TimeSeriesData::TimeSeriesData(QwtPlot* plot, QString friendlyName, quint64 plotInterval, quint64 maxInterval, double zeroValue):
    
        minValue(DBL_MAX),
        maxValue(DBL_MIN),
        zeroValue(0),
        count(0),
        mean(0.0),
        median(0.0),
        variance(0.0),
        averageWindow(50)
    
    pixhawk's avatar
    pixhawk committed
    {
        this->plot = plot;
        this->friendlyName = friendlyName;
        this->maxInterval = maxInterval;
        this->zeroValue = zeroValue;
        this->plotInterval = plotInterval;
    
        /* initialize time */
        startTime = QUINT64_MAX;
        stopTime = QUINT64_MIN;
    
        plotCount = 0;
    }
    
    TimeSeriesData::~TimeSeriesData()
    {
    
    }
    
    void TimeSeriesData::setInterval(quint64 ms)
    {
        plotInterval = ms;
    }
    
    void TimeSeriesData::setAverageWindowSize(int windowSize)
    {
        this->averageWindow = windowSize;
    }
    
    /**
     * @brief Append a data point to this data set
     *
     * @param ms The time in milliseconds
     * @param value The data value
     **/
    void TimeSeriesData::append(quint64 ms, double value)
    {
        dataMutex.lock();
        // Pre- allocate new space
    
        // FIXME Check this for validity
    
        if(static_cast<quint64>(size()) < (count + 100)) {
    
            this->ms.resize(size() + 10000);
            this->value.resize(size() + 10000);
    
    pixhawk's avatar
    pixhawk committed
        }
        this->ms[count] = ms;
        this->value[count] = value;
    
    pixhawk's avatar
    pixhawk committed
        this->lastValue = value;
    
    pixhawk's avatar
    pixhawk committed
        this->mean = 0;
    
        //QList<double> medianList = QList<double>();
    
        for (unsigned int i = 0; (i < averageWindow) && (((int)count - (int)i) >= 0); ++i) {
    
    pixhawk's avatar
    pixhawk committed
            this->mean += this->value[count-i];
    
            //medianList.append(this->value[count-i]);
    
    pixhawk's avatar
    pixhawk committed
        }
    
        this->mean = mean / static_cast<double>(qMin(averageWindow,static_cast<unsigned int>(count)));
    
    
        this->variance = 0;
    
        for (unsigned int i = 0; (i < averageWindow) && (((int)count - (int)i) >= 0); ++i) {
    
            this->variance += (this->value[count-i] - mean) * (this->value[count-i] - mean);
    
        }
        this->variance = this->variance / static_cast<double>(qMin(averageWindow,static_cast<unsigned int>(count)));
    
    
    //    qSort(medianList);
    
    //    if (medianList.count() > 2)
    //    {
    //        if (medianList.count() % 2 == 0)
    //        {
    //            median = (medianList.at(medianList.count()/2) + medianList.at(medianList.count()/2+1)) / 2.0;
    //        }
    //        else
    //        {
    //            median = medianList.at(medianList.count()/2+1);
    //        }
    //    }
    
    pixhawk's avatar
    pixhawk committed
    
        // Update statistical values
        if(ms < startTime) startTime = ms;
        if(ms > stopTime) stopTime = ms;
        interval = stopTime - startTime;
    
    
        if (interval > plotInterval) {
            while (this->ms[count - plotCount] < stopTime - plotInterval) {
    
    pixhawk's avatar
    pixhawk committed
                plotCount--;
            }
        }
    
        count++;
        plotCount++;
    
        if(minValue > value) minValue = value;
        if(maxValue < value) maxValue = value;
    
        // Trim dataset if necessary
    
        if(maxInterval > 0) {
            // maxInterval = 0 means infinite
    
    pixhawk's avatar
    pixhawk committed
    
    
            if(interval > maxInterval && !this->ms.isEmpty() && !this->value.isEmpty()) {
    
    pixhawk's avatar
    pixhawk committed
                // The time at which this time series should be cut
    
                double minTime = stopTime - maxInterval;
    
    pixhawk's avatar
    pixhawk committed
                // Delete elements from the start of the list as long the time
                // value of this elements is before the cut time
    
                while(this->ms.first() < minTime) {
    
                    this->ms.pop_front();
                    this->value.pop_front();
    
    pixhawk's avatar
    pixhawk committed
                }
            }
        }
        dataMutex.unlock();
    }
    
    /**
     * @brief Get the id of this data set
     *
     * @return The id-string
     **/
    int TimeSeriesData::getID()
    {
        return id;
    }
    
    /**
     * @brief Get the minimum value in the data set
     *
     * @return The minimum value
     **/
    double TimeSeriesData::getMinValue()
    {
        return minValue;
    }
    
    /**
     * @brief Get the maximum value in the data set
     *
     * @return The maximum value
     **/
    double TimeSeriesData::getMaxValue()
    {
        return maxValue;
    }
    
    /**
     * @return the mean
     */
    double TimeSeriesData::getMean()
    {
        return mean;
    }
    
    /**
     * @return the median
     */
    double TimeSeriesData::getMedian()
    {
        return median;
    }
    
    
    /**
     * @return the variance
     */
    double TimeSeriesData::getVariance()
    {
        return variance;
    }
    
    
    double TimeSeriesData::getCurrentValue()
    {
    
    pixhawk's avatar
    pixhawk committed
        return lastValue;
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @brief Get the zero (center) value in the data set
     * The zero value is not a statistical value, but instead manually defined
     * when creating the data set.
     * @return The zero value
     **/
    double TimeSeriesData::getZeroValue()
    {
        return zeroValue;
    }
    
    /**
     * @brief Set the zero (center) value
     *
     * @param zeroValue The zero value
     * @see getZeroValue()
     **/
    void TimeSeriesData::setZeroValue(double zeroValue)
    {
        this->zeroValue = zeroValue;
    }
    
    /**
     * @brief Get the number of points in the dataset
     *
     * @return The number of points
     **/
    int TimeSeriesData::getCount() const
    {
        return count;
    }
    
    /**
     * @brief Get the number of points in the plot selection
     *
     * @return The number of points
     **/
    int TimeSeriesData::getPlotCount() const
    {
        return plotCount;
    }
    
    /**
     * @brief Get the data array size
     * The data array size is \e NOT equal to the number of items in the data set, as
     * array space is pre-allocated. Use getCount() to get the number of data points.
     *
     * @return The data array size
     * @see getCount()
     **/
    int TimeSeriesData::size() const
    {
        return ms.size();
    }
    
    /**
     * @brief Get the X (time) values
     *
     * @return The x values
     **/
    const double* TimeSeriesData::getX() const
    {
        return ms.data();
    }
    
    const double* TimeSeriesData::getPlotX() const
    {
        return ms.data() + (count - plotCount);
    }
    
    /**
     * @brief Get the Y (data) values
     *
     * @return The y values
     **/
    const double* TimeSeriesData::getY() const
    {
        return value.data();
    }
    
    const double* TimeSeriesData::getPlotY() const