在 QCustomPlot中自定义绘图元素
QCustomPlot 提供了多种方式来创建和自定义绘图元素,从简单的图形到复杂的交互式组件。以下是几种主要的自定义方法:
1. 继承 QCPAbstractItem 创建自定义项
这是最灵活的方式,适合创建全新的交互式绘图元素。
cpp
class CustomArrow : public QCPAbstractItem
{Q_OBJECT
public:CustomArrow(QCustomPlot *parentPlot);~CustomArrow();// 必须实现的纯虚函数virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=0) const;// 自定义属性void setStart(const QPointF &start);void setEnd(const QPointF &end);void setHeadWidth(double width);void setHeadLength(double length);void setColor(const QColor &color);protected:// 必须实现的纯虚函数virtual void draw(QCPPainter *painter);private:// 定义项的位置锚点QCPItemPosition *mStart;QCPItemPosition *mEnd;// 自定义属性double mHeadWidth;double mHeadLength;QColor mColor;// 计算箭头路径的辅助函数QPainterPath arrowPath() const;
};// 实现
CustomArrow::CustomArrow(QCustomPlot *parentPlot) :QCPAbstractItem(parentPlot),mHeadWidth(10),mHeadLength(15),mColor(Qt::black)
{mStart = new QCPItemPosition(this, "start");mEnd = new QCPItemPosition(this, "end");
}double CustomArrow::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
{if (onlySelectable && !mSelectable)return -1;QPainterPath path = arrowPath();if (path.contains(pos))return mParentPlot->selectionTolerance()*0.99;return -1;
}void CustomArrow::draw(QCPPainter *painter)
{QPainterPath path = arrowPath();painter->setPen(QPen(mColor, 2));painter->setBrush(mColor);painter->drawPath(path);
}QPainterPath CustomArrow::arrowPath() const
{QPointF start = mStart->pixelPosition();QPointF end = mEnd->pixelPosition();QLineF line(end, start);QLineF normal = line.normalVector();normal.setLength(mHeadWidth/2.0);QPointF p1 = normal.p2();normal.setLength(-mHeadWidth/2.0);QPointF p2 = normal.p2();QLineF headLine1(end, p1);headLine1.setLength(mHeadLength);QLineF headLine2(end, p2);headLine2.setLength(mHeadLength);QPainterPath path;path.moveTo(start);path.lineTo(end);path.moveTo(p1);path.lineTo(end);path.lineTo(p2);return path;
}
2. 继承 QCPLayerable 创建底层绘图元素
这种方式适合需要完全控制绘制过程的元素。
cpp
class CustomWaveform : public QCPLayerable
{Q_OBJECT
public:CustomWaveform(QCustomPlot *parentPlot, const QVector<double> &data);// 自定义方法void setData(const QVector<double> &data);void setColor(const QColor &color);void setLineWidth(double width);protected:virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const;virtual void draw(QCPPainter *painter);private:QVector<double> mData;QColor mColor;double mLineWidth;
};// 使用示例
CustomWaveform *wave = new CustomWaveform(customPlot, data);
wave->setColor(Qt::blue);
wave->setLineWidth(2);
customPlot->replot();
3. 使用 QCPItem* 类组合创建复杂元素
可以通过组合现有的 QCPItem 类来创建复杂元素。
cpp
// 创建一个带文本标注的箭头
void createAnnotatedArrow(QCustomPlot *plot, const QPointF &start, const QPointF &end, const QString &text)
{// 创建箭头QCPItemLine *arrowLine = new QCPItemLine(plot);arrowLine->start->setCoords(start);arrowLine->end->setCoords(end);arrowLine->setHead(QCPLineEnding::esSpikeArrow);// 创建文本标注QCPItemText *arrowText = new QCPItemText(plot);arrowText->position->setParentAnchor(arrowLine->end);arrowText->position->setCoords(10, -10); // 偏移arrowText->setText(text);arrowText->setPositionAlignment(Qt::AlignLeft|Qt::AlignVCenter);// 创建连接线QCPItemCurve *connector = new QCPItemCurve(plot);connector->start->setParentAnchor(arrowText->left);connector->startDir->setParentAnchor(arrowText->left);connector->end->setParentAnchor(arrowLine->end);connector->endDir->setParentAnchor(arrowLine->end);connector->setPen(QPen(Qt::gray, 1, Qt::DashLine));
}
4. 自定义绘图函数
直接在 QCustomPlot 的绘制事件中添加自定义绘图。
cpp
// 在自定义类中
connect(customPlot, &QCustomPlot::afterReplot, this, &MyClass::drawCustomElements);void MyClass::drawCustomElements()
{QCPPainter painter(customPlot);// 绘制自定义背景网格painter.setPen(QPen(QColor(220, 220, 220), 0, Qt::DotLine));for (int i=0; i<10; ++i) {double x = customPlot->xAxis->range().lower + i*customPlot->xAxis->range().size()/10.0;painter.drawLine(customPlot->xAxis->coordToPixel(x),customPlot->yAxis->coordToPixel(customPlot->yAxis->range().lower),customPlot->xAxis->coordToPixel(x),customPlot->yAxis->coordToPixel(customPlot->yAxis->range().upper));}// 绘制自定义标记painter.setPen(Qt::NoPen);painter.setBrush(QColor(255, 100, 100, 150));painter.drawEllipse(QPointF(customPlot->xAxis->coordToPixel(5.0),customPlot->yAxis->coordToPixel(0.5)), 20, 20);
}
5. 自定义图例项
cpp
class CustomLegendItem : public QCPAbstractLegendItem {
public:CustomLegendItem(QCPLegend *parent, const QString &text, const QColor &color) : QCPAbstractLegendItem(parent), mText(text), mColor(color) {setText(text);}protected:virtual void draw(QCPPainter *painter) override {QRectF rect = mOuterRect.adjusted(2, 2, -2, -2);// 绘制背景painter->setBrush(QBrush(mColor.lighter(120)));painter->setPen(Qt::NoPen);painter->drawRoundedRect(rect, 3, 3);// 绘制图标painter->setBrush(QBrush(mColor));painter->drawRect(rect.left() + 3, rect.center().y() - 5, 10, 10);// 绘制文本painter->setPen(Qt::black);painter->setFont(QFont("Arial", 9));painter->drawText(rect.left() + 18, rect.top(), rect.width() - 18, rect.height(),Qt::AlignLeft | Qt::AlignVCenter, mText);}virtual QSize minimumSizeHint() const override {QFontMetrics metrics(QFont("Arial", 9));return QSize(metrics.horizontalAdvance(mText) + 25, 20);}private:QString mText;QColor mColor;
};//使用图例
// 创建自定义图例项并添加到图例
CustomLegendItem *customItem1 = new CustomLegendItem(customPlot->legend, "数据系列1", Qt::blue);
CustomLegendItem *customItem2 = new CustomLegendItem(customPlot->legend, "数据系列2", Qt::red);
customPlot->legend->addItem(customItem1);
customPlot->legend->addItem(customItem2);// 设置图例位置和样式
customPlot->legend->setFillOrder(QCPLegend::foColumnsFirst);
customPlot->legend->setWrap(3); // 每行最多3个项
customPlot->legend->setBorderPen(Qt::NoPen);
6. 自定义坐标轴标签
cpp
// 自定义坐标轴标签绘制
customPlot->xAxis->setTickLabelRotation(45);
customPlot->xAxis->setSubTickLength(0, 5);// 或者完全自定义标签
customPlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);
customPlot->xAxis->setDateTimeFormat("hh:mm:ss\nyyyy-MM-dd");
customPlot->xAxis->setDateTimeSpec(Qt::UTC);// 更复杂的自定义可以通过子类化QCPAxisTicker
class CustomTicker : public QCPAxisTicker
{
protected:virtual QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision){// 自定义标签文本if (tick == 0) return "零点";else if (tick > 0) return QString("+%1").arg(tick);else return QString("-%1").arg(-tick);}
};// 使用自定义ticker
CustomTicker *ticker = new CustomTicker;
customPlot->yAxis->setTicker(QSharedPointer<CustomTicker>(ticker));
性能优化技巧
-
缓存绘制结果:
cpp
// 在自定义项中 QPixmap mCache; bool mCacheInvalidated;void invalidateCache() { mCacheInvalidated = true; }void draw(QCPPainter *painter) {if (mCacheInvalidated || mCache.isNull()) {mCache = QPixmap(size());QCPPainter cachePainter(&mCache);// 绘制到缓存...mCacheInvalidated = false;}painter->drawPixmap(0, 0, mCache); }
-
选择性重绘:
cpp
// 只重绘必要的部分 connect(customPlot, &QCustomPlot::beforeReplot, [=](){customPlot->setViewport(customPlot->axisRect()->rect()); // 限制重绘区域 });
-
使用OpenGL加速:
cpp
customPlot->setOpenGl(true);