Qt中PaintEvent绘制实时波形图的实现示例

目录
  • 绘制思路
    • 1:接收硬件传入的数据
    • 2:定时器动态刷新页面
    • 3:真实数据处理
      • 第一步:每进行一次数据更新,都需要剔除超时显示数据。
      • 第二步:筛查有效数据,并记录
    • 4:图形绘制

上一篇文章讲述了如何使用控件进行波形图绘制,虽然很方便,但是也有一些无法避免的问题,比如说:动态绘制图形时,想要流畅的进行波动,就必须按照特定的时间实时更换数据。

接来下,我们采用在paintEvent中绘制的方式进行实时波形图绘制,首先,我们先展示下显示效果吧!

数据来源依旧是硬件传入的实时数据,如下:

[0, 3, 5, 8, 10, 13, 15, 18, 20, 23, 25, 28, 26, 23, 20, 16, 13, 11, 9, 6, 4, 3, 0]

其实有些人看到这里会说,数据都有了直接画出来不就可以了吗?

在我们实际应用过程中,这些硬件上传的数据不是一次性传出的,而是取决于你操作硬件的频率以及事件决定的。所以说,想要一次性拿出一整条数据来绘制,这个时机已经晚了。

那么,我们该如何使用paintEvent实时绘制出波形图呢?接下来就来讲解下我的思路吧,如果觉得我的思路比较繁琐,大家也可以提出来,我也学习下,弥补下自己的不足。

绘制思路

1:接收硬件传入的数据

这里使用了SetRealTimeDepthData(stDepthData stData);意思是:设置实时深度数据值。

参数穿入的是一个结构体,在使用这个函数之前我已经将数据做了简单的处理,包括了深度方向设置。

比如:当深度逐渐变大时,深度方向div是正数,当深度逐渐减小时,深度方向div是负数。

下面,我展示下我实际处理后的数据值

对于这些真实数据我设定了一个结构体,用于存储数据时间、深度方向以及具体深度值

struct DrawingEffectivePress
{
    int nDiv; //深度方向
    int nDepth; //深度值
    DWORD dwTime; //记录当前真实数据的时间
    DrawingEffectivePress():nDiv(0),nDepth(0),dwTime(0){}
}

SetRealTimeDepthData具体实现,如下:

void QDrawingWaveform::SetRealTimeDepthData(stDepthData stData)
{
    std::lock_guard<std::mutex> lck(m_dataMutex); //加锁进行数据操作
    DrawingEffectivePress stDepth;
    stDepth.nDiv = stData.nDiv>=0?1:-1;
    stDepth.nDepth = stData.nDepth;
    stDepth.dwTime = GetTickCont();
    m_vetDepth.push_back(stDepth);
}

代码讲解:

有上述图片的真实数据来看,深度的方位是逐渐递增的,那么在程序中我们采用了1和-1的方式表示,正方向时都是用1来表示,负方向时都是用-1来表示。

每有一条真实数据时,都需要记录当前真实数据的具体时间,用于绘制实时的动态走向。

2:定时器动态刷新页面

设定定时器每40毫秒刷新一次页面:

#define TimeInterval 40 //定时器时间间隔

定时器启动 m_nTimerId = startTimer(TimeInterval);

3:真实数据处理

这是我们绘制的一个重点,也是比较麻烦的一部分了。

与硬件打过交道的友友们都知道,硬件数据的不稳定性,有些时候看着数据的走向是朝下的,因为手动操作缘故,偶尔会有一些浮动的数据,这些数据需要筛除,在传入数据之前我已经做了处理,这个问题在这篇文章中是不存在的。

使用m_vetDepth存储了实际的深度数据值。

std::vector<DrawingEffectivePress> m_vetDepth;

第一步:每进行一次数据更新,都需要剔除超时显示数据。

什么叫做超时显示数据?

根据文章一开篇的动画可以看出,波形图一边进行绘制操作,一边向左移动。

在移动过程中,肯定会移出左边界,那么也就代表了当前的图形不需要展示。对此,我们就需要在每次更新数据时,判断有哪些数据是已经超过显示范围的,需要进行剔除了。

那么,到这里也就遇到了另外一个问题,我们剔除的数据是在m_vetDepth中存储的数据吗?

答案是的,但是为了逻辑简单操作,我们需要重新定义一个结构体,当前结构体主要用来做已经绘制成图形的点的记录。

std::vector<DrawingEffectivePress> m_vetEffectiveDepth;

在当前容器中存储的数据一定是具体特定标识的,也就是波形图的拐点数据,一个完整波形的最低点以及最高点。

当我们进行实际绘图时,也是取m_vetEffectiveDepth中的数据,保证了数据逻辑简单性。

现在,我们先来看一看剔除超时数据的实际代码,如下:

void QDrawingWaveform::DeletingTimeoutData()
{
    DWORD dwCurrentTime = GetTickCount(); //当前时间
    std::vector<DrawingEffectivePress>::iterator itvet = m_vetEffectivePress.begin();
    for (itvet; itvet != m_vetEffectivePress.end();)
    {
	DrawingEffectivePress stPoint = *itvet;
	if ((dwCurrentTime - stPoint.dwPressTime) > m_nSingShowTime)
	{
            //超过界面展示范围,剔除数据
            itvet = m_vetEffectivePress.erase(itvet++);
	}
	else
            itvet++;
    }
}

代码解析:实时获取最新时间,每次都判断存储的硬件操作时间与设定的最大值(m_nSingShowTime)进行比较。

当超过设定的时间时,说明图形已经消失在界面上了,就需要剔除数据。

第二步:筛查有效数据,并记录

上一步骤是剔除超时数据,那么我们该如何存储这些有效数据到m_vetEffectivePress容器中呢?

思路:

1:默认容器中存储前两条数据。

2:当后续数据来时,需要与上一条数据进行判别。

2.1:如果方向一致,说明深度一直在递增或者是递减,直接替换最后一会有效数据的值即可。

2.2:如果方向不一致,说明深度值发生了变换,可能由向下变成了向上;也可能由向上变成了向下。不再做数据替换操作,而是插入一条新数据。

3:操作一条数据后,进行数据删除。

将上述思路转变成代码,如下:

std::vector<DrawingEffectivePress>::iterator itvet = m_vetPress.begin()
for (itvet; itvet != m_vetPress.end(); )
{
	DrawingEffectivePress stPoint = *itvet;
	DrawingEffectivePress stPress;
	stPress.dwTime = stPoint.dwTime;
	stPress.nDiv = stPoint.nDiv;
	stPress.nDepth = stPoint.nDepth;
	int nsize = m_vetEffectivePress.size();
	if (nsize < 2)
	{
		m_vetEffectivePress.push_back(stPress);
	}
	else
	{
            //如果当前数据stPoint与m_vetEffectivePress的最后一位的拐点一致,
            //剔除掉m_vetEffectivePress的最后一位,存储成最新数据
            if (m_vetEffectivePress[nsize - 1].nDiv == stPoint.nDiv)
            {
                    //更新m_vetEffectivePress的最后一位
                    m_vetEffectivePress[nsize - 1] = stPress;
            }
            else
            {
                    //存储的最后一位的拐点与当前数值不一致时,直接存储
                    m_vetEffectivePress.push_back(stPress); 

            }
	}

	itvet = m_vetPress.erase(itvet++);
}

4:图形绘制

经过上述数据处理后,我们可以直接在panitEvent中直接绘制出m_vetEffectivePress图形了。

实际代码效果如下:

QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing); //抗锯齿
QPolygon polygon;
for (int i = 0; i < m_vetEffectivePress.size(); i++)
{
	DrawingEffectivePress stPoint = m_vetEffectivePress[i];
	int nX = this->CalcRealPointX(stPoint.dwPressTime);
	int nY = this->CalcRealPointY(stPoint.nDepth);
	polygon << QPoint(nX, nY);
}
painter.drawPolyline(polygon);

代码解析:根据实际数据的操作时间以及具体的深度值,可以确定波形图的x轴与y轴了。

根据时间的变化,就可以让图形看起来是一种动起来的效果。

CalcRealPointX实际处理

//TODO:计算,实际的x轴坐标
DWORD dwCurrent = GetTickCount();
int nRectRight = rect().right();
int nMoveOriginal = dwDepthTime - dwCurrent;
double dMoveLen = nMoveOriginal / 8;
int nX = nRectRight + dMoveLen;
return nX;

代码解析:实时获取当前绘制时间,并减去结构体中存储的深度时间,设置移动长度(dwMoveOriginal)。

因为要向左偏移,所以,每次用窗口的右侧区域减去就可以了。

注意:我这里使用的是"+",因为我计算得出的偏移长度一定是一个负值。实时时间一定会比实际深度时间大,所以结果肯定是一个负值。

到这里,我们的实时图形绘制算是完成了80%了,想要绘制出连续的实时深度值图形就可以实现了。

为什么说是80%呢?

因为还有一个我们需要考虑的问题,当不是连续数据获取时,使用QPolygon绘制图形时,就会出现以下效果:

当我们不是连续绘制深度值时,间隔一定时间后,再进行绘图时,就会出现上述红色区域框出来的诡异现象。

按照实际应用的绘制效果就应该如同文章刚开始的效果一样,间隔一定时间后,再次绘制,应该还是一条完整的波形数据。

QPolygon这个绘制类显然使用上述代码是不支持的,即使我们换成了DrawLine的方式,这个问题还是需要被解决的。

此时,我们就需要做一个特殊处理,当我们没有实际深度值数据时,需要实时知道当前的绘制点在哪个位置,也就是说,在没有真实数据来临之前,我们需要每间隔一个绘图数据刷新时间,需要绘制一条直线,而不是直接从上一个绘制点直接绘制波形图。

到此这篇关于Qt中PaintEvent绘制实时波形图的实现示例的文章就介绍到这了,更多相关Qt PaintEvent绘制实时波形图内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Qt利用QChart实现实时波形图的绘制

    目录 前言 1.QChart配置以及使用 1.1QChart环境配置 1.2控件使用 1.3代码配置 2.QChat设置动态折线图 2.1基础数据设置 2.2定时器控制数据变动 3.实际硬件数据采集图形绘制 4.总结 前言 前两天需要做一个绘图功能.我们的需求是这样子的:硬件实时发送数据,绘制数据波形图,要求波形图根据数据实时变化. 展示效果如下: 如果有和我一样的功能,大家可以继续往下看,没准会对你有一定的帮助哟~ 看上面的展示图会发现,怎么这么丑!确实,根据硬件实时传入的数据以及时间不同,产

  • Qt中PaintEvent绘制实时波形图的实现示例

    目录 绘制思路 1:接收硬件传入的数据 2:定时器动态刷新页面 3:真实数据处理 第一步:每进行一次数据更新,都需要剔除超时显示数据. 第二步:筛查有效数据,并记录 4:图形绘制 上一篇文章讲述了如何使用控件进行波形图绘制,虽然很方便,但是也有一些无法避免的问题,比如说:动态绘制图形时,想要流畅的进行波动,就必须按照特定的时间实时更换数据. 接来下,我们采用在paintEvent中绘制的方式进行实时波形图绘制,首先,我们先展示下显示效果吧! 数据来源依旧是硬件传入的实时数据,如下: [0, 3,

  • QT中QChart绘制折线图

    目录 一.简单绘图 二.绘制折线图 实例 一.简单绘图 Qt Charts基于Qt的Graphics View架构,其核心组件是QChartView 和 QChart QChartView是显示图标的视图,基类为QGraphicsView QChart的基类是QGraphicsltem 类的继承关系: 创建项目:.pro文件中添加:QT += charts代码如下:其他文件未修改,修改代码只在mainwindow.cpp文件中进行.mainwindow.cpp #include "mainwin

  • QT中窗口关闭自动销毁的实现示例

    目录 qt关于窗口关闭触发函数/信号 方法一. 方法二. 我们知道c++内存管理的一个规则:new出来的对象,一定要delete. 我们实现一个弹窗的时候,有时候继承widget,有时候继承dialog.不管哪种,窗体调用show方法后,不可能直接delete. 当然,new该窗体的时候,指定了父对象,就可以不用显示调用delete,它会随着父对象的销毁而销毁. 那这时候,关闭弹窗的操作,就仅仅是隐藏了.为了实现真正的delete.我们需要设施窗口的一个属性 DeleteOnClose,具体方法

  • C# 绘制实时折线图,波形图

    此Demo是采用VS自带的Chart图表控件,制作实时动态显示的折线图,和波形图.本文仅供学习分享使用,如有不足之处,还请指正. 涉及知识点: Chart 控件,功能强大,可以绘制柱状图,折线图,波形图,饼状图,大大简化了对图的开发与定制. Chart控件的相关概念: ChartArea,表示图表区域,一个Chart可以绘制多个ChartArea,重叠在一起. Series ,表示数据序列,每个ChartArea可以有多个数据线.即,Series属于ChartArea. AxisX,AxisY,

  • Qt中QPainter与坐标的使用

    目录 一.坐标系统与坐标变换 示例 二.可缩放图形 一.坐标系统与坐标变换 坐标系统 QPainter坐标变换相关函数 分组 函数原型 功能 坐标变换 void translate(qreal dx,qreal dy) 坐标系统一定的偏移量,坐标原点平移到新的点 void rotate(qreal angle) 坐标系统顺时针旋转-一个角度 void scale(qreal sx,qreal sy) 坐标系统缩放 void shear(qrael sh,qreal sy) 坐标系统做扭转变换 状

  • QT利用QPainter绘制三维饼状图

    目录 一.项目介绍 二.项目基本配置 三.UI界面设置 四.主程序实现 4.1 widget.h头文件 4.2 widget.cpp源文件 五.效果演示 一.项目介绍 本文介绍利用QPainter实现三维饼状图的绘制,由于Qt中没有三维饼状图的绘制组件,因此只能自行绘制. 二.项目基本配置 新建一个Qt案例,项目名称为“PieTest”,基类选择“QWidget”,取消选中创建UI界面复选框,完成项目创建. 三.UI界面设置 无UI界面 四.主程序实现 4.1 widget.h头文件 头文件中只

  • Qt中QZXing 的编译与使用

    目录 0.前言 1.编译 2.二维码生成 3.二维码识别 0.前言 zxing 是一个开源的一维/二维条码图像处理库,当前版本为 Java 语言开发: https://github.com/zxing/zxing QZXing 是 ZXing 的 Qt 移植版本,同样还有 cpp 等语言和框架的移植版本.从 QZXing 的文档可以看到,只有 QR Code 二维码支持编码,其他都只支持解码. https://github.com/ftylitak/qzxing 1.编译 下载源码后可以用 CM

  • Qt使用QWT绘制柱状图详解

    目录 1:设置QChart的整体背景色 2:设置有效区域的背景色 3:设置X.Y坐标轴数据 4:设置网格线 5:插入实际数据 6:X轴刻度值优化 7:设置X轴文本偏移 8:设置每个柱状体的宽度 9:设置每个柱状体的偏移量 10:修改鼠标的显示状态 有的时候我们会遇到这样一种功能,需要在柱状图中显示不同颜色的柱状体,每个主状态代表的状态不同,那么如果只是用简单的QChart是难以实现的. QT中提供了一个叫做QWT的库.QWT,全称是Qt Widgets for Technical Applica

  • C#绘制实时曲线的方法

    本文实例为大家分享了C#绘制实时曲线的具体代码,供大家参考,具体内容如下 1.要做一个调试工具,采集传感器数据并显示.绘制曲线注意坐标反转,线条的张力即可.项目中的曲线是从右往左显示的,线条的坐标都放在list里了,效果如下图: 2.上代码 public class DrawingCurve     {         private Graphics graphics; //Graphics 类提供将对象绘制到显示设备的方法         private Bitmap bitmap; //位

随机推荐