【QT性能优化】QT性能优化之QT6框架高性能统计图框架快速展示百万个数据点曲线图

QT性能优化之QT6框架高性能统计图框架快速展示百万个数据点曲线图

百万个数据点的QT统计图运行效果

10万个数据点运行效果截图:

在这里插入图片描述

10条曲线,每条曲线10000个数据点,共10万个数据点。帧率达到60FPS

在这里插入图片描述

从任务管理器可以看到,QT曲线图使用了GPU硬件加速功能。

100万个数据点运行效果截图:

在这里插入图片描述

10条曲线,每条曲线10万个数据点,共100万个数据点。帧率比较接近60FPS。

为什么曲线图看起来像是这么实在的实心条块状的呢?这是因为一条曲线上面有10万个数据点,不可避免的有相当多一部分数据点紧贴在一块了。这个例子只是为了展示QT统计图的运行性能。

在这里插入图片描述

QT曲线图中的数据点数量提升了10倍,GPU占用率也增加了一些。

100万个数据点运行效果视频展示:

QT应用能否快速展示百万数据点曲线图FPS达到60以上?看了补天云这个实测视频就知道结果了!QT性能优化之QT6框架高性能统计图框架快速展示百万个数据点曲线图

QT高性能曲线图百万个数据点

为什么在短视频中看起来帧率FPS没有那么高呢?这是因为同时在录制视频的缘故。屏幕录像确实影响到了QT统计图的实际运行帧率。截图所示帧率是在没有运行屏幕录像程序的真实运行情况下的性能。

本文视频请访问下面这个链接:

视频: QT性能优化之QT6框架高性能统计图框架快速展示百万个数据点曲线图效果

百万个数据点的QT统计图程序的源代码

在使用QT统计图模块时,必须在项目文件中引入QT Charts模块支持。

cmake应用:

find_package(Qt6 REQUIRED COMPONENTS Charts)

target_link_libraries(mytarget PRIVATE Qt6::Charts)

qmake应用:

QT += charts

qml应用:

import QtCharts

前述程序的源代码如下所示:

QApplication a(argc, argv);
 a.setApplicationDisplayName("补天云C/C++/QT系列视频课程");
 
 //创建QT统计图对象
 QChart* chart = new QChart();
 
 //隐藏图例
 chart->legend()->hide();
 
 //创建X轴和Y轴。
 QValueAxis* axis_x = new QValueAxis();
 axis_x->setRange(0.f, 1.f);
 QValueAxis* axis_y = new QValueAxis();
 axis_y->setRange(0.f, 1.f);
 chart->addAxis(axis_x, Qt::AlignBottom);
 chart->addAxis(axis_y, Qt::AlignLeft);
 
 //共10条曲线
 constexpr int SERIES_COUNT = 10;
 //每条曲线有10份随机生成的数据,每次显示时选择一份数据
 constexpr int ROW_COUNT = 10;
 //每条曲线有10万个数据点。
 constexpr int POINT_COUNT = 10000 * 10;
 
 
 //每条曲线的颜色
 QColor colors[] = {QColor(0, 0, 0),
 QColor(255, 0, 0),
 QColor(0, 255, 0),
 QColor(0, 0, 255),
 QColor(255, 255, 0),
 QColor(255, 0, 255),
 QColor(0, 255, 255),
 QColor(255, 0, 0),
 QColor(0, 255, 0),
 QColor(0, 0, 255)};
 
 //创建这10条曲线
 QList<QLineSeries*> series_list;
 for (int i = 0; i < SERIES_COUNT; i++)
 {
 QLineSeries* series = new QLineSeries();
 //使用OpenGL以便使用GPU硬件加速功能
 series->setUseOpenGL(true);
 series->setPen(QPen(colors[i]));
 chart->addSeries(series);
 series->attachAxis(axis_x);
 series->attachAxis(axis_y);
 series_list << series;
 }
 
 //创建QT统计图视图对象,用于显示统计图
 QChartView* cv = new QChartView(chart);
 
 //提生成随机化数据
 QList<QList<QList<QPointF>>> datas;
 datas.resize(SERIES_COUNT);
 
 //为每一条曲线生成初始化数据
 for (int k = 0; k < SERIES_COUNT; k++)
 {
 datas[k].resize(ROW_COUNT);
 for (int i = 0; i < ROW_COUNT; i++)
 {
 for (int j = 0; j < POINT_COUNT; j++)
 {
 float x = 1.0f * j / POINT_COUNT;
 float y = 1.f * k / SERIES_COUNT +
 0.8f / SERIES_COUNT *
 (QRandomGenerator::global()->generate() % POINT_COUNT) / POINT_COUNT;
 datas[k][i] << QPointF(x, y);
 }
 }
 }
 
 //主窗口
 ButianyunWidget w;
 QVBoxLayout* main_layout = new QVBoxLayout();
 w.setLayout(main_layout);
 
 //FPS标签
 QLabel* label = new QLabel();
 main_layout->addWidget(label);
 main_layout->addWidget(cv);
 
 //统计已经逝去的时间
 static QElapsedTimer* elapsed_timer = new QElapsedTimer();
 //当前使用哪一份数据
 static int index = 0;
 //定时器,用于控制统计图数据刷新
 QTimer* timer = new QTimer();
 timer->setSingleShot(true);
 timer->setInterval(0);
 QObject::connect(timer, &QTimer::timeout, timer, [=]{
 for (int k = 0; k < SERIES_COUNT; k++)
 {
 series_list[k]->replace(datas[k][index]);
 }
 index = (index + 1) % ROW_COUNT;
 
 //统计FPS
 static int frame_count = 0;
 frame_count++;
 if (elapsed_timer->elapsed() > 1000)
 {
 int fps = frame_count * 1000.f / elapsed_timer->elapsed();
 label->setText(QString("FPS:%1").arg(fps));
 elapsed_timer->restart();
 frame_count = 0;
 }
 });
 
 //用于在合适的时候启动刷新定时器
 QObject::connect(chart->scene(), &QGraphicsScene::changed,
 chart, [timer]{
 timer->start();
 });
 
 //最大化显示主窗口
 w.showMaximized();
 
 return a.exec();

QT统计图功能和效果展示

QT统计图支持多种类型的统计图、包括面积图、曲线图、饼状图、散点图、极点图、蜡烛图等

enum SeriesType {
SeriesTypeLine,
SeriesTypeArea,
SeriesTypeBar,
SeriesTypeStackedBar,
SeriesTypePercentBar,
SeriesTypePie,
SeriesTypeScatter,
SeriesTypeSpline,
SeriesTypeHorizontalBar,
SeriesTypeHorizontalStackedBar,
SeriesTypeHorizontalPercentBar,
SeriesTypeBoxPlot,
SeriesTypeCandlestick
};

在这里插入图片描述

QT统计图模块支持的最常见统计图

在这里插入图片描述

QT统计图模块支持的极点图(Polar)

在这里插入图片描述

QT统计图模块支持的蜡烛图(candlesticks)

在这里插入图片描述

QT统计图支持的箱线图(box-and-whiskers)

但是只有两种类型的统计图支持GPU硬件加速功能。
QLineSeries和QScatterSeries。

在这里插入图片描述

QLineSeries系列折线图

在这里插入图片描述

QScatterSeries系列散点图

QT统计图模块整体结构

在这里插入图片描述

QT统计图模块整体结构

QT统计图模块整体上可分为QT统计图、QT视图、QT坐标轴、QT系列、QT图例、QT模型映射器等几个组成部分。QT视图和QT统计图这两部分都是基于QT图形视图框架构造而成。QT视图同时还使得QT统计图模块可以很好的与QT Widgets窗口系统进行集成。QT模型视图映射器搭建了一个横跨QT统计图模块与QT模型视图代理框架之间的适配层。

由此可见,QT统计图模块基于一种开放式的架构设计,成功的将QT框架中各种现有框架和技术集成起来了,并且这种集成设计确实提升了QT统计图模块的整体性能和成熟度。从这个角度来看,QT统计图模块的这种架构设计称得上是一种非常成功的设计。

比如,QT系列中的一些提供了是否支持OpenGL的选项,使得QT统计图模块能够借助OpenGL提供的GPU硬件加速功能将所能支持的数据点的数量提升几个数量级。QT统计图控件之所以能够支持以接近FPS=60的帧率绘制出总共包含100万个数据点的10条曲线图,根本原因在于QT统计图借助了GPU硬件加速功能,将一部分绘图工作交给了GPU去完成,最终大大的提升了QT统计图的整体绘图性能。实测表明实际上在启用OpenGL支持的情况下,QT统计图控件能够支持百万个数据点而几乎不损失帧率。

QT统计图和QT图形视图框架

QT统计图模块整体上是在QT图形视图框架的基础之上构建的。QChart类型实际上就是从QT图形视图框架中的图形对象类型QGraphicsObject派生而来的。QT统计图视图类型QChartView是从QT图形视图框架中的图形视图类型QGraphicsView类型派生而来的。另外,QT统计图的抽象系列类型QAbstractSeries则是直接从QObject类型派生而来的。

在这里插入图片描述

QT统计图模块和QT图形视图框架

图形视图提供了一个表面用于管理大量自定义的二维图形条目并与之进行交互,还提供了一个视图窗口用于可视化这些图形条目,并提供缩放和旋转支持。

这个框架包含了事件传播体系,这个体系允许对场景中的条目的双精度的交互能力。条目能够处理键盘事件、鼠标按下、移动、释放和双击事件,条目还能跟踪鼠标运动。

图形视图使用BSP(二叉空间剖分)树来提供很快速的条目发现能力,作为这种能力的一个结果,它还能实时的可视化拥有数以百万计的宏大场景。

看了上面这段话,自然就明白了为什么QT统计图组件在设计时选择了基于QT图形视图框架。

下面是QT助手API DOC中对QT图形视图框架的介绍文字原文:
Graphics View provides a surface for managing and interacting with a large number of custom-made 2D graphical items, and a view widget for visualizing the items, with support for zooming and rotation.
The framework includes an event propagation architecture that allows precise double-precision interaction capabilities for the items on the scene. Items can handle key events, mouse press, move, release and double click events, and they can also track mouse movement.
Graphics View uses a BSP (Binary Space Partitioning) tree to provide very fast item discovery, and as a result of this, it can visualize large scenes in real-time, even with millions of items.

QT助手API DOC中可以查看QT图形视图框架的更多具体介绍,在此将该介绍的目录列举如下所示,有兴趣的朋友可以自行查看阅读。

在这里插入图片描述

QT助手API DOC中的QT统计图模块

在补天云C/C++/QT系列课程中,具体介绍了具体如何开发基于QT图形视图框架的应用程序,有兴趣的朋友们也可以了解一下。
在这里插入图片描述

QT统计图和QT模型视图代理框架

QT模型视图代理框架是QT应用编程领域的一个重要的基础设施,无论在传统的基于QT Widgets的应用开发模式,还是在新一代的基于QT QML/QT Quick的应用开发模式中,QT模型视图代理框架都具有无法忽视的地位和举足轻重的价值。

在这里插入图片描述

QT模型视图代理框架应用实例

通常,模式视图类型可以被分为三组概念组件:模型、视图、代理。这些组件中的每一个使用一个抽象类来定义,该抽象类提供了通用的接口,并且在一些情况下提供了一些特性的默认实现。抽象类意味着可以被子类化从而提供被别的组件所期待的一整套完整的功能;这也允许编写出特化处理的组件。
模型视图代理之间使用信号与槽进行通讯:
模型发出信号通知视图,数据源持有的数据已经发生变化。
视图发出信号提供关于用户与被显示的条目之间的交互的信息。
代理发出信号用于在编辑过程总告诉模型和视图关于编辑器的状态的信息。

QT助手中介绍QT模型视图代理框架的特点的原文如下所示:
Generally, the model/view classes can be separated into the three groups described above: models, views, and delegates. Each of these components is defined by abstract classes that provide common interfaces and, in some cases, default implementations of features. Abstract classes are meant to be subclassed in order to provide the full set of functionality expected by other components; this also allows specialized components to be written.
Models, views, and delegates communicate with each other using signals and slots:

Signals from the model inform the view about changes to the data held by the data source.
Signals from the view provide information about the user’s interaction with the items being displayed.
Signals from the delegate are used during editing to tell the model and view about the state of the editor.

下图展示了模型视图代理框架的整体架构和架构中的模型(Model)、视图(View)、代理(Delegate)三者之间的关系。

在这里插入图片描述

QT模型视图代理框架整体架构

下图展示了QT模型视图代理框架所支持的三种类型的数据模型,也是在软件开发中描述数据组织形式时最常用的三种基础形式。
一维数据模型:列表模型; 二维数据模型:表格模型;层次数据模型:树状模型。

在这里插入图片描述

QT中的三种数据模型

QT统计图模块中的各种统计图,最终目的肯定是为了以各种形式来展示数据。那么这些数据从何而来呢?显然QT模型视图代理框架中的模型就可以用于提供展示所需要的数据。QT统计图模块为了将QT模型视图代理框架集成进来,引入了一系列的model mapper类型,也就是模型映射类型。

QXYModelMapper用于在QT统计图的QXYSeries系列对象和QT模型视图代理的数据模型对象之间建立双向关联关系,也就是在QT统计图模块和QT模型视图代理框架之间搭建了一个桥梁,或者说就是一个适配层。QT统计图模块只与这个模型映射对象打交道,而无需和QT模型视图代理框架直接打交道;但是QT统计图模块借助这个适配层,确实能够实现间接调用QT模型视图代理框架的功能,或者说获取到了QT模型视图代理框架的一部分价值;换句话来讲,就是通过这个模型映射对象,实现了QT统计图模块和QT模型视图代理框架之间的解耦。

QXYModelMapper类型的类型定义如下所示:

class Q_CHARTS_EXPORT QXYModelMapper : public QObject
{
 Q_OBJECT
 
protected:
 explicit QXYModelMapper(QObject *parent = nullptr);
 
 //与数据模型对象建立关联
 QAbstractItemModel *model() const;
 void setModel(QAbstractItemModel *model);
 
 //与QT统计图的系列对象建立关联
 QXYSeries *series() const;
 void setSeries(QXYSeries *series);
 
 int first() const;
 void setFirst(int first);
 
 int count() const;
 void setCount(int count);
 
 Qt::Orientation orientation() const;
 void setOrientation(Qt::Orientation orientation);
 
 int xSection() const;
 void setXSection(int xSection);
 
 int ySection() const;
 void setYSection(int ySection);
 
protected:
//这个model mapper对象的d_ptr指针,指向私有数据对象。
//关于QT对象中的d_ptr的用法和理解,
//请参考<<补天云QT原理与源码分析视频课程>>
 QXYModelMapperPrivate *const d_ptr;
 Q_DECLARE_PRIVATE(QXYModelMapper)
};

QXYModelMapper类型的私有数据对象的数据类型:

class Q_CHARTS_PRIVATE_EXPORT QXYModelMapperPrivate : public QObject
{
 Q_OBJECT
 
public:
 QXYModelMapperPrivate(QXYModelMapper *q);
 
public Q_SLOTS:
 //处理数据模型变更有关的信号
 void modelUpdated(QModelIndex topLeft, QModelIndex bottomRight);
 void modelRowsAdded(QModelIndex parent, int start, int end);
 void modelRowsRemoved(QModelIndex parent, int start, int end);
 void modelColumnsAdded(QModelIndex parent, int start, int end);
 void modelColumnsRemoved(QModelIndex parent, int start, int end);
 void handleModelDestroyed();
 
 //处理统计图系列的变更有关的信号
 void handlePointAdded(int pointPos);
 void handlePointRemoved(int pointPos);
 void handlePointsRemoved(int pointPos, int count);
 void handlePointReplaced(int pointPos);
 void handleSeriesDestroyed();
 void initializeXYFromModel();
 
private:
 QModelIndex xModelIndex(int xPos);
 QModelIndex yModelIndex(int yPos);
 void insertData(int start, int end);
 void removeData(int start, int end);
 void blockModelSignals(bool block = true);
 void blockSeriesSignals(bool block = true);
 qreal valueFromModel(QModelIndex index);
 void setValueToModel(QModelIndex index, qreal value);
 
private:
 QXYSeries *m_series;
 QAbstractItemModel *m_model;
 int m_first;
 int m_count;
 Qt::Orientation m_orientation;
 int m_xSection;
 int m_ySection;
 bool m_seriesSignalsBlock;
 bool m_modelSignalsBlock;
 
private:
//这个q_ptr指针,指向model mapper对象。
//关于QT对象中的q_ptr的用法和理解,
//请参考<<补天云QT原理与源码分析视频课程>>
 QXYModelMapper *q_ptr;
 Q_DECLARE_PUBLIC(QXYModelMapper)
};


QT统计图模块中类似这样的模型映射器对象类型还有一些,比如:QPieModelMapper、QVBarModelMapper、QVBoxPlotModelMapper、QVCandlestickModelMapper等,在此不一一列举了,具体实现细节有所不同,但是基本的思想还是相同的。

补天云<<QT/QSS/QML/C++/STL项目实战原理源码界面美化视频课程>>中具体介绍了如何编写基于QT模型视图代理框架的应用程序,以及如何自定义框架中的各种组件类型。

在这里插入图片描述

补天云C/C++/QT系列视频课程中的模型视图代理框架

QT QML中的QT统计图

QT统计图模块提供了一些QML数据类型,因此QT QML应用程序中也可以使用QT统计图模块的功能。

在这里插入图片描述

QT 统计图的 QML应用程序

import QtQuick 2.0
import QtCharts 2.0

ChartView {
width: 400
height: 300
theme: ChartView.ChartThemeBrownSand
antialiasing: true

PieSeries {
id: pieSeries
PieSlice { label: “eaten”; value: 94.9 }
PieSlice { label: “not yet eaten”; value: 5.1 }
}
}

QT统计图模块提供的QML类型如下所示:

在这里插入图片描述

QT统计图模块的QML类型

Q在这里插入图片描述
T统计图模块的QML类型

站在C++层面来考虑QT统计图模块的QML支持,可以认为是在QT统计图模块的C++类型的基础之上,做了一些适配QML应用程序的一些包装。

lass Q_CHARTSQML_PRIVATE_EXPORT DeclarativeChart : public QQuickItem
{
 Q_OBJECT
 Q_PROPERTY(Theme theme READ theme WRITE setTheme)
 Q_PROPERTY(Animation animationOptions READ animationOptions WRITE setAnimationOptions)
 Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged REVISION(2, 1))
 Q_PROPERTY(QEasingCurve animationEasingCurve READ animationEasingCurve WRITE setAnimationEasingCurve NOTIFY animationEasingCurveChanged REVISION(2, 1))
 Q_PROPERTY(QString title READ title WRITE setTitle)
 Q_PROPERTY(QFont titleFont READ titleFont WRITE setTitleFont)
 Q_PROPERTY(QColor titleColor READ titleColor WRITE setTitleColor NOTIFY titleColorChanged)
 Q_PROPERTY(QLegend *legend READ legend CONSTANT)
 Q_PROPERTY(int count READ count)
 Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged)
 Q_PROPERTY(bool dropShadowEnabled READ dropShadowEnabled WRITE setDropShadowEnabled NOTIFY dropShadowEnabledChanged)
 Q_PROPERTY(qreal backgroundRoundness READ backgroundRoundness WRITE setBackgroundRoundness NOTIFY backgroundRoundnessChanged REVISION(1, 3))
 Q_PROPERTY(DeclarativeMargins *margins READ margins NOTIFY marginsChanged REVISION(1, 2))
 Q_PROPERTY(QRectF plotArea READ plotArea WRITE setPlotArea NOTIFY plotAreaChanged REVISION(1, 1))
 Q_PROPERTY(QColor plotAreaColor READ plotAreaColor WRITE setPlotAreaColor NOTIFY plotAreaColorChanged REVISION(1, 3))
 Q_PROPERTY(QQmlListProperty<QAbstractAxis> axes READ axes REVISION(1, 2))
 Q_PROPERTY(bool localizeNumbers READ localizeNumbers WRITE setLocalizeNumbers NOTIFY localizeNumbersChanged REVISION(2, 0))
 Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged REVISION(2, 0))
 Q_ENUMS(Animation)
 Q_ENUMS(Theme)
 Q_ENUMS(SeriesType)
 QML_NAMED_ELEMENT(ChartView)
 QML_ADDED_IN_VERSION(1, 0)
 QML_EXTRA_VERSION(2, 0)
 
public:
 DeclarativeChart(QQuickItem *parent = 0);
 ~DeclarativeChart();
 
//实现QML可视化界面元素的基本操作,就是重新实现这个函数。
//QT QML C++扩展开发具体细节,
//请参考<<补天云QT QML Quick C++高级扩展开发视频课程>>。
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) override;
 
public:
 Q_INVOKABLE QAbstractSeries *series(int index);
 Q_INVOKABLE QAbstractSeries *series(QString seriesName);
 Q_INVOKABLE QAbstractSeries *createSeries(int type, QString name = QString(),
 QAbstractAxis *axisX = 0, QAbstractAxis *axisY = 0);
 Q_INVOKABLE void removeSeries(QAbstractSeries *series);
 Q_INVOKABLE void removeAllSeries() { m_chart->removeAllSeries(); }
 Q_INVOKABLE void setAxisX(QAbstractAxis *axis, QAbstractSeries *series = 0);
 Q_INVOKABLE void setAxisY(QAbstractAxis *axis, QAbstractSeries *series = 0);
 Q_INVOKABLE QAbstractAxis *axisX(QAbstractSeries *series = 0);
 Q_INVOKABLE QAbstractAxis *axisY(QAbstractSeries *series = 0);
 Q_INVOKABLE void zoom(qreal factor);
//.......
 
private:
 //内部持有QT统计图模块的QChart对象指针
QChart *m_chart;
//内部持有QT图形视图框架的图形场景QGraphicsScene对象指针
 QGraphicsScene *m_scene;
 //内部持有QT统计图模块的系列数据管理器对象的指针
GLXYSeriesDataManager *m_glXYDataManager;
//.......
};

Qt Data Visualization模块

当然,QT框架中除了传统的QT统计图模块之外,还提供了专门的Qt Data Visualization模块用于更好的可视化展示数据。Qt Data Visualization同样提供了C++ API和QML API。本文专门介绍QT统计图模块,因此不具体展开介绍QT数据可视化模块了。

在这里插入图片描述

QT 数据可视化模块的效果

在这里插入图片描述

QT数据可视化模块的效果

如果您认为这篇文章对您有所帮助,请您一定立即点赞+喜欢+收藏,本文作者将能从您的点赞+喜欢+收藏中获取到创作新的好文章的动力。如果您认为作者写的文章还有一些参考价值,您也可以关注这篇文章的作者。

posted @ 2023-07-15 17:01  QT界面美化性能优化  阅读(216)  评论(0编辑  收藏  举报  来源