I've put together a hack that works for me to create a QCPColorMap with a circular buffer. Note there may be some typos in the below code, and I have not tested it in all modes (forward vs. backward, horizontal vs. vertical), but the general insanity is there.
diff -Nrub a/qcustomplot.cpp b/qcustomplot.cpp
--- a/qcustomplot.cpp 2022-11-06 10:54:46.000000000 -0500
+++ b/qcustomplot.cpp 2024-04-15 11:01:30.331952823 -0400
@@ -25845,12 +25845,15 @@
\see setSize, setKeySize, setValueSize, setRange, setKeyRange, setValueRange
*/
-QCPColorMapData::QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange) :
+QCPColorMapData::QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange, bool circular, bool invert) :
mKeySize(0),
mValueSize(0),
mKeyRange(keyRange),
mValueRange(valueRange),
mIsEmpty(true),
+ mCircular(circular),
+ mInvertDirection(invert),
+ mCircBufStartIndex(0),
mData(nullptr),
mAlpha(nullptr),
mDataModified(true)
@@ -25902,6 +25905,9 @@
memcpy(mAlpha, other.mAlpha, sizeof(mAlpha[0])*size_t(keySize*valueSize));
}
mDataBounds = other.mDataBounds;
+ mCircular = other.mCircular;
+ mInvertDirection = other.mInvertDirection;
+ mCircBufStartIndex = other.mCircBufStartIndex;
mDataModified = true;
}
return *this;
@@ -25927,6 +25933,22 @@
return 0;
}
+double *QCPColorMapData::cellRow(int valueInex) {
+ if (mCircular && valueIndex >= 0 && valueIndex < mValueSize)
+ {
+ int offset;
+ if (mInvertDirection) {
+ offset = (mCircBufStartIndex + valueIndex*mKeySize) % (mKeySize*mValueSize); // keyIndex == 0 means get the oldest data, then next-oldest, etc.
+ } else {
+ offset = (mCircBufStartIndex-mKeySize) - valueIndex*mKeySize; // keyIndex == 0 means get the newest data, then next-newest, etc.
+ if (offset < 0) offset += (mKeySize*mValueSize);
+ }
+ return &mData[offset];
+ }
+ else
+ return nullptr;
+}
+
/*!
Returns the alpha map value of the cell with the indices \a keyIndex and \a valueIndex.
@@ -25943,6 +25965,16 @@
return 255;
}
+unsigned char *QCPColorMapData::alphaRow(int valueIndex) {
+ if (mCircular && mAlpha && valueIndex >= 0 && valueIndex < mValueSize)
+ {
+ const int offset = (mCircBufStartIndex + valueIndex*mKeySize) % (mKeySize*mValueSize);
+ return &mAlpha[offset];
+ }
+ else
+ return nullptr;
+}
+
/*!
Resizes the data array to have \a keySize cells in the key dimension and \a valueSize cells in
the value dimension.
@@ -25983,6 +26015,7 @@
createAlpha();
mDataModified = true;
+ mCircBufStartIndex = 0;
}
}
@@ -26114,6 +26147,22 @@
qDebug() << Q_FUNC_INFO << "index out of bounds:" << keyIndex << valueIndex;
}
+void QCPColorMapData::setRow(int valueIndex, int nPts, const double *z)
+{
+ if (valueIndex >= 0 && valueIndex < mValueSize && nPts == mKeySize)
+ {
+ memcpy( &mData[valueIndex*mKeySize], z, nPts * sizeof(double) );
+ const double *minVal = std::min_element(z, z+nPts);
+ if (*minVal < mDataBounds.lower)
+ mDataBounds.lower = *minVal; // This does not account for the possibility that we may have replaced the existing lower or upper bound with this setRow
+ const double *maxVal = std::max_element(z, z+nPts);
+ if (*maxVal > mDataBounds.upper)
+ mDataBounds.upper = *maxVal;
+ mDataModified = true;
+ } else
+ qDebug() << Q_FUNC_INFO << "index out of bounds:" << nPts << valueIndex;
+}
+
/*!
Sets the alpha of the color map cell given by \a keyIndex and \a valueIndex to \a alpha. A value
of 0 for \a alpha results in a fully transparent cell, and a value of 255 results in a fully
@@ -26313,6 +26362,29 @@
}
}
+void QCPColorMapData::pushRow(int nPts, const double *z) {
+ if (!mCircular) {
+ qDebug() << Q_FUNC_INFO << "storage buffer is not circular";
+ return;
+ }
+ if ( nPts + mCircBufStartIndex >= mKeySize*mValueSize )
+ {
+ const int nPtsPart = (mKeySize*mValueSize) - (nPts+mCircBufStartIndex); // TODO: Might be an off-by-one here
+ memcpy( &mData[mCircBufStartIndex], z, nPtsPart * sizeof(double) );
+ memcpy( &mData[0], z+nPtsPart, (nPts - nPtsPart) * sizeof(double) );
+ } else {
+ memcpy( &mData[mCircBufStartIndex], z, nPts * sizeof(double) );
+ }
+ mCircBufStartIndex += nPts;
+ mCircBufStartIndex %= mKeySize*mValueSize;
+ const double *minVal = std::min_element(z, z+nPts);
+ if (*minVal < mDataBounds.lower)
+ mDataBounds.lower = *minVal; // // This does not account for the possibility that we may have replaced the existing lower or upper bound with this pushRow
+ const double *maxVal = std::max_element(z, z+nPts);
+ if (*maxVal > mDataBounds.upper)
+ mDataBounds.upper = *maxVal;
+ mDataModified = true;
+}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// QCPColorMap
@@ -26788,22 +26860,24 @@
} else if (!mUndersampledMapImage.isNull())
mUndersampledMapImage = QImage(); // don't need oversampling mechanism anymore (map size has changed) but mUndersampledMapImage still has nonzero size, free it
- const double *rawData = mMapData->mData;
- const unsigned char *rawAlpha = mMapData->mAlpha;
if (keyAxis->orientation() == Qt::Horizontal)
{
const int lineCount = valueSize;
const int rowCount = keySize;
for (int line=0; line<lineCount; ++line)
{
+ const double *rawData = mMapData->cellRow(line);
+ const unsigned char *rawAlpha = mMapData->alphaRow(line);
QRgb* pixels = reinterpret_cast<QRgb*>(localMapImage->scanLine(lineCount-1-line)); // invert scanline index because QImage counts scanlines from top, but our vertical index counts from bottom (mathematical coordinate system)
if (rawAlpha)
- mGradient.colorize(rawData+line*rowCount, rawAlpha+line*rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
+ mGradient.colorize(rawData, rawAlpha, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
else
- mGradient.colorize(rawData+line*rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
+ mGradient.colorize(rawData, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
}
} else // keyAxis->orientation() == Qt::Vertical
{
+ const double *rawData = mMapData->mData;
+ const unsigned char *rawAlpha = mMapData->mAlpha;
const int lineCount = keySize;
const int rowCount = valueSize;
for (int line=0; line<lineCount; ++line)
diff -Nrub a/qcustomplot.h b/qcustomplot.h
--- a/qcustomplot.h 2022-11-06 10:54:46.000000000 -0500
+++ b/qcustomplot.h 2024-04-15 11:01:30.335952847 -0400
@@ -6022,7 +6022,7 @@
class QCP_LIB_DECL QCPColorMapData
{
public:
- QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange);
+ QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange, bool circular = false, bool invert = false);
~QCPColorMapData();
QCPColorMapData(const QCPColorMapData &other);
QCPColorMapData &operator=(const QCPColorMapData &other);
@@ -6035,6 +6035,8 @@
QCPRange dataBounds() const { return mDataBounds; }
double data(double key, double value);
double cell(int keyIndex, int valueIndex);
+ double *cellRow(int valueIndex);
+ unsigned char *alphaRow(int valueIndex);
unsigned char alpha(int keyIndex, int valueIndex);
// setters:
@@ -6046,7 +6048,9 @@
void setValueRange(const QCPRange &valueRange);
void setData(double key, double value, double z);
void setCell(int keyIndex, int valueIndex, double z);
+ void setRow(int valueIndex, int nPts, const double *z);
void setAlpha(int keyIndex, int valueIndex, unsigned char alpha);
+ void pushRow(int nPts, const double *z);
// non-property methods:
void recalculateDataBounds();
@@ -6063,6 +6067,9 @@
int mKeySize, mValueSize;
QCPRange mKeyRange, mValueRange;
bool mIsEmpty;
+ bool mCircular;
+ bool mInvertDirection;
+ int mCircBufStartIndex;
// non-property members:
double *mData;