qcustomplotvect.cpp
619 lines
| 26.8 KiB
| text/x-c
|
CppLexer
r7 | #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; | ||||
} | ||||