【Qt】拖拽曲线

   许多时候,我们都会用图表显示数据,便于清晰直观的呈现数据走势和变化。某些环境下,更希望能够在图表上拖拽曲线,达到修改数据的目的。之前,使用海思PQ调试工具时,发现gamma模块的曲线拖拽功能做的很好,便用Qt做了一个曲线拖拽的demo(如图1)。一来可以了解海思工具中拖拽曲线的实现方式,二来可以加深对Qt图表操作的理解。
【Qt】拖拽曲线_第1张图片

图1 整体效果示意图

该demo具备以下功能:

  • 鼠标在图表中移动时,呈现十字光标状态,并显示鼠标当前位置的坐标值。
  • 鼠标为十字光标时,按下左键,选择矩形区域进行放大。单击鼠标右键恢复图表大小(如图2所示)。
  • 鼠标移动到数据点附近,光标切换为垂直调整光标,并将数据点的坐标显示在右上角的X,Y编辑框中。
  • 鼠标为垂直调整光标时,按下左键,可拖动数据点,调整数据点的纵坐标(如图3所示)。
  • 捕获到数据点时,可在Y编辑框中编辑该数据点的纵坐标,点击set按钮后生效(如图4所示)。
  • 选中“Force Correctness on Drag”时,当前点的纵坐标yn会被限制在[yn-1,yn+1]范围内。
  • 改变Curve Type的类型,在图表上显示不同类型的曲线(如图5所示)。

   为了能够实现鼠标拖动曲线的功能,需要在QChartView组件中对鼠标事件进行处理。因此需要自定义一个QChartView继承的类,实现鼠标拖动功能。在文中通过自定义CurveChartView类处理鼠标操作,其定义如下。

#ifndef _CURVECHARTVIEW_H
#define _CURVECHARTVIEW_H

#include 
#include 
#include 
#include 

QT_CHARTS_USE_NAMESPACE

class CurveChartView : public QChartView
{
	Q_OBJECT

public:
	CurveChartView(QWidget *parent = Q_NULLPTR);
	~CurveChartView();

	void setForceCorrectnessOnDrag(bool status);			// 限制拖动点范围
	void setSeriesIndex(int index);							// 更改curve type类型
	void setCurrentPointValue(const QPointF &value);		// 修改当前坐标数据
	void setSeriesList(QList<QScatterSeries *> sList, 
		QList<QSplineSeries *> lList);						// 设置曲线数据
	void setYRange(int minVal, int maxVal);					// Y轴范围

protected:
	void mousePressEvent(QMouseEvent *event);
	void mouseMoveEvent(QMouseEvent *event);
	void mouseReleaseEvent(QMouseEvent *event);

private:
	bool forceCorrectnessFlag;		// 拖动范围限制标志
	bool dragPointFlag;				// 拖动数据点标志
	QPoint beginPoint;				// 选择矩形区域的起点
	QPoint endPoint;				// 选择矩形区域的重点
	int pointIndex;					// 当前数据点序号
	int seriesIndex;				// 当前曲线类型
	int dataCount;					// 数据点个数
	int minValue;					// Y轴最小值
	int maxValue;					// Y轴最大值

	QList<QScatterSeries *> scatterList;	// 散点序列集合
	QList<QSplineSeries *> lineList;		// 曲线序列集合

	qreal distance(const QPointF &p1, const QPointF &p2);	// 计算距离
	void detectDragPoint(const QPointF &point);				// 捕获待拖动的数据点
	void updateSeriesData(const QPointF &point);			// 更新数据点数据

signals:
	void signalMouseMovePoint(QPoint point);		// 鼠标移动信号
	void signalCurrentDragPoint(QPointF point);		// 数据点拖动信号

};

#endif

   在设计UI界面时,按照图1所示效果进行布局,并将用于显示图表的Widget控件提升为CurveChartView类。
   在图表范围内,鼠标默认采用十字光标。此状态按下鼠标,会将拖拽模式设置为橡皮筋模式,并记录坐标;释放鼠标,则会记录鼠标释放坐标,放大预览。当点击鼠标右键时,可将预览恢复到初始状态。
   在十字光标状态下,移动鼠标靠近数据点时,鼠标会被数据点捕获,由十字光标切换为垂直调整光标状态。在垂直调整光标状态下,拖动数据点,即可改变当前数据点的值。

/* 按下鼠标 */
void CurveChartView::mousePressEvent(QMouseEvent *event)
{
	QCursor currentCusor = this->cursor();
	if (event->button() == Qt::LeftButton)			// 左键按下
	{
		// 十字光标状态
		if (currentCusor.shape() == Qt::CrossCursor)
		{
			this->setDragMode(QGraphicsView::RubberBandDrag);
			beginPoint = event->pos();
		}
		else		// 垂直调整光标
		{
			this->setDragMode(QGraphicsView::NoDrag);
			dragPointFlag = true;
		}
	}
	QChartView::mousePressEvent(event);
}

/* 移动鼠标 */
void CurveChartView::mouseMoveEvent(QMouseEvent *event)
{
	QPoint point = event->pos();
	emit signalMouseMovePoint(point);
	if (dragPointFlag)		// 检测是否正在拖点
	{
		this->setCursor(Qt::SizeVerCursor);
		updateSeriesData(point);	// 更新拖动点数值
	}
	else
	{
		this->setCursor(Qt::CrossCursor);
		detectDragPoint(point);		// 捕获拖动点
	}

	QChartView::mouseMoveEvent(event);
}

/* 释放鼠标 */
void CurveChartView::mouseReleaseEvent(QMouseEvent *event)
{
	QCursor currentCusor = this->cursor();
	if (event->button() == Qt::LeftButton)
	{
		if (currentCusor.shape() == Qt::CrossCursor)
		{	// 左键释放,十字光标,获取选择矩形终点,缩放图像
			endPoint = event->pos();
			QRectF rectF;
			rectF.setTopLeft(this->beginPoint);
			rectF.setBottomRight(this->endPoint);
			this->chart()->zoomIn(rectF);
		}
		else
		{
			dragPointFlag = false;
		}
	}
	else if (event->button() == Qt::RightButton)
	{	// 右键释放,恢复图表大小
		this->chart()->zoomReset();
	}
	QChartView::mouseReleaseEvent(event);
}

   鼠标移动时,会计算鼠标所在位置与曲线数据之间的距离,当某个数据点与鼠标所在位置的距离小于设定的阈值时,便会将距离最近的数据点记为可拖动的数据点。

/*
	描述: 捕获拖动点
*/
void CurveChartView::detectDragPoint(const QPointF &point)
{
	// check data
	if (scatterList.isEmpty() || lineList.isEmpty())
		return;

	// detect drag point
	QPointF curPoint = this->chart()->mapToValue(point);
	QScatterSeries *curSeries = scatterList.at(seriesIndex);
	QVector<QPointF> seriesData = curSeries->pointsVector();
	for (int i = 0; i < seriesData.count(); i++)
	{
		if (distance(curPoint, seriesData.at(i)) <= 1)		// 距离检测
		{
			pointIndex = i;
			emit signalCurrentDragPoint(seriesData.at(i));	// 标记拖动点
			this->setCursor(Qt::SizeVerCursor);				// 切换光标状态
			break;
		}
	}
}

效果示意:

【Qt】拖拽曲线_第2张图片

图2 选择放大示意图

【Qt】拖拽曲线_第3张图片

图3 拖拽数据点示意图

【Qt】拖拽曲线_第4张图片

图4 修改数据示意图

【Qt】拖拽曲线_第5张图片

图5 更改曲线类型示意图

说明:
   上述功能,采用VS2013+Qt5.8环境编译,且验证通过。由于代码较多,在博客中贴全部源码会显得冗余,且占篇幅。故将完整代码上传GitHub,其地址为:https://github.com/ShrekLi/Programing/tree/master/Qt_OperateCurve。

参考文献:
   [1] 王维波,2018. Qt 5.9 C++开发指南[M]. 北京:人民邮电出版社

个人声明:
   以上内容,纯属个人观点,不喜勿喷。未经本人同意,不得私自转载。博客中出现的代码仅供学习参考,不得有其他用途。若文中存在纰漏,或读者有更好的建议,欢迎留言探讨。也可邮箱联系:[email protected]

你可能感兴趣的:(【程序设计】,c++,qt5)