地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

914 lines
23 KiB

15 years ago
/*=====================================================================
PIXHAWK Micro Air Vehicle Flying Robotics Toolkit
(c) 2009, 2010 PIXHAWK PROJECT <http://pixhawk.ethz.ch>
This file is part of the PIXHAWK project
PIXHAWK 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.
PIXHAWK 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 PIXHAWK. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
/**
* @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>
/**
* @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): QwtPlot(parent),
minTime(QUINT64_MAX),
maxTime(QUINT64_MIN),
maxInterval(MAX_STORAGE_INTERVAL),
timeScaleStep(DEFAULT_SCALE_INTERVAL), // 10 seconds
automaticScrollActive(false),
m_active(true),
15 years ago
d_data(NULL),
d_curve(NULL)
15 years ago
{
this->plotid = plotid;
this->plotInterval = interval;
maxValue = DBL_MIN;
minValue = DBL_MAX;
//lastMaxTimeAdded = QTime();
curves = QMap<QString, QwtPlotCurve*>();
data = QMap<QString, TimeSeriesData*>();
scaleMaps = QMap<QString, QwtScaleMap*>();
yScaleEngine = new QwtLinearScaleEngine();
setAxisScaleEngine(QwtPlot::yLeft, yScaleEngine);
/* Create color map */
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));
plotPosition = 0;
setAutoReplot(false);
// Set grid
QwtPlotGrid *grid = new QwtPlotGrid;
grid->setMinPen(QPen(Qt::darkGray, 0, Qt::DotLine));
grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));
grid->enableXMin(true);
// TODO xmin?
grid->attach(this);
// Set left scale
//setAxisOptions(QwtPlot::yLeft, QwtAutoScale::Logarithmic);
// Set bottom scale
setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());
setAxisLabelRotation(QwtPlot::xBottom, -25.0);
setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom);
// Add some space on the left and right side of the scale to prevent flickering
QwtScaleWidget* bottomScaleWidget = axisWidget(QwtPlot::xBottom);
const int fontMetricsX = QFontMetrics(bottomScaleWidget->font()).height();
bottomScaleWidget->setMinBorderDist(fontMetricsX * 2, fontMetricsX / 2);
plotLayout()->setAlignCanvasToScales(true);
// Set canvas background
setCanvasBackground(QColor(40, 40, 40));
// Enable zooming
Zoomer *zoomer = new Zoomer(canvas());
zoomer->setRubberBandPen(QPen(Qt::blue, 2, Qt::DotLine));
zoomer->setTrackerPen(QPen(Qt::blue));
// Start QTimer for plot update
updateTimer = new QTimer(this);
connect(updateTimer, SIGNAL(timeout()), this, SLOT(paintRealtime()));
updateTimer->start(DEFAULT_REFRESH_RATE);
// QwtPlot::setAutoReplot();
// canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false);
// canvas()->setPaintAttribute(QwtPlotCanvas::PaintPacked, false);
}
LinechartPlot::~LinechartPlot()
{
removeAllData();
}
int LinechartPlot::getPlotId()
{
return this->plotid;
}
/**
* @param id curve identifier
*/
double LinechartPlot::getCurrentValue(QString id)
{
return data.value(id)->getCurrentValue();
}
/**
* @param id curve identifier
*/
15 years ago
double LinechartPlot::getMean(QString id)
{
return data.value(id)->getMean();
}
/**
* @param id curve identifier
*/
double LinechartPlot::getMedian(QString id)
{
return data.value(id)->getMedian();
}
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;
}
15 years ago
/**
* @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();
/* Check if dataset identifier already exists */
if(!data.contains(dataname)) {
addCurve(dataname);
}
// Add new value
TimeSeriesData* dataset = data.value(dataname);
// Append data
dataset->append(ms, value);
// Scaling values
if(ms < minTime) minTime = ms;
if(ms > maxTime) maxTime = ms;
storageInterval = maxTime - minTime;
//
if (value < minValue) minValue = value;
if (value > maxValue) maxValue = value;
valueInterval = maxValue - minValue;
// Assign dataset to curve
QwtPlotCurve* curve = curves.value(dataname);
curve->setRawData(dataset->getPlotX(), dataset->getPlotY(), dataset->getPlotCount());
// qDebug() << "mintime" << minTime << "maxtime" << maxTime << "last max time" << "window position" << getWindowPosition();
datalock.unlock();
}
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::PaintFiltered);
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
emit curveAdded(id);
15 years ago
}
QColor LinechartPlot::getNextColor()
{
/* Return current color and increment counter for next round */
nextColor++;
if(nextColor >= colors.size()) nextColor = 0;
return colors[nextColor++];
}
QColor LinechartPlot::getColorForCurve(QString id)
{
return curves.value(id)->pen().color();
}
/**
* @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 color of a curve
*
* This method emits the colorSet(id, color) signal.
*
* @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);
curve->setPen(color);
emit colorSet(id, color);
15 years ago
}
/**
* @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::setVisible(QString id, bool visible)
{
if(curves.contains(id)) {
curves.value(id)->setVisible(visible);
if(visible) {
curves.value(id)->attach(this);
} else {
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)
{
setVisible(id, false);
}
/**
* @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)
{
setVisible(id, true);
}
//void LinechartPlot::showCurve(QString id, int position)
//{
// //@TODO Implement this position-dependent
// curves.value(id)->show();
//}
/**
* @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();
}
/**
* @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)
{
plotInterval = interval;
QMap<QString, TimeSeriesData*>::iterator j;
for(j = data.begin(); j != data.end(); ++j) {
TimeSeriesData* d = data.value(j.key());
d->setInterval(interval);
}
}
/**
* @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;
}
QList<QColor> LinechartPlot::getColorMap()
{
return colors;
}
/**
* @brief Set logarithmic scaling for the curve
**/
void LinechartPlot::setLogarithmicScaling()
{
yScaleEngine = new QwtLog10ScaleEngine();
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)
{
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)
{
// Update plot window value to new max time if the last time was also the max time
windowLock.lock();
if (automaticScrollActive) {
if (MG::TIME::getGroundTimeNow() > maxTime && abs(MG::TIME::getGroundTimeNow() - maxTime) < 5000000)
{
plotPosition = MG::TIME::getGroundTimeNow();
}
else
{
plotPosition = maxTime;// + lastMaxTimeAdded.msec();
}
setAxisScale(QwtPlot::xBottom, plotPosition - plotInterval, plotPosition, timeScaleStep);
/* Notify about change. Even if the window position was not changed
15 years ago
* itself, the relative position of the window to the interval must
* have changed, as the interval likely increased in length */
emit windowPositionChanged(getWindowPosition());
}
15 years ago
windowLock.unlock();
15 years ago
// Defined both on windows 32- and 64 bit
15 years ago
#ifndef _WIN32
// const bool cacheMode =
// canvas()->testPaintAttribute(QwtPlotCanvas::PaintCached);
const bool oldDirectPaint =
canvas()->testAttribute(Qt::WA_PaintOutsidePaintEvent);
15 years ago
const QPaintEngine *pe = canvas()->paintEngine();
bool directPaint = pe->hasFeature(QPaintEngine::PaintOutsidePaintEvent);
if ( pe->type() == QPaintEngine::X11 )
{
// Even if not recommended by TrollTech, Qt::WA_PaintOutsidePaintEvent
// works on X11. This has an tremendous effect on the performance..
directPaint = true;
}
canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, directPaint);
15 years ago
#endif
replot();
15 years ago
#ifndef _WIN32
canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, oldDirectPaint);
15 years ago
#endif
/*
15 years ago
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);
}*/
// static quint64 timestamp = 0;
//
//
// qDebug() << "PLOT INTERVAL:" << MG::TIME::getGroundTimeNow() - timestamp;
//
// timestamp = MG::TIME::getGroundTimeNow();
}
15 years ago
}
/**
* @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) {
// 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());
15 years ago
}
// 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();
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.00),
median(0.00),
averageWindow(50)
{
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
if(static_cast<quint64>(size()) < (count + 100))
{
this->ms.resize(size() + 1000);
this->value.resize(size() + 1000);
}
this->ms[count] = ms;
this->value[count] = value;
this->mean = 0;
QList<double> medianList = QList<double>();
for (unsigned int i = 0; (i < averageWindow) && (((int)count - (int)i) >= 0); ++i)
{
this->mean += this->value[count-i];
medianList.append(this->value[count-i]);
}
mean = mean / static_cast<double>(qMin(averageWindow,static_cast<unsigned int>(count)));
qSort(medianList);
if (medianList.size() > 2)
{
if (medianList.size() % 2 == 0)
{
median = (medianList.at(medianList.size()/2) + medianList.at(medianList.size()/2+1)) / 2.0;
}
else
{
median = medianList.at(medianList.size()/2+1);
}
}
// 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)
{
plotCount--;
}
}
count++;
plotCount++;
if(minValue > value) minValue = value;
if(maxValue < value) maxValue = value;
// Trim dataset if necessary
if(maxInterval > 0)
{ // maxInterval = 0 means infinite
if(interval >= maxInterval && !this->ms.isEmpty() && !this->value.isEmpty())
{
// The time at which this time series should be cut
quint64 minTime = stopTime - maxInterval;
// 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.remove(0);
this->value.remove(0);
}
}
}
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;
}
double TimeSeriesData::getCurrentValue()
{
return ms.last();
}
15 years ago
/**
* @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
{
return value.data() + (count - plotCount);
}