5.QCustomPlot 创建动态轴标签

此示例演示了QCustomPlot 标签的更高级用法。创建一个新的小型类AxisTag,该类管理一组条目,这些条目一起形成指向轴并突出显示特定坐标的标签。

为了在主应用程序中展示它,在rect的右侧创建了两个轴,并制作了两个相应的标签来指示两个图形的最右边的数据点值,这些值不断更新。

本教程随附的示例项目称为axis-tags-example  ,是完整软件包下载的一部分

一个新类来处理所涉及的项目

您在上面的屏幕快照中看到的标签包含两个可见的条目:一个QCPItemText,它为我们提供当前坐标(由矩形边框包围的文本),以及一个 QCPItemLine  ,其头部的线尾设置为箭头形状,从而提供了箭头指向靠左。

但是,还有另一个不可见的条目可以帮助您定位标签。甲QCPItemTracer在各坐标值的高度坐在右轴矩形边界(内轴的水平位置)。它提供了其他条目的主要父锚,因此上下移动该跟踪器将上下移动整个标签。

MainWindow 代码中单独管理所有三个条目将容易出错,并且样式不好。因此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;
  QCustomPlot *mPlot;
  QPointer<QCPGraph> mGraph1;
  QPointer<QCPGraph> mGraph2;
  AxisTag *mTag1;
  AxisTag *mTag2;
  QTimer mDataTimer;
};

最后执行MainWindow该类。它基本上在右侧设置了两个y轴,两个图形和两个轴标签。在timerSlot() 反复通过一个叫QTimer。在插槽中,新的数据点被添加到图形中,并且标签通过其方法进行更新。AxisTag::updatePosition

#include "mainwindow.h"
#include "ui_mainwindow.h"
 
MainWindow::MainWindow(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::MainWindow),
  mPlot(0),
  mTag1(0),
  mTag2(0)
{
  ui->setupUi(this);
   
  mPlot = new QCustomPlot(this);
  setCentralWidget(mPlot);
   
  // configure plot to have two right axes:
  mPlot->yAxis->setTickLabels(false);
  connect(mPlot->yAxis2, SIGNAL(rangeChanged(QCPRange)), mPlot->yAxis, SLOT(setRange(QCPRange))); // left axis only mirrors inner right axis
  mPlot->yAxis2->setVisible(true);
  mPlot->axisRect()->addAxis(QCPAxis::atRight);
  mPlot->axisRect()->axis(QCPAxis::atRight, 0)->setPadding(30); // add some padding to have space for tags
  mPlot->axisRect()->axis(QCPAxis::atRight, 1)->setPadding(30); // add some padding to have space for tags
   
  // create graphs:
  mGraph1 = mPlot->addGraph(mPlot->xAxis, mPlot->axisRect()->axis(QCPAxis::atRight, 0));
  mGraph2 = mPlot->addGraph(mPlot->xAxis, mPlot->axisRect()->axis(QCPAxis::atRight, 1));
  mGraph1->setPen(QPen(QColor(250, 120, 0)));
  mGraph2->setPen(QPen(QColor(0, 180, 60)));
   
  // create tags with newly introduced AxisTag class (see axistag.h/.cpp):
  mTag1 = new AxisTag(mGraph1->valueAxis());
  mTag1->setPen(mGraph1->pen());
  mTag2 = new AxisTag(mGraph2->valueAxis());
  mTag2->setPen(mGraph2->pen());
   
  connect(&mDataTimer, SIGNAL(timeout()), this, SLOT(timerSlot()));
  mDataTimer.start(40);
}
 
MainWindow::~MainWindow()
{
  delete ui;
}
 
void MainWindow::timerSlot()
{
  // calculate and add a new data point to each graph:
  mGraph1->addData(mGraph1->dataCount(), qSin(mGraph1->dataCount()/50.0)+qSin(mGraph1->dataCount()/50.0/0.3843)*0.25);
  mGraph2->addData(mGraph2->dataCount(), qCos(mGraph2->dataCount()/50.0)+qSin(mGraph2->dataCount()/50.0/0.4364)*0.15);
 
  // make key axis range scroll with the data:
  mPlot->xAxis->rescale();
  mGraph1->rescaleValueAxis(false, true);
  mGraph2->rescaleValueAxis(false, true);
  mPlot->xAxis->setRange(mPlot->xAxis->range().upper, 100, Qt::AlignRight);
   
  // update the vertical axis tag positions and texts to match the rightmost data point of the graphs:
  double graph1Value = mGraph1->dataMainValue(mGraph1->dataCount()-1);
  double graph2Value = mGraph2->dataMainValue(mGraph2->dataCount()-1);
  mTag1->updatePosition(graph1Value);
  mTag2->updatePosition(graph2Value);
  mTag1->setText(QString::number(graph1Value, 'f', 2));
  mTag2->setText(QString::number(graph2Value, 'f', 2));
   
  mPlot->replot();
}

 

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值