资讯详情

QCustomPlot开发笔记(二):QCustomPlot用户交互、元素项以及特殊用法

若该文为原创文章,转载请注明原文出处 博客地址:https://hpzwl.blog.csdn.net/article/details/124567140

红胖子(红模仿)博文全集:开发技术集合(包括Qt实用技术,树莓派,三维,OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等。…(点击传送门)

上一篇:《QCustomPlot开发笔记(1):QCustomPlot简介、下载和基本绘图 下一篇文章期待…

前言

??在最后一篇文章中,基本绘图继续描述用户交互、元素项和Qt一些特殊用法。

QCustomPlot用户交互

??QCustomPlot 提供多种内置的用户交互。它们大致可以分为:

  • 通过鼠标拖动和滚动鼠标滚轮进行范围操作
  • 单击选择绘图实体
  • 用户点击绘图实体时发出的信号

范围操作

拽托

??用户操作轴范围的默认方法是相应的QCPAxisRect执行拖动操作。 ??在这里插入图片描述 ??要在QCustomPlot拖动小部件的应用范围需要标志添加到当前允许的交互中。这可以通过并指定。默认情况下,两个方向都允许。 ??在拖动操作过程中,通过配置更新配置轴的范围,自动调用replots。这会给用户一种用鼠标抓取绘图坐标平面的印象。最初,范围拖动轴配置为矩形的底部和左侧轴。 ??在拖动操作过程中,通过实时更新范围配置的轴,自动导致重新绘制。这给用户一种用鼠标抓住图形坐标平面来移动它的印象。最初,范围拖动轴配置为矩形底部和左轴。QCustomPlot默认情况下,小部件是横轴和纵轴,它们是

缩放

??用户可以使用鼠标滚轮来更改范围的大小,即放大或缩小绘图。此行为由交互标志组成控制,该标志还需要通过激活。就像范围拖动一样,缩放也可选择受影响的轴和方向。请参见函数。 ??另外,可以使用控制缩放因子。在普通鼠标硬件上,鼠标滚轮的步长对应于应用于轴范围的因素。如果系数大于1,向前滚动鼠标滚轮将减小范围(放大),向后滚动将增加范围(缩小)。要逆转此行为,请将鼠标滚轮缩放因子设置为小于1(但大于零)。缩放始终以绘图中当前鼠标光标的位置为中心。这意味着将光标指向感兴趣的功能,滚动鼠标滚轮可以放大。

选择机制

?? ??,如轴和图形。 开头的交互标志通常可以控制绘图中特定类别的实体。例如,设置 允许用户单击选择图表(如图形)。文档中的所有交互标志。允许同时选择多个对象,请设置交互标志。然后,用户可以按住多选择修改器(见)默认情况下,修改器连续选择多个对象

控制个体的选择性和选择性

??在单个对象上使用函数可以进一步微调可选性。例如,如果用户不能在绘图中选择特定的图形,请调用。可以通过函数通过编程修改选定状态。即使禁止用户的选择性,也可以通过编程改变选择状态。 ??取消绘图中所有对象的选择,请调用

选择对象的外观

??选定的对象通常用不同的笔、画笔或字体显示。可以使用配置其他方法。可以看出,它们的命名类似于原始(非选择)属性,但前缀是选择。

多部分对象

??有些物体(如轴和图例)的外观比较复杂,所以仅仅选择一个布尔值是不够的。在这种情况下,都是标志的or组合(相应的)QFlags类型称为SelectableParts)。每个多部件对象定义自己SelectablePart类型。 ?? ??例如,QCPAxis概念上由三部分组成:轴主干、标签(数字)和带有标记的轴标签。因为这三部分可以单独选择,QCPAxis::SelectablePart定义了。可以选择轴干和刻度标签,但不能选择轴标签,可以调用。为了控制多个对象的当前选择状态,请使用它方法。

反应选择的变化

??选择变更后,每个对象都会发出一个名字信号。无论是改是由用户引起的,还是通过调用编程造成的都无所谓。 ??如果用户交互改变了绘图中的选择,则会发出QCustomPlot宽信号。在与此信号相连的插槽中,可以检查某些对象的选择状态,并做出相应的反应。该方法可能有助于检索特定类型的选择对象。

用户交互信号

??QCustomPlot独立于选择机制,在用户交互时发出各种信号。最低级别是信号。当QCustomPlot当触发小部件的相应事件时,它们会发出。请注意,。然而,如果你不想QCustomPlot这些信号允许更容易访问简单任务的用户交互。 ??图中某些对象的单击和双击也有更高级别的信号报告:。所有这些信号都将报告单击中哪个对象(如果是多个对象,则报告单击中哪个部分)和相关信号QMuseEvent。

元素项:支持的图形元素

  

使用箭头和文本的基本示例

  此示例显示如何创建始终位于轴矩形顶部的文本标签,以及将打印坐标中的点与该标签连接的箭头。

// 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项目定位的灵活性实现的。项目可以在打印坐标、绝对像素坐标和轴矩形大小的分数单位中定位。的文档更详细地介绍了如何使用这些不同的可能性。      与绘图表一样,创建自己的项目也很容易。这可以通过创建自己的QCPAbstractItem子类来实现。请参阅QCPAbstractItem文档中的子类部分。

项剪切

  默认情况下,项目被剪裁到主轴矩形,这意味着它们仅在主轴矩形内可见。要使项目在该axis rect外部可见,请通过调用 禁用剪裁。   另一方面,如果希望将项目剪裁到不同的轴rect,可以通过指定它。项目的clipAxisRect属性仅用于剪裁行为,原则上与项目可能通过其位置成员绑定到的坐标轴无关(请参见)。但是,通常情况下,用于剪裁的轴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,是完整下载包的一部分。   

预设限制

  与在滚动条和轴之间来回传播更改相关的信号是。由于我们希望保持正常范围的拖动和缩放,当轴的rangeChanged信号发出时,滚动条滑块的位置和大小必须更新。   QScrollBar是基于整数的。因此,我们需要一个将整数滚动条值转换为轴坐标的因子。例如,如果希望能够在坐标范围-5到5之间平滑地滚动轴,我们可以将因子设置为0.01(即将滚动条值除以100),从而将滚动条的范围设置为-500,500。

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; 

标签: 线条连接器快插矩形连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台