Qt Graphs 模块拟取代 charts 和 data visualization还有很长的路要走

近期关注 Qt 6.10 的分支进展, 发现了 Qt 6.10 的 charts 和 data visualization (以下简称 DV)已经被deprecated, 功能将会合并到 graphs 模块。如果后面 charts\ DV 被弃用,那算是很大的API变化了。从Qt 6.5 以后开始引入的 graphs 使用的是QML的渲染器,和之前的 Qt Widgets GraphicsView 完全不同。如果有依赖上述模块的应用,需要提前评估API的变化。

1. 可能遇到的主要问题

只要是和QML相关的东西,那问题无外乎就是老电脑的兼容性,以及跨语言的API。

1.1 老旧和低成本环境可能不再适用

QML的渲染器过度依赖OS的本地3D加速API,如 Windows的 DirectX。在VMWare/VirtualBox环境下,QML有概率无法正常渲染图形。在只有核心显卡,没有配置独立显卡的低成本计算机上,QML也非常卡顿,甚至可能无法启动(如果驱动比较老或者配置不正确)。

与之形成对比的,是Widgets的2D渲染,基本是在backend里自绘的,因此,不但可以支持老旧计算机,还能在 vnc、framebuf里使用。

1.2 跨语言API调用和性能问题

charts和DV是支持C++ Only模式的开发的。但是, graphs 却不是。官网的例子里,C++ Widgets 程序使用 graphs 必须要进行QML混合编程。这里就牵扯到三个场景:

  1. 从C++中动态向 QML 刷新曲线,特别是1秒刷新23次的这种动画。
  2. 从C++中精确获得QML图元的signal,如橡皮筋选择、有图元被双击等等。
  3. 从C++中查询各个图元的属性和状态。比如查询视图的宽度、高度,选中的状态等。

这些操作要达到高性能、易于使用,需要特别注意不出现内存深度拷贝,且管理好QObject对象树的生命周期(life-cycle)。

2. 初步试用

抱着试试看的态度,初步对 Qt Graphs的API进行了研究,并以最简单的动态折线图进行了开发测试。

pro文件

模块包括 quickwidgets graphs quick 等模块。

pro 复制代码
QT       += core gui widgets quickwidgets graphs quick

CONFIG += c++17

SOURCES += \
    main.cpp \
    graphstest.cpp

HEADERS += \
    graphstest.h

FORMS += \
    graphstest.ui

2.2 main.cpp

cpp 复制代码
#include "graphstest.h"

#include <QApplication>

int main(int argc, char *argv[])
{
	QApplication a(argc, argv);
	graphsTest w;
	w.show();
	return a.exec();
}

2.3 graphstest.h

cpp 复制代码
#ifndef GRAPHSTEST_H
#define GRAPHSTEST_H

#include <QDateTimeAxis>
#include <QDialog>
#include <QLineSeries>
#include <QValueAxis>
#include <QVector>
QT_BEGIN_NAMESPACE
namespace Ui
{
class graphsTest;
}
QT_END_NAMESPACE

class graphsTest : public QDialog
{
	Q_OBJECT

public:
	graphsTest(QWidget *parent = nullptr);
	~graphsTest();

protected:
	void timerEvent(QTimerEvent *evt) override;
private slots:
	void on_pushButton_add_clicked();
	void on_checkBox_update_clicked();

private:
	Ui::graphsTest *ui;
	QDateTimeAxis *m_ax;
	QValueAxis *m_ay;
	int m_timerEvent;
	QVector<QLineSeries *> m_lineSeries;
};
#endif // GRAPHSTEST_H

2.4 graphstest.cpp

cpp 复制代码
#include "graphstest.h"
#include <QDateTime>
#include <QDebug>
#include <QQuickItem>
#include "ui_graphstest.h"
graphsTest::graphsTest(QWidget *parent)
	: QDialog(parent)
	, ui(new Ui::graphsTest)
	, m_ax(new QDateTimeAxis(this))
	, m_ay(new QValueAxis(this))
	, m_timerEvent(-1)
{
	ui->setupUi(this);
	QDateTime dtmNow = QDateTime::currentDateTime();
	m_ax->setMin(dtmNow.addDays(-1));
	m_ax->setMax(dtmNow);
	m_ay->setRange(-100, 100);

	QList<QObject *> seriesList;
	ui->graphsView->setResizeMode(QQuickWidget::SizeRootObjectToView);
	ui->graphsView->setInitialProperties({{"seriesList", QVariant::fromValue(seriesList)},
										  {"axisX", QVariant::fromValue(m_ax)},
										  {"axisY", QVariant::fromValue(m_ay)},
										  {"zoomAreaEnabled", true}});
	ui->graphsView->loadFromModule("QtGraphs", "GraphsView");
}

graphsTest::~graphsTest()
{
	delete ui;
}

void graphsTest::timerEvent(QTimerEvent *evt)
{
	if (evt->timerId() == m_timerEvent)
	{
		QList<QPointF> data;
		QDateTime dtmNow = QDateTime::currentDateTime();
		const int N = m_lineSeries.size();
		for (int n = 0; n < N; ++n)
		{
			for (int i = 0; i < 30; ++i)
			{
				data << QPointF(dtmNow.addSecs(-3600 * 24.0 / 30 * (29 - i)).toMSecsSinceEpoch(),
								(rand() % 500 - 250) / 100.0 + n * 16 - 80);
			}
			m_lineSeries[n]->replace(data);
		}
		m_ax->setMin(dtmNow.addDays(-1));
		m_ax->setMax(dtmNow);
		if (!ui->checkBox_update->isChecked())
		{
			killTimer(m_timerEvent);
			m_timerEvent = -1;
		}
	}
	QDialog::timerEvent(evt);
}

void graphsTest::on_pushButton_add_clicked()
{
	if (m_lineSeries.size() >= 10)
		return;
	//Add to Graph
	QVariant seriesListVariant = ui->graphsView->rootObject()->property("seriesList");
	if (seriesListVariant.canConvert<QQmlListProperty<QObject>>())
	{
		QLineSeries *newLine = new QLineSeries(this);
		newLine->setColor(QColor(rand() % 128, rand() % 128, rand() % 128));
		newLine->setHoverable(true);
		newLine->setName(QString("Testing %1").arg(m_lineSeries.size()));
		connect(newLine,
				&QAbstractSeries::hover,
				[this](const QString &seriesName, QPointF position, QPointF value) -> void
				{
					ui->lineEdit_msg->setText(QString("%1:%2 %3 = %4 %5")
												  .arg(seriesName)
												  .arg(position.x())
												  .arg(position.y())
												  .arg(value.x())
												  .arg(value.y()));
				});
		//Add
		QQmlListProperty<QObject> prop = seriesListVariant.value<QQmlListProperty<QObject>>();
		prop.append(&prop, newLine);
		m_lineSeries.append(newLine);
	}
	if (m_timerEvent < 0)
		m_timerEvent = startTimer(500);
}

void graphsTest::on_checkBox_update_clicked()
{
	if (ui->checkBox_update->isChecked())
		m_timerEvent = startTimer(500);
}

2.5 graphstest.ui

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>graphsTest</class>
 <widget class="QDialog" name="graphsTest">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>579</width>
    <height>332</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>graphsTest</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout_2">
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QQuickWidget" name="graphsView">
       <property name="resizeMode">
        <enum>QQuickWidget::ResizeMode::SizeRootObjectToView</enum>
       </property>
      </widget>
     </item>
     <item>
      <layout class="QVBoxLayout" name="verticalLayout">
       <property name="sizeConstraint">
        <enum>QLayout::SizeConstraint::SetMaximumSize</enum>
       </property>
       <item>
        <widget class="QPushButton" name="pushButton_add">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="text">
          <string>Add Serials</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QCheckBox" name="checkBox_update">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="text">
          <string>Updating</string>
         </property>
        </widget>
       </item>
       <item>
        <spacer name="verticalSpacer">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="orientation">
          <enum>Qt::Orientation::Vertical</enum>
         </property>
         <property name="sizeHint" stdset="0">
          <size>
           <width>20</width>
           <height>40</height>
          </size>
         </property>
        </spacer>
       </item>
      </layout>
     </item>
    </layout>
   </item>
   <item>
    <widget class="QLineEdit" name="lineEdit_msg">
     <property name="sizePolicy">
      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
       <horstretch>0</horstretch>
       <verstretch>0</verstretch>
      </sizepolicy>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <customwidgets>
  <customwidget>
   <class>QQuickWidget</class>
   <extends>QWidget</extends>
   <header location="global">QtQuickWidgets/QQuickWidget</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

3. 运行效果

运行效果如下,主要问题包括(BUG):

  1. 新插入的对象的色彩,会影响到旧对象的色彩。
  2. 各个连线的收尾会连接起来。

4 评价

Qt 团队看起来对传统的 Widgets 已经不是很愿意维护了,这种大规模的API变化,以及完全不顾及兼容老计算机、集成显卡的环境的激进改进,要么是公司no zuo no die,要么说明QML技术的趋势已经势不可挡。

QML技术与C++的紧密互动,相当程度的缓解了API跨语言的问题,其实QML就是个调用C++ QObject 运行时的胶水语言,其和C++的联系非常紧密。对Qt Widgets而言,如果使用了 QML的功能,就会在硬件兼容性上面大打折扣,这对于很多桌面场景而言是不能接受的。对未来技术的发展,我们会保持持续的跟踪,并适时为我们使用 charts 的既有代码进行 graphs的适配,并在具备 charts 的环境下优先使用 charts.

相关推荐
落笔映浮华丶1 小时前
蓝桥杯零基础到获奖-第3章 C++ 变量和常量
c++·蓝桥杯
肥or胖2 小时前
【音视频协议篇】RTSP系列
c++·笔记·音视频
WebGoC开发者2 小时前
C++题解(37) 信息学奥赛一本通1318:【例5.3】自然数的拆分
c++·算法·青少年编程·题解
Aurora_wmroy3 小时前
算法竞赛备赛——【图论】求最短路径——小结
数据结构·c++·算法·蓝桥杯·图论
whoarethenext3 小时前
使用 C++ 和 OpenCV 进行表面划痕检测
开发语言·c++·opencv·划痕检测
tomato093 小时前
Codeforces Round 1037 (Div. 3)(补题)
c++
遇见尚硅谷4 小时前
C语言:20250719笔记
c语言·开发语言·数据结构·c++·算法
mit6.8244 小时前
7.17 滑动窗口 |assign |memo |pii bfs |位运算
c++·算法
寄思~5 小时前
PyQt5—Qt QDialog 学习笔记
开发语言·笔记·python·qt·学习