【Qt】拖拽曲线

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

图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 <QtCharts/QChartView>
#include <QtCharts/QScatterSeries>
#include <QtCharts/QSplineSeries>
#include <QtCore/QtMath>

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;
		}
	}
}

效果示意:

Alt

图2 选择放大示意图

Alt

图3 拖拽数据点示意图

Alt

图4 修改数据示意图

Aly

图5 更改曲线类型示意图

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

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

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

React Hooks 是 React 16.8 中新增的特性,它可以让你在函数组件中使用 state、生命周期钩子等 React 特性。使用 Hooks 可以让你写出更简洁、可复用且易于测试的代码。 React Hooks 提供了一系列的 Hook 函数,包括 useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect 和 useDebugValue。每个 Hook 都有特定的用途,可以帮助你处理不同的问题。 下面是 React Hooks 的一些常用 Hook 函数: 1. useState useState 是最常用的 Hook 之一,它可以让你在函数组件中使用 state。useState 接受一个初始状态值,并返回一个数组,数组的第一个值是当前 state 值,第二个值是更新 state 值的函数。 ``` const [count, setCount] = useState(0); ``` 2. useEffect useEffect 可以让你在组件渲染后执行一些副作用操作,比如订阅事件、异步请求数据等。useEffect 接受两个参数,第一个参数是一个回调函数,第二个参数是一个数组,用于控制 useEffect 的执行时机。 ``` useEffect(() => { // 这里可以执行副作用操作 }, [dependencies]); ``` 3. useContext useContext 可以让你在组件树中获取 context 的值。它接受一个 context 对象,并返回该 context 的当前值。 ``` const value = useContext(MyContext); ``` 4. useRef useRef 可以让你在组件之间共享一个可变的引用。它返回一个对象,该对象的 current 属性可以存储任何值,并在组件的生命周期中保持不变。 ``` const ref = useRef(initialValue); ref.current = value; ``` 5. useCallback useCallback 可以让你缓存一个函数,以避免在每次渲染时都创建一个新的函数实例。它接受一个回调函数和一个依赖数组,并返回一个 memoized 的回调函数。 ``` const memoizedCallback = useCallback(() => { // 这里是回调函数的逻辑 }, [dependencies]); ``` 6. useMemo useMemo 可以让你缓存一个计算结果,以避免在每次渲染时都重新计算。它接受一个计算函数和一个依赖数组,并返回一个 memoized 的计算结果。 ``` const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); ``` 以上就是 React Hooks 的一些常用 Hook 函数,它们可以帮助你更好地处理组件状态、副作用、上下文和性能优化等问题。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值