若该文为原创文章,转载请注明原文出处 博客地址:https://hpzwl.blog.csdn.net/article/details/124567140
红胖子(红模仿)博文全集:开发技术集合(包括Qt实用技术,树莓派,三维,OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等。…(点击传送门)
上一篇:《QCustomPlot开发笔记(1):QCustomPlot简介、下载和基本绘图 下一篇文章期待…
前言
??在最后一篇文章中,基本绘图继续描述用户交互、元素项和Qt一些特殊用法。
QCustomPlot用户交互
??QCustomPlot 提供多种内置的用户交互。它们大致可以分为:
- 通过鼠标拖动和滚动鼠标滚轮进行范围操作
- 单击选择绘图实体
- 用户点击绘图实体时发出的信号
范围操作
拽托
??用户操作轴范围的默认方法是相应的QCPAxisRect执行拖动操作。 ?? ??要在QCustomPlot拖动小部件的应用范围需要标志添加到当前允许的交互中。这可以通过并指定
缩放
??用户可以使用鼠标滚轮来更改范围的大小,即放大或缩小绘图。此行为由交互标志组成
选择机制
?? ??
控制个体的选择性和选择性
??在单个对象上使用
选择对象的外观
??选定的对象通常用不同的笔、画笔或字体显示。可以使用
多部分对象
??有些物体(如轴和图例)的外观比较复杂,所以仅仅选择一个布尔值是不够的。在这种情况下,
反应选择的变化
??选择变更后,每个对象都会发出一个名字
用户交互信号
??QCustomPlot独立于选择机制,在用户交互时发出各种信号。最低级别是
元素项:支持的图形元素
使用箭头和文本的基本示例
此示例显示如何创建始终位于轴矩形顶部的文本标签,以及将打印坐标中的点与该标签连接的箭头。
// add the text label at the top:
QCPItemText *textLabel = new QCPItemText(customPlot);
textLabel->setPositionAlignment(Qt::AlignTop|Qt::AlignHCenter);
textLabel->position->setType(QCPItemPosition::ptAxisRectRatio);
textLabel->position->setCoords(0.5, 0); // place position at center/top of axis rect
textLabel->setText("Text Item Demo");
textLabel->setFont(QFont(font().family(), 16)); // make font a bit larger
textLabel->setPen(QPen(Qt::black)); // show black border around text
// add the arrow:
QCPItemLine *arrow = new QCPItemLine(customPlot);
arrow->start->setParentAnchor(textLabel->bottom);
arrow->end->setCoords(4, 1.6); // point to (4, 1.6) in x-y-plot coordinates
arrow->setHead(QCPLineEnding::esSpikeArrow);
请注意,即使拖动打印范围,箭头仍会附着在打印坐标(4,1.6)上,并相应地旋转/拉伸。这是通过QCustomPlot项目定位的灵活性实现的。项目可以在打印坐标、绝对像素坐标和轴矩形大小的分数单位中定位。
项剪切
默认情况下,项目被剪裁到主轴矩形,这意味着它们仅在主轴矩形内可见。要使项目在该axis rect外部可见,请通过调用
更高级的使用
有关QCustomPlot的项目系统可以实现的更高级、更真实的演示,请查看“特殊用例”项目动态轴标记”。
特殊用法
在QTextDocument中嵌入绘图生成报告通常需要在文本文档中插入绘图和图表。 示例演示了QCustomPlot如何与QTextDocument交互,以轻松实现这一点。 示例项目是完整下载包的一部分。
QCPDocumentObject
QCustomPlot和QTextDocument之间的接口是QCPDocumentObject。请注意,此类不在标准qcustomplot中.cpp,.h文件,但在qcpdocumentobject中定义.cpp/.h在本示例项目中。 它有两个目的:
- 从QCustomPlot生成文本字符格式。这允许在QTextDocument中插入绘图,例如在光标位置。
- 重新绘制或导出QTextDocument时,在QTextDocument内呈现静态绘图。 文本字符格式有什么好处:使用自定义格式插入QChar::ObjectReplacementCharacter是Qt允许将自定义对象插入文本文档的方式。 假设QCustomPlot是ui->plot,而文本文档的QTextEdit是ui->textEdit。第一步是将QCPDocumentObject注册为文本文档中打印对象的处理程序:
// register the plot document object (only needed once, no matter how many plots will be in the QTextDocument):
QCPDocumentObject *plotObjectHandler = new QCPDocumentObject(this);
ui->textEdit->document()->documentLayout()->registerHandler(QCPDocumentObject::PlotTextFormat, plotObjectHandler);
在这个调用之后,我们可以开始在文本文档中插入绘图。这就是静态方法QCPDocumentObject::generatePlotFormat(QCustomPlot *plot, int width, int height)的优点。它以给定的宽度和高度(如果保留为0,则使用绘图的当前宽度和高度)获取绘图的矢量化快照,并将其附加到QTextCharFormat。返回的QTextCharFormat随后可用于格式化QChar::ObjectReplacementCharacter,该字符随后显示为绘图对象。因此,可以按如下方式在当前光标位置插入绘图:
QTextCursor cursor = ui->textEdit->textCursor();
// insert the current plot at the cursor position. QCPDocumentObject::generatePlotFormat creates a
// vectorized snapshot of the passed plot (with the specified width and height) which gets inserted
// into the text document.
double width = ui->cbUseCurrentSize->isChecked() ? 0 : ui->sbWidth->value();
double height = ui->cbUseCurrentSize->isChecked() ? 0 : ui->sbHeight->value();
cursor.insertText(QString(QChar::ObjectReplacementCharacter), QCPDocumentObject::generatePlotFormat(ui->plot, width, height));
ui->textEdit->setTextCursor(cursor);
组件cbUseCurrentSize、sbWidth和sbHeight是示例项目用户界面的一部分。如上所述,文本文档中的plot对象保持其矢量化性质。因此,将其导出为PDF(或其他能够矢量化内容的格式)可以获得最高质量的输出。将上述文档保存为PDF文件,并在PDF查看器中打开,将显示以下内容 可以看出,放大插入的绘图可以显示平滑的线条。
用滚动条控制轴范围
虽然控制轴范围最直观的方法是范围拖动和缩放机制,但也可能需要为此提供滚动条。这可以通过信号和插槽将轴与滚动条连接来实现。在轴的QCPRange和滚动条的整数值之间转换需要一个中间槽。 示例项目名为scrollbar axis range control,是完整下载包的一部分。
预设限制
与在滚动条和轴之间来回传播更改相关的信号是
ui->horizontalScrollBar->setRange(-500, 500);
ui->verticalScrollBar->setRange(-500, 500);
如果可访问的坐标范围在任何点发生变化,只需更改滚动条的最大值/最小值即可。 进行坐标变换的中间槽称为horzScrollBarChanged、vertScrollBarChanged、xAxisChanged和yAxisChanged。它们连接到滚动条和x-/y轴的相应信号:
connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(horzScrollBarChanged(int)));
connect(ui->verticalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(vertScrollBarChanged(int)));
connect(ui->plot->xAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(xAxisChanged(QCPRange)));
connect(ui->plot->yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(yAxisChanged(QCPRange)));
坐标变换槽
这两种类型的插槽(轴范围到滚动条和滚动条到轴范围)都相当简单。它们分别获取滚动条或滚动条的更改值,应用转换并将结果设置为轴或滚动条。这些插槽用于在移动滚动条滑块时更新轴范围:
void MainWindow::horzScrollBarChanged(int value)
{
// if user is dragging plot, we don't want to replot twice
if (qAbs(ui->plot->xAxis->range().center()-value/100.0) > 0.01)
{
ui->plot->xAxis->setRange(value/100.0, ui->plot->xAxis->range().size(), Qt::AlignCenter);
ui->plot->replot();
}
}
void MainWindow::vertScrollBarChanged(int value)
{
// if user is dragging plot, we don't want to replot twice
if (qAbs(ui->plot->yAxis->range().center()+value/100.0) > 0.01)
{
ui->plot->yAxis->setRange(-value/100.0, ui->plot->yAxis->range().size(), Qt::AlignCenter);
ui->plot->replot();
}
}
首先,我们在这里看到滚动条值除以100.0到轴坐标的转换。还请注意:
- 当滑块位于顶部时,垂直滚动条的值较低;
- 当滑块位于底部时,垂直滚动条的值较高。
对于打印轴,情况正好相反,这就是为什么在包含垂直滚动条值的表达式中添加减号的原因,例如在设置yAxis范围时。 条件qAbs(ui->plot->xAxis->range().center()-value/100.0)>0.01是必要的,这样范围拖动不会导致双重触发,这是由更改信号和插槽之间的来回切换引起的。这可能是因为在范围拖动时,QCustomPlot会自动重新打印自己,并发出被拖动轴的范围更改信号。在这个应用程序中,rangeChanged信号将调用插槽xAxisChanged或yAxisChanged,它通过调用滚动条的setValue方法来更新滚动条滑块的位置。此方法依次发出滚动条的valueChanged信号,该信号连接到上面的插槽。在这里,如果值变化,第二次变化就会发生。该检查确保仅当当前轴范围与新的(转换的)滚动条值实际不同时才执行replot。如果用户拖动轴范围,则不会出现这种情况,因此会跳过冗余的replot和轴范围更新。 轴范围更改时更新滚动条的插槽很简单:
void MainWindow::xAxisChanged(QCPRange range)
{
// adjust position of scroll bar slider
ui->horizontalScrollBar->setValue(qRound(range.center()*100.0));
// adjust size of scroll bar slider
ui->horizontalScrollBar->setPageStep(qRound(range.size()*100.0));
}
void MainWindow::yAxisChanged(QCPRange range)
{
// adjust position of scroll bar slider
ui->verticalScrollBar->setValue(qRound(-range.center()*100.0));
// adjust size of scroll bar slider
ui->verticalScrollBar->setPageStep(qRound(range.size()*100.0));
}
使用项目创建动态轴标记
这个例子演示了QCustomPlot的item系统的更高级的用法。创建了一个新的小类AxisTag,它管理一组项目,这些项目一起构成指向轴并高亮显示特定坐标的标记。 为了在主应用程序中展示它,在axis rect的右侧创建了两个轴,并制作了两个相应的标记,以指示连续更新的两个图形的最右侧数据点值。 示例项目称为axis tags example,是完整下载包的一部分:
处理相关项目的新类
在上面的屏幕截图中看到的标记由两个可见项组成:一个QCPItemText,它以矩形边框包围的文本形式为我们提供当前坐标;另一个QCPItemLine,其头部的线条端点设置为箭头形状,提供指向左侧的箭头。 然而,还有另一个不可见的项目可以帮助定位标签。QCPItemTracer位于右轴矩形边界(内轴的水平位置)的相应值坐标高度处。它为其他项提供主父锚点,因此上下移动跟踪器将上下移动整个标记。 在主窗口代码中分别管理这三个项目很容易出错,而且风格也不好。因此,创建了一个新的类AxisTag,负责设置和处理这三个项。以下是AxisTag类的头代码:
#include <QObject>
#include "qcustomplot.h"
class AxisTag : public QObject
{
Q_OBJECT
public:
explicit AxisTag(QCPAxis *parentAxis);
virtual ~AxisTag();
// setters:
void setPen(const QPen &pen);
void setBrush(const QBrush &brush);
void setText(const QString &text);
// getters:
QPen pen() const {
return mLabel->pen(); }
QBrush brush() const {
return mLabel->brush(); }
QString text() const {
return mLabel->text(); }
// other methods:
void updatePosition(double value);
protected:
QCPAxis *mAxis;
QPointer<QCPItemTracer> mDummyTracer;
QPointer<QCPItemLine> mArrow;
QPointer<QCPItemText> mLabel;
};
为了本示例的清晰性,将接口保持在最小值。在现实世界中,用户可能需要该类的更多定制和额外功能,例如,一个泛化来支持其他轴方向。 下一个代码段是AxisTag类的实现。在其构造函数中,三个项之间建立了以下锚定父子关系。青蓝色圆圈表示QCPItemTracer位置、QCPItemLine结束/开始位置和QCPItemText位置。 可以找到相关代码段的解释:
#include "axistag.h"
AxisTag::AxisTag(QCPAxis *parentAxis) :
QObject(parentAxis),
mAxis(parentAxis)
{
// The dummy tracer serves here as an invisible anchor which always sticks to the right side of
// the axis rect
mDummyTracer = new QCPItemTracer(mAxis->parentPlot());
mDummyTracer->setVisible(false);
mDummyTracer->position->setTypeX(QCPItemPosition::ptAxisRectRatio);
mDummyTracer->position->setTypeY(QCPItemPosition::ptPlotCoords);
mDummyTracer->position->setAxisRect(mAxis->axisRect());
mDummyTracer->position->setAxes(0, mAxis);
mDummyTracer->position->setCoords(1, 0);
// the arrow end (head) is set to move along with the dummy tracer by setting it as its parent
// anchor. Its coordinate system (setCoords) is thus pixels, and this is how the needed horizontal
// offset for the tag of the second y axis is achieved. This horizontal offset gets dynamically
// updated in AxisTag::updatePosition. the arrow "start" is simply set to have the "end" as parent
// anchor. It is given a horizontal offset to the right, which results in a 15 pixel long arrow.
mArrow = new QCPItemLine(mAxis->parentPlot());
mArrow->setLayer("overlay");
mArrow->setClipToAxisRect(false);
mArrow->setHead(QCPLineEnding::esSpikeArrow);
mArrow->end->setParentAnchor(mDummyTracer->position);
mArrow->start->setParentAnchor(mArrow->end);
mArrow->start->setCoords(15, 0);
// The text label is anchored at the arrow start (tail) and has its "position" aligned at the
// left, and vertically centered to the text label box.
mLabel = new QCPItemText(mAxis->parentPlot());
mLabel->setLayer("overlay");
mLabel->setClipToAxisRect(false);
mLabel->setPadding(QMargins(3, 0, 3, 0));
mLabel->setBrush(QBrush(Qt::white));
mLabel->setPen(QPen(Qt::blue));
mLabel->setPositionAlignment(Qt::AlignLeft|Qt::AlignVCenter);
mLabel->position->setParentAnchor(mArrow->start);
}
AxisTag::~AxisTag()
{
if (mDummyTracer)
mDummyTracer->parentPlot()->removeItem(mDummyTracer);
if (mArrow)
mArrow->parentPlot()->removeItem(mArrow);
if (mLabel)
mLabel->parentPlot()->removeItem(mLabel);
}
void AxisTag::setPen(const QPen &pen)
{
mArrow->setPen(pen);
mLabel->setPen(pen);
}
void AxisTag::setBrush(const QBrush &brush)
{
mLabel->setBrush(brush);
}
void AxisTag::setText(const QString &text)
{
mLabel->setText(text);
}
void AxisTag::updatePosition(double value)
{
// since both the arrow and the text label are chained to the dummy tracer (via anchor
// parent-child relationships) it is sufficient to update the dummy tracer coordinates. The
// Horizontal coordinate type was set to ptAxisRectRatio so to keep it aligned at the right side
// of the axis rect, it is always kept at 1. The vertical coordinate type was set to
// ptPlotCoordinates of the passed parent axis, so the vertical coordinate is set to the new
// value.
mDummyTracer->position->setCoords(1, value);
// We want the arrow head to be at the same horizontal position as the axis backbone, even if
// the axis has a certain offset from the axis rect border (like the added second y axis). Thus we
// set the horizontal pixel position of the arrow end (head) to the axis offset (the pixel
// distance to the axis rect border). This works because the parent anchor of the arrow end is
// the dummy tracer, which, as described earlier, is tied to the right axis rect border.
mArrow->end->setCoords(mAxis->offset(), 0);
}
主要应用
主应用程序现在使用了这个新的AxisTag类,从而避免了直接进行项操作,所有这些都是在AxisTag实例中处理的。 这是MainWindow类的标题。与之前一样,可以在内联注释中找到解释:
#include <QMainWindow> #include "qcustomplot.h" #include "axistag.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void timerSlot(); private: Ui::MainWindow *ui;