|
|
#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()
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
void QCPGraphVect::setData(QVector<QCPData> *data)
|
|
|
{
|
|
|
if(data!=mData)
|
|
|
{
|
|
|
delete this->mData;
|
|
|
this->mData = 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;
|
|
|
}
|
|
|
|