思想记录--QWT如何实现点选数据?

本文介绍了一种基于QwtPlot的鼠标点击选点优化方法。通过重写鼠标点击事件,实现高效的点选数据功能。文章详细阐述了点选数据的思想、优化策略及编程实现过程。

一、效果展示

在这里插入图片描述


二、点选数据思想

策略:重写QwtPlot类的鼠标点击事件,鼠标点击之后可以获得鼠标点击处坐标点P(x,y):

QPointF P = this->canvas()->mapFromGlobal(QCursor::pos());

但是点P是软件的物理坐标,需要将之转化为视窗上的画布坐标点Q(x,y):

QPointF Q = QPointF(this->invTransform(QwtPlot::xBottom,P.x()),this->invTransform(QwtPlot::yLeft,P.y())); 

附:视窗上的画布坐标转换为软件的物理坐标:

QPointF P = QPointF(this->transform(QwtPlot::xBottom,Q.x()),this->transform(QwtPlot::yLeft,Q.y()));

得到了点击处相对于视窗的坐标Q之后,就可以去判断是否选中数据。

在这里插入图片描述

判断标准:以点Q为圆心,设置一个阈值半径r,如果比较点在圆内,即两点之间的距离小于半径r;

灵敏度由阈值半径r来控制,r大则匹配较为粗糙,r小则匹配较为精细;

如果视窗上只有一条曲线,从头到尾遍历曲线,用点Q去比较,如果匹配到数据点,则标记(QwtPlotMarker)出曲线上的该数据点,如果没匹配到数据点,则说明没有点中数据点。

如果视窗上有多条曲线,则参考上述方法,依次遍历各条曲线,遍历完曲线1,遍历曲线2,依次类推。一旦在遍历过程中匹配到数据点,则退出,无须再向后寻找。


三、查找数据优化

上述遍历曲线匹配数据点存在弊端,小数据量还行,一旦数据量大,匹配就会很迟钝,所以必须对查找数据策略进行优化。

在这里插入图片描述

策略:点选数据点必在曲线绘制完之后,所以存在曲线的数据集:QVector<QVector<double> > Data

数据绘制完之后,为了便于查找,需对曲线数据集进行处理。

得到一个新的曲线数据集:QVector<QVector<Node *> > Data_solve

如上图示:依据x轴进行切片,将曲线划分为n的数据节点(Node),数据节点的定义如下:

typedef struct data_node
{
	QVector<double> data;
    double data_min;
    double data_max;
}Node;

每个节点包含一部分数据(data),和该部分数据的最大值(data_max)、最小值(data_min);

划分好之后,就可以按照如下策略去匹配数据:

在这里插入图片描述

获得相对于视窗的点击点Q之后,先去第一条曲线中匹配,用点Q和Node中的最大值、最小值比较,如果点Q在范围内,则说明点Q可能在该区间,进入该节点进行遍历匹配,如果匹配到了数据点,则退出;如果没匹配到数据点,则开始去第二条曲线中匹配,依次类推,遍历完所有曲线都没有匹配到数据点则说明为点中数据点。

优化后效率分析

假如有三条曲线,每条曲线100个数据点,考虑最坏情况,匹配点是最后一条曲线里的最后一个点。

如果按照常规遍历方式,最坏情况需要比较300次。

如果采用优化后的策略,假如将每条曲线划分为10个数据节点,每个节点里10个数据点,最坏情况需要比较10+10+10+10=40次,可见效率显著提高。


四、编程实现

数据存储:子节点定义

class Node
{
public:
	Node();
	~Node();
	
	QVector<QPointF> data; //子节点数据
	double data_max;       //子节点数据最大值
	double data_min;       //子节点数据最小值

	//更新子节点最大、最小值
	void update_data_min_max() //因为data有序,所以直接取首尾即可
	{
		this->data_min = data[0].x(); 			  //更新区间最小值
		this->data_max = data[data.size()-1].x(); //更新区间最大值
	}
};

1、整理数据

采集的数据是独立的,X一路,Y一路,需要将两路数据合并成点集数据:QVector<QPointF>

void Neaten_Data() //整理数据
{
	Data.clear();
	QVector<QVector<double> > Data_X;
	QVector<QVector<double> > Data_Y;
	get_data_record(Data_X,Data_Y); //获取X-Y数据

	int Size = Data_X.size() < Data_Y.size() ? Data_X.size() : Data_Y.size();
	for(int i=0; i<Size; i++)//遍历数据生成QVector<QPointF> Curve_data
	{
		int size = Data_X[i].size() < Data_Y[i].size() ? Data_X[i].size() : Data_Y[i].size();
		QVector<QPointF> Curve_data;
		for(int j=0; j<size; j++)
		{
			Curve_data.push_back(QPointF(Data_X[i][j],Data_Y[i][j]));
		}
		Data.push_back(Curve_data); //将一条曲线数据追加到曲线集中中
		Curve_data.clear();
	}

	Data_Sort(Data); //依据x对数据点进行从小到大排序
	Depart_Data(Data,Data_solve); //划分数据节点
}

2、数据排序

数据集可能不是参考x轴从小到大有序的,所以需对数据进行排序操作。

此处借助QMap(或QHash),因为QMap会依据key进行排序,不管插入的先后顺序,所以只需将x作为key,y作为value,插入QMap,然后遍历QMap,将结果读入QVector<QPointF>即可。

void Data_Sort(QVector<QVector<QPointF> > &Data) //依据x轴的值从小到大排序
{
	QVector<QPointF> sortedPointVector;
	QMap<double, double>sonicMap; //利用QMap自动排序
	for (int i = 0; i < Data.size(); i++)
	{
		for(int j = 0; j<Data[i].size(); j++)
		{
			sonicMap.insert(Data[i][j].x(), Data[i][j].y());
		}

		QMap<double, double>::Iterator it = sonicMap.begin();
		while (it!=sonicMap.end())
		{
			QPointF sonicPoint;
			sonicPoint.setX(it.key());
			sonicPoint.setY(it.value());
			sortedPointVector.append(sonicPoint);
			it++;
		}

		Data[i] = sortedPointVector;
		sortedPointVector.clear();
		sonicMap.clear();
	}
}

3、数据划分

每个节点的上限是10个数据点,将曲线划分为多个节点

void Depart_Data(QVector<QVector<QPointF> > &Data, QVector<QVector<Node *> > &Data_solve)
{
	QVector<Node *> Curve_data_solve; //划分之后的单条曲线数据
	QVector<QPointF> Curve_data;      //未划分的单条曲线数据
	Node* node; 					  //单个节点
	for(int i=0; i<Data_solve.size(); i++) //释放之前的内存空间
	{
		for(int j=0; j<Data_solve[i].size(); j++)
		{
			node = Data_solve[i][j];
			delete node;
			node = NULL;
		}
	}
	Data_solve.clear();

	for(int i=0; i<Data.size(); i++) //n条曲线
	{
		Curve_data_solve.clear();
		Curve_data.clear();
		Curve_data = Data[i]; //取出一条曲线
		int size = Curve_data.size(); //曲线长度

		if(size<=10) //如果曲线长度小于10,则该曲线只有一个节点
		{
			node = new Node;
			node->data = Curve_data;
			node->update_data_min_max();
			Curve_data_solve.push_back(node);
		}
		else
		{	
			int count = 0;
			int node_full_num = size / 10; //满10个数据点的节点个数
			int node_last_num = size % 10; //最后一个数据节点的数据点数

			for(int j=0; j<node_full_num; j++)//整理满10个数据点的节点
			{
				node = new Node;
				for(int t=0; t<10; t++)
				{
					node->data.push_back(Curve_data[count+t]);
				}
				node->update_data_min_max();
				Curve_data_solve.push_back(node);
				count += 10;
			}

			if(node_last_num>0) //存在不满10的节点
			{
				node = new Node;
				for(int k=0; k<node_last_num; k++)//整理不满10个数据点的节点
				{
					node->data.push_back(Curve_data[count+k]);
				}
				node->update_data_min_max();
				Curve_data_solve.push_back(node);
				count += node_last_num;
			}
		}

		Data_solve.push_back(Curve_data_solve); //将整理后的单条曲线数据追加到曲线数据集中
	}
}

4、鼠标点击事件

void mousePressEvent(QMouseEvent *) //鼠标点击事件
{
	/*
	绘制选中点策略:
		1、在画布上添加鼠标点击事件,获取鼠标点击处的坐标,并通过映射,得到对应的画布坐标;
		2、依次遍历每条曲线的每个节点,将点击坐标与数据坐标相匹配;
		3、如果数据坐标在以点击坐标为圆心的圆内,则判定选中数据,遍历停止;
		4、将QwtPlotMarker的坐标设置为选中的数据坐标;
	*/
	if(choose_point_status)//选点
	{
		QPointF P = this->canvas()->mapFromGlobal(QCursor::pos());  //获取鼠标点击处的坐标(物理) 
		QPointF Q = QPointF(this->invTransform(current_x,P.x()),this->invTransform(current_y,P.y())); //将物理坐标转换为画布坐标
		for(int i=0; i<Data_solve.size(); i++)//遍历n条曲线
		{
			for(int j=0; j<Data_solve[i].size(); j++)//遍历每条曲线的节点
			{
				//找到合适的节点
				if(Q.x()>=Data_solve[i][j]->data_min && Q.x()<=Data_solve[i][j]->data_max)
				{
					for(int k=0; k<Data_solve[i][j]->data.size(); k++)//进入节点遍历匹配数据
					{
						double dist_result = cal_twoPoint_dis(Q,Data_solve[i][j]->data[k]); //计算“数据坐标”和“鼠标点击点”两点之间的距离
						if(dist_result <= R) //以鼠标点击点为圆心,如果有数据坐标在圆内(两点之间的距离小于半径)
						{
							choose_point->setXValue(Data_solve[i][j]->data[k].x()); //给标记点QwtPlotMarker设置坐标
							choose_point->setYValue(Data_solve[i][j]->data[k].y());
							current_pos = QPointF(Data_solve[i][j]->data[k].x(),Data_solve[i][j]->data[k].y()); //记录当前选中的数据点坐标
							this->replot(); //更新绘图事件
							break; //只要点击坐标匹配到数据坐标则退出
						}
					}
				}
			}
		}
		
		return;
	}//if(choose_point)
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝勒里恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值