此示例演示了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();
}