maxlogo 发表于 2020-3-23 21:09:46

QCustomPlot之平滑曲线下(九)


接上篇,上篇只是实现了平滑曲线的绘制,但是并没有实现平滑曲线与0点线之间的填充区域以及两个`QCPGraph`之间的填充区域,我们将在这里实现它们

#### 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点线围成的区域
    {
      // 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围成的区域
    {
      // 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点线之间的填充区域

```
const QPainterPath QCPGraph::getSmoothFillPath(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    // 只有一个点构不成填充区域
    if (segment.size() < 2)
      return QPainterPath();

    // 起点,终点对应在轴上的位置
    QPointF start = getFillBasePoint(lineData->at(segment.begin()));
    QPointF end = getFillBasePoint(lineData->at(segment.end() - 1));

    // 将平滑曲线连成一个封闭区域
    QPainterPath path = SmoothCurveGenerator::generateSmoothCurve(*lineData);
    path.lineTo(end);
    path.lineTo(start);
    path.lineTo(lineData->at(segment.begin()));
    return path;
}
```



#### 与其它QCPGraph围成的区域
`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;

    //! 以下为添加的内容
    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.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));
    }
    //!

    // 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
      //! 以下为添加的内容
      QPointF firstPoint = QPointF(croppedData->at(0).x(), valueAxis->coordToPixel(valueAxis->range().upper));// 注意这里只裁剪到了轴矩形的可见区域
      //!
      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).setY(croppedData->at(0).y()+slope*(staticData->first().x()-croppedData->at(0).x()));
      (*croppedData).setX(staticData->first().x());

      //! 以下为添加的内容
      QPointF lastPoint = QPointF(staticData->first().x(), valueAxis->coordToPixel(valueAxis->range().lower));// 注意这里只裁剪到了轴矩形的可见区域
      QPainterPath droppedPath;
      droppedPath.addRect(QRectF(firstPoint, lastPoint).normalized());
      result -= droppedPath;// 裁掉多余区域
      //!

      // 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
      //! 以下为添加的内容
      firstPoint = QPointF(croppedData->last().x(), valueAxis->coordToPixel(valueAxis->range().lower));// 注意这里只裁剪到了轴矩形的可见区域
      //!
      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).setY(croppedData->at(li-1).y()+slope*(staticData->last().x()-croppedData->at(li-1).x()));
      (*croppedData).setX(staticData->last().x());

      //! 以下为添加的内容
      lastPoint = QPointF(staticData->last().x(), valueAxis->coordToPixel(valueAxis->range().upper));
      droppedPath = QPainterPath();
      droppedPath.addRect(QRectF(firstPoint, lastPoint).normalized());
      result -= droppedPath;// 裁掉多余区域
      //!
    } 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
      //! 以下为添加的内容
      QPointF firstPoint = QPointF(valueAxis->coordToPixel(valueAxis->range().upper), croppedData->first().y());
      //!
      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).setX(croppedData->at(0).x()+slope*(staticData->first().y()-croppedData->at(0).y()));
      (*croppedData).setY(staticData->first().y());

      //! 以下为添加的内容
      QPointF lastPoint = QPointF(valueAxis->coordToPixel(valueAxis->range().lower), staticData->first().y());
      QPainterPath droppedPath;
      droppedPath.addRect(QRectF(firstPoint, lastPoint).normalized());
      result -= droppedPath;
      //!

      // 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
      //! 以下为添加的内容
      firstPoint = QPointF(valueAxis->coordToPixel(valueAxis->range().lower), croppedData->last().y());
      //!
      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).setX(croppedData->at(li-1).x()+slope*(staticData->last().y()-croppedData->at(li-1).y()));
      (*croppedData).setY(staticData->last().y());

      //! 以下为添加的内容
      lastPoint = QPointF(valueAxis->coordToPixel(valueAxis->range().upper), staticData->last().y());
      droppedPath = QPainterPath();
      droppedPath.addRect(QRectF(firstPoint, lastPoint).normalized());
      result -= droppedPath;
      //!
    }
    return result;
}
```



#### 已知问题
- 限制线风格为`lsLine`的原因是因为其它风格可能会导致围成的区域在拖动/缩放的时候可能导致闪烁,而且其它风格对于平滑曲线来说并没有意义,所以干脆将风格限制为了`lsLine`
- 由于算法的原因,当点只有两个的时候,曲线会变成直线`Special case: Bezier curve should be a straight line.`

虽然有以上的问题,但已经可以满足我们的使用了
页: [1]
查看完整版本: QCustomPlot之平滑曲线下(九)