接上篇QCustomPlot之平滑曲線上(八),上篇只是實現了平滑曲線的繪制,但是并沒有實現平滑曲線與0點線之間的填充區(qū)域以及兩個QCPGraph之間的填充區(qū)域,我們將在這里實現它們
drawFill函數的修改
void QCPGraph::drawFill(QCPPainter *painter, QVector<QPointF> *lines) const
{
if (mLineStyle == lsImpulse) return; // fill doesn't make sense for impulse plot
if (painter->brush().style() == Qt::NoBrush || painter->brush().color().alpha() == 0) return;
applyFillAntialiasingHint(painter);
QVector<QCPDataRange> segments = getNonNanSegments(lines, keyAxis()->orientation());
if (!mChannelFillGraph) // 與0點線圍成的區(qū)域
{
// draw base fill under graph, fill goes all the way to the zero-value-line:
for (int i=0; i<segments.size(); ++i)
if (mSmooth && mLineStyle == lsLine) painter->drawPath(getSmoothFillPath(lines, segments.at(i))); // 平滑曲線
else painter->drawPolygon(getFillPolygon(lines, segments.at(i))); // 折線
} else // 與其它QCPGraph圍成的區(qū)域
{
// draw fill between this graph and mChannelFillGraph:
QVector<QPointF> otherLines;
mChannelFillGraph->getLines(&otherLines, QCPDataRange(0, mChannelFillGraph->dataCount()));
if (!otherLines.isEmpty())
{
QVector<QCPDataRange> otherSegments = getNonNanSegments(&otherLines, mChannelFillGraph->keyAxis()->orientation());
QVector<QPair<QCPDataRange, QCPDataRange> > segmentPairs = getOverlappingSegments(segments, lines, otherSegments, &otherLines);
for (int i=0; i<segmentPairs.size(); ++i) {
if (mSmooth && mLineStyle == lsLine && mChannelFillGraph->mLineStyle == lsLine) // 稍后會解釋為什么要限制線風格為lsLine
painter->drawPath(getSmoothChannelFillPath(lines, segmentPairs.at(i).first, &otherLines, segmentPairs.at(i).second)); // 平滑曲線
else
painter->drawPolygon(getChannelFillPolygon(lines, segmentPairs.at(i).first, &otherLines, segmentPairs.at(i).second)); // 折線
}
}
}
}
與0點線之間的填充區(qū)域
const QPainterPath QCPGraph::getSmoothFillPath(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
// 只有一個點構不成填充區(qū)域
if (segment.size() < 2)
return QPainterPath();
// 起點,終點對應在軸上的位置
QPointF start = getFillBasePoint(lineData->at(segment.begin()));
QPointF end = getFillBasePoint(lineData->at(segment.end() - 1));
// 將平滑曲線連成一個封閉區(qū)域
QPainterPath path = SmoothCurveGenerator::generateSmoothCurve(*lineData);
path.lineTo(end);
path.lineTo(start);
path.lineTo(lineData->at(segment.begin()));
return path;
}

與0點線之間的填充區(qū)域
與其它QCPGraph圍成的區(qū)域
getSmoothChannelFillPath基本是從getChannelFillPolygon復制過來的,我們在這上面進行修改,修改的內容我都有注釋
const QPainterPath QCPGraph::getSmoothChannelFillPath(const QVector<QPointF> *thisData, QCPDataRange thisSegment,
const QVector<QPointF> *otherData, QCPDataRange otherSegment) const
{
QPainterPath result;
if (!mChannelFillGraph)
return result;
QCPAxis *keyAxis = mKeyAxis.data();
QCPAxis *valueAxis = mValueAxis.data();
if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
if (!mChannelFillGraph.data()->mKeyAxis) { qDebug() << Q_FUNC_INFO << "channel fill target key axis invalid"; return result; }
if (mChannelFillGraph.data()->mKeyAxis.data()->orientation() != keyAxis->orientation())
return result; // don't have same axis orientation, can't fill that (Note: if keyAxis fits, valueAxis will fit too, because it's always orthogonal to keyAxis)
if (thisData->isEmpty()) return result;
QVector<QPointF> thisSegmentData(thisSegment.size());
QVector<QPointF> otherSegmentData(otherSegment.size());
std::copy(thisData->constBegin()+thisSegment.begin(), thisData->constBegin()+thisSegment.end(), thisSegmentData.begin());
std::copy(otherData->constBegin()+otherSegment.begin(), otherData->constBegin()+otherSegment.end(), otherSegmentData.begin());
// pointers to be able to swap them, depending which data range needs cropping:
QVector<QPointF> *staticData = &thisSegmentData;
QVector<QPointF> *croppedData = &otherSegmentData;
//! [1] 以下為添加的內容
result = SmoothCurveGenerator::generateSmoothCurve(thisSegmentData);
if (mChannelFillGraph->mSmooth && mChannelFillGraph->mLineStyle == lsLine) { // mChannelFillGraph也是平滑曲線
QVector<QPointF> otherSegmentDataReverse(otherSegmentData.size());
for (int i = otherSegmentData.size() - 1; i >= 0; --i)
otherSegmentDataReverse[otherSegmentData.size() - i - 1] = otherSegmentData.at(i);
result = SmoothCurveGenerator::generateSmoothCurve(result, otherSegmentDataReverse);
} else { // mChannelFillGraph 是折線
// mLineStyle != lsLine 會導致閃爍,目前還不知道什么原因造成
for (int i = otherSegmentData.size() - 1; i >= 0; --i)
result.lineTo(otherSegmentData.at(i));
}
//! [1]
// crop both vectors to ranges in which the keys overlap (which coord is key, depends on axisType):
if (keyAxis->orientation() == Qt::Horizontal)
{
// x is key
// crop lower bound:
if (staticData->first().x() < croppedData->first().x()) // other one must be cropped
qSwap(staticData, croppedData);
const int lowBound = findIndexBelowX(croppedData, staticData->first().x());
if (lowBound == -1) return result; // key ranges have no overlap
//! [2] 以下為添加的內容
QPointF firstPoint = QPointF(croppedData->at(0).x(), valueAxis->coordToPixel(valueAxis->range().upper)); // 注意這里只裁剪到了軸矩形的可見區(qū)域
//! [2]
croppedData->remove(0, lowBound);
// set lowest point of cropped data to fit exactly key position of first static data point via linear interpolation:
if (croppedData->size() < 2) return result; // need at least two points for interpolation
double slope;
if (!qFuzzyCompare(croppedData->at(1).x(), croppedData->at(0).x()))
slope = (croppedData->at(1).y()-croppedData->at(0).y())/(croppedData->at(1).x()-croppedData->at(0).x());
else
slope = 0;
(*croppedData)[0].setY(croppedData->at(0).y()+slope*(staticData->first().x()-croppedData->at(0).x()));
(*croppedData)[0].setX(staticData->first().x());
//! [3] 以下為添加的內容
QPointF lastPoint = QPointF(staticData->first().x(), valueAxis->coordToPixel(valueAxis->range().lower)); // 注意這里只裁剪到了軸矩形的可見區(qū)域
QPainterPath droppedPath;
droppedPath.addRect(QRectF(firstPoint, lastPoint).normalized());
result -= droppedPath; // 裁掉多余區(qū)域
//! [3]
// crop upper bound:
if (staticData->last().x() > croppedData->last().x()) // other one must be cropped
qSwap(staticData, croppedData);
int highBound = findIndexAboveX(croppedData, staticData->last().x());
if (highBound == -1) return result; // key ranges have no overlap
//! [4] 以下為添加的內容
firstPoint = QPointF(croppedData->last().x(), valueAxis->coordToPixel(valueAxis->range().lower)); // 注意這里只裁剪到了軸矩形的可見區(qū)域
//! [4]
croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
// set highest point of cropped data to fit exactly key position of last static data point via linear interpolation:
if (croppedData->size() < 2) return result; // need at least two points for interpolation
const int li = croppedData->size()-1; // last index
if (!qFuzzyCompare(croppedData->at(li).x(), croppedData->at(li-1).x()))
slope = (croppedData->at(li).y()-croppedData->at(li-1).y())/(croppedData->at(li).x()-croppedData->at(li-1).x());
else
slope = 0;
(*croppedData)[li].setY(croppedData->at(li-1).y()+slope*(staticData->last().x()-croppedData->at(li-1).x()));
(*croppedData)[li].setX(staticData->last().x());
//! [5] 以下為添加的內容
lastPoint = QPointF(staticData->last().x(), valueAxis->coordToPixel(valueAxis->range().upper));
droppedPath = QPainterPath();
droppedPath.addRect(QRectF(firstPoint, lastPoint).normalized());
result -= droppedPath; // 裁掉多余區(qū)域
//! [5]
} else // mKeyAxis->orientation() == Qt::Vertical
{
// y is key
// crop lower bound:
if (staticData->first().y() < croppedData->first().y()) // other one must be cropped
qSwap(staticData, croppedData);
int lowBound = findIndexBelowY(croppedData, staticData->first().y());
if (lowBound == -1) return result; // key ranges have no overlap
//! [6] 以下為添加的內容
QPointF firstPoint = QPointF(valueAxis->coordToPixel(valueAxis->range().upper), croppedData->first().y());
//! [6]
croppedData->remove(0, lowBound);
// set lowest point of cropped data to fit exactly key position of first static data point via linear interpolation:
if (croppedData->size() < 2) return result; // need at least two points for interpolation
double slope;
if (!qFuzzyCompare(croppedData->at(1).y(), croppedData->at(0).y())) // avoid division by zero in step plots
slope = (croppedData->at(1).x()-croppedData->at(0).x())/(croppedData->at(1).y()-croppedData->at(0).y());
else
slope = 0;
(*croppedData)[0].setX(croppedData->at(0).x()+slope*(staticData->first().y()-croppedData->at(0).y()));
(*croppedData)[0].setY(staticData->first().y());
//! [7] 以下為添加的內容
QPointF lastPoint = QPointF(valueAxis->coordToPixel(valueAxis->range().lower), staticData->first().y());
QPainterPath droppedPath;
droppedPath.addRect(QRectF(firstPoint, lastPoint).normalized());
result -= droppedPath;
//! [7]
// crop upper bound:
if (staticData->last().y() > croppedData->last().y()) // other one must be cropped
qSwap(staticData, croppedData);
int highBound = findIndexAboveY(croppedData, staticData->last().y());
if (highBound == -1) return result; // key ranges have no overlap
//! [8] 以下為添加的內容
firstPoint = QPointF(valueAxis->coordToPixel(valueAxis->range().lower), croppedData->last().y());
//! [8]
croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
// set highest point of cropped data to fit exactly key position of last static data point via linear interpolation:
if (croppedData->size() < 2) return result; // need at least two points for interpolation
int li = croppedData->size()-1; // last index
if (!qFuzzyCompare(croppedData->at(li).y(), croppedData->at(li-1).y())) // avoid division by zero in step plots
slope = (croppedData->at(li).x()-croppedData->at(li-1).x())/(croppedData->at(li).y()-croppedData->at(li-1).y());
else
slope = 0;
(*croppedData)[li].setX(croppedData->at(li-1).x()+slope*(staticData->last().y()-croppedData->at(li-1).y()));
(*croppedData)[li].setY(staticData->last().y());
//! [9] 以下為添加的內容
lastPoint = QPointF(valueAxis->coordToPixel(valueAxis->range().upper), staticData->last().y());
droppedPath = QPainterPath();
droppedPath.addRect(QRectF(firstPoint, lastPoint).normalized());
result -= droppedPath;
//! [9]
}
return result;
}

與其它QCPGraph圍成的區(qū)域
已知問題
- 限制線風格為
lsLine的原因是因為其它風格可能會導致圍成的區(qū)域在拖動/縮放的時候可能導致閃爍,而且其它風格對于平滑曲線來說并沒有意義,所以干脆將風格限制為了lsLine - 由于QPainterPath的原因,它會在某些情況下導致貝塞爾曲線變直,而我們的平滑曲線是通過貝塞爾曲線繪制的,所以在某些情況下(比如放大的時候)會發(fā)現曲線變直了,Qt的原文如下:
Bezier curves may be flattened to line segments due to numerical instability of doing bezier curve intersections. - 經測試,當顯示區(qū)域的點個數為兩個的時候,曲線會變直,不知道是因為曲線算法的原因還是Qt的原因
雖然有以上的問題,但已經可以滿足我們的使用了