意见箱
恒创运营部门将仔细参阅您的意见和建议,必要时将通过预留邮箱与您保持联络。感谢您的支持!
意见/建议
提交建议

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

来源:恒创科技 编辑:恒创科技编辑部
2024-01-28 20:00:59


一、效果展示

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


二、点选数据思想

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


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

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之后,就可以去判断是否选中数据。

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

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

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

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

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


三、查找数据优化

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

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

策略:点选数据点必在曲线绘制完之后,所以存在曲线的数据集:​​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​​);

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

思想记录--QWT如何实现点选数据?_最小值_04

获得相对于视窗的点击点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)
}


上一篇: QWT--滚轮放大缩小和拖拽视窗 下一篇: 手机怎么远程登录云服务器?