赞
踩
在绘制图表时,我们有时需要实现一些交互式操作,例如图表缩放、显示鼠标光标处的数据,坐标、选择曲线上的数据点、显示或隐藏序列等。
具体功能
QChart 的上层父类是 QGraphicsItem,其功能类似于图形/视图架构中的图形项的功能。QChart有一些接口函数可实现图表的移动、缩放等操作
QPointF mapToPosition(const QPointF &value, QAbstractSeries *series = nullptr)
QPointF mapToValue(const QPointF &position, QAbstractSeries *series = nullptr)
void zoom(qreal factor) //缩放图表,factor 值大于 1 表示放大,factor 值为 0~1 表示缩小
void zoomIn() //放大 2 倍
void zoomIn(const QRectF &rect) //放大到最大,使得 rect 表示的矩形范围依然能被显示
void zoomOut() //缩小到原来的一半
void zoomReset() //恢复原始大小
void scroll(qreal dx, qreal dy) //移动图表的可视区域,参数单位是像素
QChartView 有一个函数 setRubberBand()可以设置在视图上用鼠标框选时的放大模式:
void QChartView::setRubberBand(const QChartView::RubberBands &rubberBand)
枚举类型 QChartView::RubberBand 有以下几种枚举值。
• QChartView::NoRubberBand:无任何动作,不自动放大。
• QChartView::VerticalRubberBand:拖动鼠标时,自动绘制一个矩形框,宽度等于整个图的宽度,高度等于鼠标拖动的范围的高度。释放鼠标后,放大显示此矩形框内的内容。
• QChartView::HorizontalRubberBand:拖动鼠标时,自动绘制一个矩形框,高度等于整个图的高度,宽度等于鼠标拖动的范围的宽度。释放鼠标后,放大显示此矩形框内的内容。
• QChartView::RectangleRubberBand:拖动鼠标时,自动绘制一个矩形框,宽度和高度分别等于鼠标拖动的范围的宽度和高度。释放鼠标后,显示效果与 VerticalRubberBand 模式的基本相同,只是垂直方向放大,没有放大显示框选的矩形框的内容。这应该是 Qt 6.2 的一个 bug。
• QChartView::ClickThroughRubberBand:这是一个额外的选项,需要与其他选项进行或运算,再作为函数 setRubberBand()的参数。使用这个选项后,鼠标的 clicked()信号才会被传递给图表中的序列对象,否则,在自动框选放大模式下,序列接收不到 clicked()信号。
在 QChartView 的父类 QGraphicsView 中有一个函数 setDragMode(),用于设置鼠标拖动模式,
它的函数原型定义如下:
void QGraphicsView::setDragMode(QGraphicsView::DragMode mode)
参数 mode 是枚举类型 QGraphicsView::DragMode,其各种枚举值的作用如下。
• QGraphicsView::NoDrag:无动作。
• QGraphicsView::ScrollHandDrag:鼠标光标变成手形,拖动鼠标时会拖动图中的曲线。
• QGraphicsView::RubberBandDrag:鼠标光标变成十字形,拖动鼠标时会自动绘制一个矩形框。
函数 setDragMode()设置的值不会影响 QChartView 的自动放大功能,即不管 setDragMode()设置的是什么鼠标拖动模式,只要函数 setRubberBand()设置的是某种自动放大模式,在拖动鼠标时图表就会放大
QLineSeries 的父类 QXYSeries 中定义了很多信号,其中对于交互式操作比较有用
的是如下几个信号。
需要在 QChartView 组件里对鼠标事件和按键事件进行处理,这就需要自定义一个从
QChartView 继承的类。
TChartView 类的定义如下:
class TChartView : public QChartView { Q_OBJECT private: QPoint beginPoint; //选择矩形区域的起点 QPoint endPoint; //选择矩形区域的终点 bool m_customZoom= false; //是否使用自定义矩形放大模式 protected: void mousePressEvent(QMouseEvent *event); //鼠标左键被按下 void mouseReleaseEvent(QMouseEvent *event); //鼠标左键被释放 void mouseMoveEvent(QMouseEvent *event); //鼠标移动 void keyPressEvent(QKeyEvent *event); //按键事件 void wheelEvent(QWheelEvent *event); //鼠标滚轮事件,缩放 public: TChartView(QWidget *parent = nullptr); ~TChartView(); void setCustomZoomRect(bool custom); //设置是否使用自定义矩形放大模式 signals: void mouseMovePoint(QPoint point); //鼠标移动信号 };
下面是 TChartView 类构造函数和公有函数 setCustomZoomRect()的代码:
TChartView::TChartView(QWidget *parent):QChartView(parent)
{
this->setMouseTracking(true); //必须设置为 true,这样才会实时产生 mouseMoveEvent 事件
this->setDragMode(QGraphicsView::NoDrag); //设置拖动模式
this->setRubberBand(QChartView::NoRubberBand); //设置自动放大模式
}
void TChartView::setCustomZoomRect(bool custom)
{
m_customZoom= custom;
}
拖动鼠标框选范围时,会触发 mousePressEvent()和 mouseReleaseEvent()事件处理函数,这两个函数的代码如下:
void TChartView::mousePressEvent(QMouseEvent *event) {//鼠标左键被按下,记录 beginPoint if (event->button() == Qt::LeftButton) beginPoint= event->pos(); QChartView::mousePressEvent(event); //父类继续处理事件,必须如此调用 } void TChartView::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { endPoint= event->pos(); if ((this->dragMode() == QGraphicsView::ScrollHandDrag) &&(this->rubberBand() == QChartView::NoRubberBand)) //移动 chart()->scroll(beginPoint.x()-endPoint.x(), endPoint.y() - beginPoint.y()); else if (m_customZoom && this->dragMode() == QGraphicsView::RubberBandDrag) {//放大 QRectF rectF; rectF.setTopLeft(beginPoint); rectF.setBottomRight(endPoint); this->chart()->zoomIn(rectF); //按矩形区域放大 } } QChartView::mouseReleaseEvent(event); //父类继续处理事件,必须如此调用 }
void TChartView::mouseMoveEvent(QMouseEvent *event) {//鼠标移动事件 QPoint point= event->pos(); emit mouseMovePoint(point); //发射信号 QChartView::mouseMoveEvent(event); //父类继续处理事件 } void TChartView::keyPressEvent(QKeyEvent *event) {//按键控制 switch (event->key()) { case Qt::Key_Left: chart()->scroll(10, 0); break; case Qt::Key_Right: chart()->scroll(-10, 0); break; case Qt::Key_Up: chart()->scroll(0, -10); break; case Qt::Key_Down: chart()->scroll(0, 10); break; case Qt::Key_PageUp: chart()->scroll(0, -50); break; case Qt::Key_PageDown: chart()->scroll(0, 50); break; case Qt::Key_Escape: chart()->zoomReset(); break; default: QGraphicsView::keyPressEvent(event); } } void TChartView::wheelEvent(QWheelEvent *event) {//鼠标滚轮事件处理,缩放 QPoint numDegrees = event->angleDelta()/8; if (!numDegrees.isNull()) { QPoint numSteps = numDegrees/15; //步数 int stepY=numSteps.y(); //垂直方向上滚轮的滚动步数 if (stepY >0) //大于 0,前向滚动,放大 chart()->zoom(1.1*stepY); else chart()->zoom(-0.9*stepY); } event->accept(); }
采用可视化方法设计主窗口界面,在工作区放置一个 QGraphicsView 组件,然后将其提升为TChartView 类,将其对象名称设置为 chartView。主窗口类 MainWindow 的定义如下:
class MainWindow : public QMainWindow { Q_OBJECT private: QChart *chart; //图表对象 QLabel *lab_chartXY; //状态栏上的标签 QLabel *lab_hoverXY; QLabel *lab_clickXY; void createChart(); //创建图表 void prepareData(); //准备数据 int getIndexFromX(QXYSeries *series, qreal xValue, qreal tol=0.05); //返回数据点的序号 public: MainWindow(QWidget *parent = nullptr); private slots: void do_legendMarkerClicked(); //图例被点击 void do_mouseMovePoint(QPoint point); //鼠标移动 void do_series_clicked(const QPointF &point); //序列被点击 void do_series_hovered(const QPointF &point, bool state); //移入或移出序列 private: Ui::MainWindow *ui; };
MainWindow 类中有几个自定义槽函数,用于与一些信号关联并进行处理。函数 getIndexFromX()用于在一个序列中根据参数xValue的值确定数据点的序号,在用鼠标选择数据点时会用到这个函数。
MainWindow 类的构造函数代码如下:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); this->setCentralWidget(ui->chartView); lab_chartXY = new QLabel("Chart X=, Y= "); //用于添加到状态栏的 QLabel 组件 lab_chartXY->setMinimumWidth(200); ui->statusBar->addWidget(lab_chartXY); lab_hoverXY = new QLabel("Hovered X=, Y= "); lab_hoverXY->setMinimumWidth(200); ui->statusBar->addWidget(lab_hoverXY); lab_clickXY = new QLabel("Clicked X=, Y= "); lab_clickXY->setMinimumWidth(200); ui->statusBar->addWidget(lab_clickXY); createChart(); //创建图表 prepareData(); //生成数据 connect(ui->chartView,SIGNAL(mouseMovePoint(QPoint)), this, SLOT(do_mouseMovePoint(QPoint))); //鼠标移动事件 }
构造函数里创建了图表,还将 chartView 的 mouseMovePoint()信号与槽函数 do_mouseMovePoint()关联。创建图表的代码如下:
void MainWindow::createChart() { //创建图表 chart = new QChart(); ui->chartView->setChart(chart); ui->chartView->setRenderHint(QPainter::Antialiasing); ui->chartView->setCursor(Qt::CrossCursor); //设置鼠标光标为十字形 QLineSeries *series0 = new QLineSeries(); series0->setName("LineSeries 曲线"); series0->setPointsVisible(true); //显示数据点 series0->setMarkerSize(5); //数据点大小 series0->setSelectedColor(Qt::blue); //选中点的颜色 connect(series0,&QLineSeries::clicked, this, &MainWindow::do_series_clicked); connect(series0,&QLineSeries::hovered, this, &MainWindow::do_series_hovered); QSplineSeries *series1 = new QSplineSeries(); series1->setName("SplineSeries 曲线"); series1->setPointsVisible(true); series1->setMarkerSize(5); series1->setSelectedColor(Qt::blue); //选中点的颜色 connect(series1,&QSplineSeries::clicked, this, &MainWindow::do_series_clicked); connect(series1,&QSplineSeries::hovered, this, &MainWindow::do_series_hovered); QPen pen(Qt::black); pen.setStyle(Qt::DotLine); //虚线 pen.setWidth(2); series0->setPen(pen); pen.setStyle(Qt::SolidLine); //实线 series1->setPen(pen); chart->addSeries(series0); chart->addSeries(series1); QValueAxis *axisX = new QValueAxis; axisX->setRange(0, 10); axisX->setLabelFormat("%.1f"); //标签格式 axisX->setTickCount(11); //主刻度个数 axisX->setMinorTickCount(2); axisX->setTitleText("time(secs)"); QValueAxis *axisY = new QValueAxis; axisY->setRange(-2, 2); axisY->setLabelFormat("%.2f"); //标签格式 axisY->setTickCount(5); axisY->setMinorTickCount(2); axisY->setTitleText("value"); chart->addAxis(axisX,Qt::AlignBottom); //坐标轴添加到图表中,并指定方向 chart->addAxis(axisY,Qt::AlignLeft); series0->attachAxis(axisX); //序列 series0 附加坐标轴 series0->attachAxis(axisY); series1->attachAxis(axisX); //序列 series1 附加坐标轴 series1->attachAxis(axisY); foreach (QLegendMarker* marker, chart->legend()->markers()) connect(marker, SIGNAL(clicked()), this, SLOT(do_legendMarkerClicked())); } void MainWindow::prepareData() {//为序列生成数据 QLineSeries *series0= (QLineSeries *)chart->series().at(0); QSplineSeries *series1= (QSplineSeries *)chart->series().at(1); qreal t=0, y1,y2, intv=0.5; int cnt= 21; for(int i=0; i<cnt; i++) { int rd= QRandomGenerator::global()->bounded(-5,6); //随机整数,[-5,5] y1= qSin(2*t)+rd/50; series0->append(t,y1); rd= QRandomGenerator::global()->bounded(-5,6); //随机整数,[-5,5] y2= qSin(2*t+20)+rd/50; series1->append(t,y2); t += intv; } }
自定义槽函数 do_mouseMovePoint()与界面组件 chartView 的 mouseMovePoint()信号关联,该函数代码如下:
void MainWindow::do_mouseMovePoint(QPoint point)
{
QPointF pt= chart->mapToValue(point); //变换为图表的坐标
QString str= QString::asprintf("Chart X=%.1f,Y=%.2f",pt.x(),pt.y());
lab_chartXY->setText(str); //状态栏上显示
}
主要是利用QLegendMarker 的点击信号关联自定义槽函数,而QLegendMarker的成员函数type可以返回图例标记类型,下面是这个自定义槽函数do_legendMarkerClicked()的代码:
void MainWindow::do_legendMarkerClicked() { QLegendMarker* marker= qobject_cast<QLegendMarker*> (sender()); marker->series()->setVisible(!marker->series()->isVisible()); //序列的可见性 marker->setVisible(true); //图例标记总是可见的 qreal alpha= 1.0; if (!marker->series()->isVisible()) alpha= 0.5; //设置为半透明表示序列不可见 QBrush brush= marker->labelBrush(); QColor color= brush.color(); color.setAlphaF(alpha); brush.setColor(color); marker->setLabelBrush(brush); //设置文字的 brush brush= marker->brush(); color= brush.color(); color.setAlphaF(alpha); brush.setColor(color); marker->setBrush(brush); //设置图例标记的 brush }
两个序列的 hovered()信号关联同一个槽函数 do_series_hovered(),这个函数的代码如下:
void MainWindow::do_series_hovered(const QPointF &point, bool state)
{
QString str= "Series X=, Y=";
if (state)
str= QString::asprintf("Hovered X=%.1f,Y=%.2f",point.x(),point.y());
lab_hoverXY->setText(str); //状态栏显示
QLineSeries *series= qobject_cast<QLineSeries*> (sender()); //获取信号发射者
QPen pen= series->pen();
if (state)
pen.setColor(Qt::red); //鼠标光标移入序列,序列变成红色
else
pen.setColor(Qt::black); //鼠标光标移出序列,序列恢复为黑色
series->setPen(pen);
}
void MainWindow::do_series_clicked(const QPointF &point) { QString str= QString::asprintf("Clicked X=%.1f,Y=%.2f",point.x(),point.y()); lab_clickXY->setText(str); //状态栏显示 QLineSeries *series= qobject_cast<QLineSeries*> (sender()); //获取信号发射者 int index= getIndexFromX(series, point.x()); //获取数据点序号 if (index<0) return; bool isSelected= series->isPointSelected(index); //数据点是否被选中 series->setPointSelected(index,!isSelected); //设置状态,选中或取消选中 } int MainWindow::getIndexFromX(QXYSeries *series, qreal xValue, qreal tol) { QList<QPointF> points= series->points(); //返回数据点的列表 int index= -1; for (int i=0; i<points.count(); i++) { qreal dx= xValue - points.at(i).x(); if (qAbs(dx) <= tol) { index= i; break; } } return index; //-1 表示没有找到 }
TChartView 类里对鼠标事件和按键事件进行了处理,通过鼠标操作和按键操作就可以进行图表的缩放和移动,操作方式还与 QChartView 的 dragMode()和 rubberBand()函数的值有关。窗口上方有两个下拉列表框用于设置拖动模式和框选模式,其代码如下:
void MainWindow::on_comboDragMode_currentIndexChanged(int index) {// 设置拖动模式,dragMode,有 3 种模式: NoDrag、ScrollHandDrag、RubberBandDrag ui->chartView->setDragMode(QGraphicsView::DragMode(index)); } void MainWindow::on_comboRubberBand_currentIndexChanged(int index) {//设置框选模式, rubberBand ui->chartView->setCustomZoomRect(index == 4); //是否自定义模式 //必须有 ClickThroughRubberBand,才能将 clicked()信号传递给序列 QFlags<QChartView::RubberBand> flags= QChartView::ClickThroughRubberBand; switch(index) { case 0: ui->chartView->setRubberBand(QChartView::NoRubberBand); return; case 1: flags |= QChartView::VerticalRubberBand; //垂直方向选择 break; case 2: flags |= QChartView::HorizontalRubberBand; //水平方向选择 break; case 3: case 4: flags |= QChartView::RectangleRubberBand; //矩形框选 } ui->chartView->setRubberBand(flags); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。