##// END OF EJS Templates
added quick fileName generation for data export, and fixed wrong date reading...
added quick fileName generation for data export, and fixed wrong date reading on cassini data files.

File last commit:

r9:f32891d6150c default
r10:63067c6877ba default
Show More
qcustomplotvect.cpp
629 lines | 27.1 KiB | text/x-c | CppLexer
#include "qcustomplotvect.h"
#include <QVector>
#include <QVectorIterator>
QCustomPlotVect::QCustomPlotVect(QWidget *parent)
:QCustomPlot(parent)
{
}
QCustomPlotVect::~QCustomPlotVect()
{
}
QCPGraphVect *QCustomPlotVect::addGraph(QCPAxis *keyAxis, QCPAxis *valueAxis)
{
if (!keyAxis) keyAxis = xAxis;
if (!valueAxis) valueAxis = yAxis;
if (!keyAxis || !valueAxis)
{
qDebug() << Q_FUNC_INFO << "can't use default QCustomPlot xAxis or yAxis, because at least one is invalid (has been deleted)";
return 0;
}
if (keyAxis->parentPlot() != this || valueAxis->parentPlot() != this)
{
qDebug() << Q_FUNC_INFO << "passed keyAxis or valueAxis doesn't have this QCustomPlot as parent";
return 0;
}
QCPGraphVect *newGraph = new QCPGraphVect(keyAxis, valueAxis);
if (addPlottable(newGraph))
{
newGraph->setName(QLatin1String("Graph ")+QString::number(mGraphs.size()));
return newGraph;
} else
{
delete newGraph;
return 0;
}
}
QCPGraphVect::QCPGraphVect(QCPAxis *keyAxis, QCPAxis *valueAxis)
:QCPGraph(keyAxis,valueAxis)
{
mData = new QVector<QCPData>();
}
QCPGraphVect::~QCPGraphVect()
{
delete mData;
}
void QCPGraphVect::setData(QVector<QCPData> *data)
{
if(data!=mData)
{
delete this->mData;
this->mData = data;
}
}
QVector<QCPData> *QCPGraphVect::getVisibleData()
{
QVector<QCPData>* data = new QVector<QCPData>();
bool mAdaptiveSampling_save = mAdaptiveSampling;
mAdaptiveSampling = false;
getPreparedData(data,NULL);
mAdaptiveSampling = mAdaptiveSampling_save;
return data;
}
void QCPGraphVect::draw(QCPPainter *painter)
{
if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
int test = mKeyAxis.data()->range().size();
test = mData->count();
if (mKeyAxis.data()->range().size() <= 0 || mData->isEmpty()) return;
if (mLineStyle == lsNone && mScatterStyle.isNone()) return;
// allocate line and (if necessary) point vectors:
QVector<QPointF> *lineData = new QVector<QPointF>;
QVector<QCPData> *scatterData = 0;
if (!mScatterStyle.isNone())
scatterData = new QVector<QCPData>;
// fill vectors with data appropriate to plot style:
getPlotData(lineData, scatterData);
// check data validity if flag set:
#ifdef QCUSTOMPLOT_CHECK_DATA
QCPDataMap::const_iterator it;
for (it = mData->constBegin(); it != mData->constEnd(); ++it)
{
if (QCP::isInvalidData(it.value().key, it.value().value) ||
QCP::isInvalidData(it.value().keyErrorPlus, it.value().keyErrorMinus) ||
QCP::isInvalidData(it.value().valueErrorPlus, it.value().valueErrorPlus))
qDebug() << Q_FUNC_INFO << "Data point at" << it.key() << "invalid." << "Plottable name:" << name();
}
#endif
// draw fill of graph:
drawFill(painter, lineData);
// draw line:
if (mLineStyle == lsImpulse)
drawImpulsePlot(painter, lineData);
else if (mLineStyle != lsNone)
drawLinePlot(painter, lineData); // also step plots can be drawn as a line plot
// draw scatters:
if (scatterData)
drawScatterPlot(painter, scatterData);
// free allocated line and point vectors:
delete lineData;
if (scatterData)
delete scatterData;
}
void QCPGraphVect::getPlotData(QVector<QPointF> *lineData, QVector<QCPData> *scatterData) const
{
switch(mLineStyle)
{
case lsNone: getScatterPlotData(scatterData); break;
case lsLine: getLinePlotData(lineData, scatterData); break;
case lsStepLeft: getStepLeftPlotData(lineData, scatterData); break;
case lsStepRight: getStepRightPlotData(lineData, scatterData); break;
case lsStepCenter: getStepCenterPlotData(lineData, scatterData); break;
case lsImpulse: getImpulsePlotData(lineData, scatterData); break;
}
}
void QCPGraphVect::getLinePlotData(QVector<QPointF> *linePixelData, QVector<QCPData> *scatterData) const
{
QCPAxis *keyAxis = mKeyAxis.data();
QCPAxis *valueAxis = mValueAxis.data();
if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
if (!linePixelData) { qDebug() << Q_FUNC_INFO << "null pointer passed as linePixelData"; return; }
QVector<QCPData> lineData;
getPreparedData(&lineData, scatterData);
linePixelData->reserve(lineData.size()+2); // added 2 to reserve memory for lower/upper fill base points that might be needed for fill
linePixelData->resize(lineData.size());
// transform lineData points to pixels:
if (keyAxis->orientation() == Qt::Vertical)
{
for (int i=0; i<lineData.size(); ++i)
{
(*linePixelData)[i].setX(valueAxis->coordToPixel(lineData.at(i).value));
(*linePixelData)[i].setY(keyAxis->coordToPixel(lineData.at(i).key));
}
} else // key axis is horizontal
{
for (int i=0; i<lineData.size(); ++i)
{
(*linePixelData)[i].setX(keyAxis->coordToPixel(lineData.at(i).key));
(*linePixelData)[i].setY(valueAxis->coordToPixel(lineData.at(i).value));
}
}
}
QCPRange QCPGraphVect::getKeyRange(bool &foundRange, QCPAbstractPlottable::SignDomain inSignDomain, bool includeErrors) const
{
QCPRange range;
bool haveLower = false;
bool haveUpper = false;
double current, currentErrorMinus, currentErrorPlus;
if (inSignDomain == sdBoth) // range may be anywhere
{
QVector<QCPData>::const_iterator it = mData->constBegin();
while (it != mData->constEnd())
{
current = (*it).key;
currentErrorMinus = (includeErrors ? (*it).keyErrorMinus : 0);
currentErrorPlus = (includeErrors ? (*it).keyErrorPlus : 0);
if (current-currentErrorMinus < range.lower || !haveLower)
{
range.lower = current-currentErrorMinus;
haveLower = true;
}
if (current+currentErrorPlus > range.upper || !haveUpper)
{
range.upper = current+currentErrorPlus;
haveUpper = true;
}
++it;
}
} else if (inSignDomain == sdNegative) // range may only be in the negative sign domain
{
QVector<QCPData>::const_iterator it = mData->constBegin();
while (it != mData->constEnd())
{
current = (*it).key;
currentErrorMinus = (includeErrors ? (*it).keyErrorMinus : 0);
currentErrorPlus = (includeErrors ? (*it).keyErrorPlus : 0);
if ((current-currentErrorMinus < range.lower || !haveLower) && current-currentErrorMinus < 0)
{
range.lower = current-currentErrorMinus;
haveLower = true;
}
if ((current+currentErrorPlus > range.upper || !haveUpper) && current+currentErrorPlus < 0)
{
range.upper = current+currentErrorPlus;
haveUpper = true;
}
if (includeErrors) // in case point is in valid sign domain but errobars stretch beyond it, we still want to geht that point.
{
if ((current < range.lower || !haveLower) && current < 0)
{
range.lower = current;
haveLower = true;
}
if ((current > range.upper || !haveUpper) && current < 0)
{
range.upper = current;
haveUpper = true;
}
}
++it;
}
} else if (inSignDomain == sdPositive) // range may only be in the positive sign domain
{
QVector<QCPData>::const_iterator it = mData->constBegin();
while (it != mData->constEnd())
{
current = (*it).key;
currentErrorMinus = (includeErrors ? (*it).keyErrorMinus : 0);
currentErrorPlus = (includeErrors ? (*it).keyErrorPlus : 0);
if ((current-currentErrorMinus < range.lower || !haveLower) && current-currentErrorMinus > 0)
{
range.lower = current-currentErrorMinus;
haveLower = true;
}
if ((current+currentErrorPlus > range.upper || !haveUpper) && current+currentErrorPlus > 0)
{
range.upper = current+currentErrorPlus;
haveUpper = true;
}
if (includeErrors) // in case point is in valid sign domain but errobars stretch beyond it, we still want to get that point.
{
if ((current < range.lower || !haveLower) && current > 0)
{
range.lower = current;
haveLower = true;
}
if ((current > range.upper || !haveUpper) && current > 0)
{
range.upper = current;
haveUpper = true;
}
}
++it;
}
}
foundRange = haveLower && haveUpper;
return range;
}
QCPRange QCPGraphVect::getValueRange(bool &foundRange, QCPAbstractPlottable::SignDomain inSignDomain, bool includeErrors) const
{
QCPRange range;
bool haveLower = false;
bool haveUpper = false;
double current, currentErrorMinus, currentErrorPlus;
if (inSignDomain == sdBoth) // range may be anywhere
{
QVector<QCPData>::const_iterator it = mData->constBegin();
while (it != mData->constEnd())
{
current = (*it).value;
currentErrorMinus = (includeErrors ? (*it).valueErrorMinus : 0);
currentErrorPlus = (includeErrors ? (*it).valueErrorPlus : 0);
if (current-currentErrorMinus < range.lower || !haveLower)
{
range.lower = current-currentErrorMinus;
haveLower = true;
}
if (current+currentErrorPlus > range.upper || !haveUpper)
{
range.upper = current+currentErrorPlus;
haveUpper = true;
}
++it;
}
} else if (inSignDomain == sdNegative) // range may only be in the negative sign domain
{
QVector<QCPData>::const_iterator it = mData->constBegin();
while (it != mData->constEnd())
{
current = (*it).value;
currentErrorMinus = (includeErrors ? (*it).valueErrorMinus : 0);
currentErrorPlus = (includeErrors ? (*it).valueErrorPlus : 0);
if ((current-currentErrorMinus < range.lower || !haveLower) && current-currentErrorMinus < 0)
{
range.lower = current-currentErrorMinus;
haveLower = true;
}
if ((current+currentErrorPlus > range.upper || !haveUpper) && current+currentErrorPlus < 0)
{
range.upper = current+currentErrorPlus;
haveUpper = true;
}
if (includeErrors) // in case point is in valid sign domain but errobars stretch beyond it, we still want to get that point.
{
if ((current < range.lower || !haveLower) && current < 0)
{
range.lower = current;
haveLower = true;
}
if ((current > range.upper || !haveUpper) && current < 0)
{
range.upper = current;
haveUpper = true;
}
}
++it;
}
} else if (inSignDomain == sdPositive) // range may only be in the positive sign domain
{
QVector<QCPData>::const_iterator it = mData->constBegin();
while (it != mData->constEnd())
{
current = (*it).value;
currentErrorMinus = (includeErrors ? (*it).valueErrorMinus : 0);
currentErrorPlus = (includeErrors ? (*it).valueErrorPlus : 0);
if ((current-currentErrorMinus < range.lower || !haveLower) && current-currentErrorMinus > 0)
{
range.lower = current-currentErrorMinus;
haveLower = true;
}
if ((current+currentErrorPlus > range.upper || !haveUpper) && current+currentErrorPlus > 0)
{
range.upper = current+currentErrorPlus;
haveUpper = true;
}
if (includeErrors) // in case point is in valid sign domain but errobars stretch beyond it, we still want to geht that point.
{
if ((current < range.lower || !haveLower) && current > 0)
{
range.lower = current;
haveLower = true;
}
if ((current > range.upper || !haveUpper) && current > 0)
{
range.upper = current;
haveUpper = true;
}
}
++it;
}
}
foundRange = haveLower && haveUpper;
return range;
}
void QCPGraphVect::getPreparedData(QVector<QCPData> *lineData, QVector<QCPData> *scatterData) const
{
QCPAxis *keyAxis = mKeyAxis.data();
QCPAxis *valueAxis = mValueAxis.data();
if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
// get visible data range:
QVector<QCPData>::const_iterator lower, upper; // note that upper is the actual upper point, and not 1 step after the upper point
getVisibleDataBounds(lower, upper);
if (lower == mData->constEnd() || upper == mData->constEnd())
return;
// count points in visible range, taking into account that we only need to count to the limit maxCount if using adaptive sampling:
int maxCount = std::numeric_limits<int>::max();
if (mAdaptiveSampling)
{
int keyPixelSpan = qAbs(keyAxis->coordToPixel((*lower).key)-keyAxis->coordToPixel((*upper).key));
maxCount = 2*keyPixelSpan+2;
}
int dataCount = countDataInBounds(lower, upper, maxCount);
if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at least two points per pixel on average
{
if (lineData)
{
QVector<QCPData>::const_iterator it = lower;
QVector<QCPData>::const_iterator upperEnd = upper+1;
double minValue = (*it).value;
double maxValue = (*it).value;
QVector<QCPData>::const_iterator currentIntervalFirstPoint = it;
int reversedFactor = keyAxis->rangeReversed() != (keyAxis->orientation()==Qt::Vertical) ? -1 : 1; // is used to calculate keyEpsilon pixel into the correct direction
int reversedRound = keyAxis->rangeReversed() != (keyAxis->orientation()==Qt::Vertical) ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey
double currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel((*lower).key)+reversedRound));
double lastIntervalEndKey = currentIntervalStartKey;
double keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates
bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes)
int intervalDataCount = 1;
++it; // advance iterator to second data point because adaptive sampling works in 1 point retrospect
while (it != upperEnd)
{
if ((*it).key < currentIntervalStartKey+keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this cluster if necessary
{
if ((*it).value < minValue)
minValue = (*it).value;
else if ((*it).value > maxValue)
maxValue = (*it).value;
++intervalDataCount;
} else // new pixel interval started
{
if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster
{
if (lastIntervalEndKey < currentIntervalStartKey-keyEpsilon) // last point is further away, so first point of this cluster must be at a real data point
lineData->append(QCPData(currentIntervalStartKey+keyEpsilon*0.2, (*currentIntervalFirstPoint).value));
lineData->append(QCPData(currentIntervalStartKey+keyEpsilon*0.25, minValue));
lineData->append(QCPData(currentIntervalStartKey+keyEpsilon*0.75, maxValue));
if ((*it).key > currentIntervalStartKey+keyEpsilon*2) // new pixel started further away from previous cluster, so make sure the last point of the cluster is at a real data point
lineData->append(QCPData(currentIntervalStartKey+keyEpsilon*0.8, (*(it-1)).value));
} else
lineData->append(QCPData((*currentIntervalFirstPoint).key, (*currentIntervalFirstPoint).value));
lastIntervalEndKey = (*(it-1)).key;
minValue = (*it).value;
maxValue = (*it).value;
currentIntervalFirstPoint = it;
currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel((*it).key)+reversedRound));
if (keyEpsilonVariable)
keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor));
intervalDataCount = 1;
}
++it;
}
// handle last interval:
if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster
{
if (lastIntervalEndKey < currentIntervalStartKey-keyEpsilon) // last point wasn't a cluster, so first point of this cluster must be at a real data point
lineData->append(QCPData(currentIntervalStartKey+keyEpsilon*0.2, (*currentIntervalFirstPoint).value));
lineData->append(QCPData(currentIntervalStartKey+keyEpsilon*0.25, minValue));
lineData->append(QCPData(currentIntervalStartKey+keyEpsilon*0.75, maxValue));
} else
lineData->append(QCPData((*currentIntervalFirstPoint).key, (*currentIntervalFirstPoint).value));
}
if (scatterData)
{
double valueMaxRange = valueAxis->range().upper;
double valueMinRange = valueAxis->range().lower;
QVector<QCPData>::const_iterator it = lower;
QVector<QCPData>::const_iterator upperEnd = upper+1;
double minValue = (*it).value;
double maxValue = (*it).value;
QVector<QCPData>::const_iterator minValueIt = it;
QVector<QCPData>::const_iterator maxValueIt = it;
QVector<QCPData>::const_iterator currentIntervalStart = it;
int reversedFactor = keyAxis->rangeReversed() ? -1 : 1; // is used to calculate keyEpsilon pixel into the correct direction
int reversedRound = keyAxis->rangeReversed() ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey
double currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel((*lower).key)+reversedRound));
double keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates
bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes)
int intervalDataCount = 1;
++it; // advance iterator to second data point because adaptive sampling works in 1 point retrospect
while (it != upperEnd)
{
if ((*it).key < currentIntervalStartKey+keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this pixel if necessary
{
if ((*it).value < minValue && (*it).value > valueMinRange && (*it).value < valueMaxRange)
{
minValue = (*it).value;
minValueIt = it;
} else if ((*it).value > maxValue && (*it).value > valueMinRange && (*it).value < valueMaxRange)
{
maxValue = (*it).value;
maxValueIt = it;
}
++intervalDataCount;
} else // new pixel started
{
if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them
{
// determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot):
double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue)-valueAxis->coordToPixel(maxValue));
int dataModulo = qMax(1, qRound(intervalDataCount/(valuePixelSpan/4.0))); // approximately every 4 value pixels one data point on average
QVector<QCPData>::const_iterator intervalIt = currentIntervalStart;
int c = 0;
while (intervalIt != it)
{
if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && (*intervalIt).value > valueMinRange && (*intervalIt).value < valueMaxRange)
scatterData->append((*intervalIt));
++c;
++intervalIt;
}
} else if ((*currentIntervalStart).value > valueMinRange && (*currentIntervalStart).value < valueMaxRange)
scatterData->append((*currentIntervalStart));
minValue = (*it).value;
maxValue = (*it).value;
currentIntervalStart = it;
currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel((*it).key)+reversedRound));
if (keyEpsilonVariable)
keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor));
intervalDataCount = 1;
}
++it;
}
// handle last interval:
if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them
{
// determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot):
double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue)-valueAxis->coordToPixel(maxValue));
int dataModulo = qMax(1, qRound(intervalDataCount/(valuePixelSpan/4.0))); // approximately every 4 value pixels one data point on average
QVector<QCPData>::const_iterator intervalIt = currentIntervalStart;
int c = 0;
while (intervalIt != it)
{
if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && (*intervalIt).value > valueMinRange && (*intervalIt).value < valueMaxRange)
scatterData->append((*intervalIt));
++c;
++intervalIt;
}
} else if ((*currentIntervalStart).value > valueMinRange && (*currentIntervalStart).value < valueMaxRange)
scatterData->append(*currentIntervalStart);
}
} else // don't use adaptive sampling algorithm, transfer points one-to-one from the map into the output parameters
{
QVector<QCPData> *dataVector = 0;
if (lineData)
{
dataVector = lineData;
}
else if (scatterData)
dataVector = scatterData;
if (dataVector)
{
QVector<QCPData>::const_iterator it = lower;
QVector<QCPData>::const_iterator upperEnd = upper+1;
dataVector->reserve(dataCount+2); // +2 for possible fill end points
while (it != upperEnd)
{
dataVector->append(*it);
++it;
}
}
if (lineData && scatterData)
*scatterData = *dataVector;
}
}
QVector<QCPData>::const_iterator __lowerBoundDico_vect(QVector<QCPData>* vector,double key)
{
int DX=vector->size()/2;
int pos=DX;
// double test=(*vector)[vector->length()-1].key;
if(key>((*vector)[vector->length()-1].key))
return vector->constEnd();
if(key<((*vector)[0].key))
return vector->constBegin();
while (DX>1)
{
DX=DX/2;
if((*vector)[pos].key > key)
{
pos-=DX;
}
else
{
pos+=DX;
}
}
if((*vector)[pos].key >= key)
return vector->constBegin()+pos;
return vector->constBegin()+pos+1;
}
QVector<QCPData>::const_iterator __upperBoundDico_vect(QVector<QCPData>* vector,double key)
{
int DX=vector->size()/2;
int pos=DX;
if(key>((*vector)[vector->length()-1].key))
return vector->constEnd();
if(key<((*vector)[0].key))
return vector->constBegin();
while (DX>1)
{
DX=DX/2;
if((*vector)[pos].key > key)
{
pos-=DX;
}
else
{
pos+=DX;
}
}
return vector->constBegin()+pos+1;
}
void QCPGraphVect::getVisibleDataBounds(QVector<QCPData>::const_iterator &lower, QVector<QCPData>::const_iterator &upper) const
{
if (!mKeyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; return; }
if (mData->isEmpty())
{
lower = mData->constEnd();
upper = mData->constEnd();
return;
}
QVector<QCPData>::const_iterator lbound = __lowerBoundDico_vect(mData,mKeyAxis.data()->range().lower);
QVector<QCPData>::const_iterator ubound = __upperBoundDico_vect(mData,mKeyAxis.data()->range().upper);
bool lowoutlier = lbound != mData->constBegin(); // indicates whether there exist points below axis range
bool highoutlier = ubound != mData->constEnd(); // indicates whether there exist points above axis range
lower = (lowoutlier ? lbound-1 : lbound); // data point range that will be actually drawn
upper = (highoutlier ? ubound : ubound-1); // data point range that will be actually drawn
}
int QCPGraphVect::countDataInBounds(const QVector<QCPData>::const_iterator &lower, const QVector<QCPData>::const_iterator &upper, int maxCount) const
{
if (upper == mData->constEnd() && lower == mData->constEnd())
return 0;
QVector<QCPData>::const_iterator it = lower;
int count = 1;
while (it != upper && count < maxCount)
{
++it;
++count;
}
return count;
}