当前位置: 代码网 > it编程>编程语言>C/C++ > QT 图表的使用(三) -- 图表交互操作

QT 图表的使用(三) -- 图表交互操作

2024年08月01日 C/C++ 我要评论
图表交互操作

在绘制图表时,我们有时需要实现一些交互式操作,例如图表缩放、显示鼠标光标处的数据,坐标、选择曲线上的数据点、显示或隐藏序列等。

在这里插入图片描述

具体功能

  • 图表中包含一个 qlineseries 序列和一个qsplineseries 序列,且显示了数据点。
  • 在图表上拖动鼠标时可以放大图表,或拖动曲线移动。使用鼠标滚轮可以放大或缩小图表。
  • 鼠标光标在图表上移动时,会在状态栏上实时显示鼠标光标处的数据坐标。
  • 鼠标光标移动到一个序列上时,序列颜色会变为红色,并显示曲线上的坐标;鼠标光标移出序列,时序列颜色恢复为黑色。
  • 点击序列上的数据点,数据点的选中状态就会改变。选中的数据点会用专门的颜色显示,并且数据点标记要大于正常数据点的标记,如图 12-9 中所示的几个大一些的数据点。
  • 图例具有类似于复选框的功能,点击图例某一项就可以显示或隐藏对应的序列。
图表交互操作概述
qchart类的功能函数

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的自动放大功能

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()设置的是某种自动放大模式,在拖动鼠标时图表就会放大

qxyseries类的信号

qlineseries 的父类 qxyseries 中定义了很多信号,其中对于交互式操作比较有用

的是如下几个信号。

  • void clicked(const qpointf &point) //点击了曲线
  • void doubleclicked(const qpointf &point) //双击了曲线
  • void hovered(const qpointf &point, bool state) //鼠标光标移入或移出了曲线
  • void pressed(const qpointf &point) //鼠标光标在曲线上,按下了某个鼠标键
  • void released(const qpointf &point) //鼠标光标在曲线上,释放了某个鼠标键
自定义图表视图类tchartview
tchartview的定义

需要在 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 的点击信号关联自定义槽函数,而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()和 clicked()信号的处理

两个序列的 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); 
}
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com