Qt 图表:从实时数据到多样化的视图
引言
在现代软件开发中,实时数据的可视化处理是一个常见的需求。Qt Charts 提供了一系列工具,不仅可以实时更新图表,还能展示多种类型的数据。本文将通过一个示例来介绍如何在 Qt 应用程序中使用 Qt Charts 创建实时更新的图表,并探讨继承自 QAbstractAxis
和 QAbstractSeries
的不同类型,以及如何使用它们来丰富您的图表应用程序。
初始设置与主窗口类
在设置主窗口类时,我们包含了构建图表所需的 Qt Charts 模块相关头文件,并使用了 Qt Charts 命名空间。主窗口类中包含了图表视图、数据序列、坐标轴和定时器的声明。构造函数中进行了图表的初始设置,包括创建图表视图、设置标题、配置图例和序列以及初始化坐标轴。
图表的动态更新
我们使用 QTimer
来周期性调用更新槽 slotTimeOut
,该槽函数负责生成新的数据点并更新图表。使用 QSplineSeries
和 QLineSeries
,我们可以展示平滑的曲线和直线,而 QDateTimeAxis
作为 X 轴,允许我们在 X 轴上显示时间。
多样化的坐标轴类型
Qt Charts 通过继承自 QAbstractAxis
的类来支持多种类型的坐标轴,为开发者提供了灵活的数据展示方式:
- QValueAxis: 展示数值数据,可以定义刻度数、间隔和格式。
- QCategoryAxis: 用于显示具有特定名称或类别的数据点。
- QDateTimeAxis: 专门用于显示日期和时间数据,自动调整刻度以适应时间范围。
- QLogValueAxis: 提供对数刻度,适用于显示广范围的数值数据。
- QBarCategoryAxis: 通常与条形图一起使用,适合将类别与条形图的条形对应起来。
- QPolarChartAxisAngular 和 QPolarChartAxisRadial: 用于极坐标图表,分别表示角度和半径。
多样化的数据系列类型
类似地,Qt Charts 提供了多种数据系列类型,通过继承自 QAbstractSeries
的类,可以实现丰富的图表展示:
- QLineSeries 和 QSplineSeries: 分别用于绘制线形图和平滑曲线。
- QScatterSeries: 展示散点图,用于分析变量间的关系。
- QBarSeries, QStackedBarSeries 和 QPercentBarSeries: 分别表示分组条形图、堆叠条形图和百分比条形图。
- QHorizontalBarSeries, QHorizontalStackedBarSeries 和 QHorizontalPercentBarSeries: 水平展示的条形图系列。
- QPieSeries: 用于绘制饼图。
- QAreaSeries: 展示面积图。
- QCandlestickSeries 和 QBoxPlotSeries: 常用于金融和统计领域。
示例实现
数据可视化是现代软件开发的重要组成部分,特别是当涉及到实时数据显示时。Qt Charts 提供了一套强大的工具,允许开发者快速构建动态图表。本文将通过一个具体的示例来介绍如何使用 Qt Charts 创建一个实时更新的图表,该图表显示最近10秒内的数据并随时间自动滚动。
项目
# CMakeList.txt: sample2 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
file(GLOB SRC "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
file(GLOB HEADER "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
# 将源代码添加到此项目的可执行文件。
add_executable (sample2 ${SRC} ${HEADER})
if (CMAKE_VERSION VERSION_GREATER 3.12)
set_property(TARGET sample2 PROPERTY CXX_STANDARD 20)
endif()
# TODO: 如有需要,请添加测试并安装目标。
find_package(Qt5 COMPONENTS Core Widgets Charts REQUIRED)
target_link_libraries(sample2
PRIVATE
Qt5::Core
Qt5::Widgets
Qt5::Charts
)
包含必要的头文件
首先,我们需要包含构建图表所需的 Qt Charts 模块相关头文件。这些头文件提供了创建和操作图表所需的类和函数。
#include <QMainWindow>
#include <QChart>
#include <QChartView>
#include <QSplineSeries>
#include <QLineSeries>
#include <QDateTimeAxis>
#include <QValueAxis>
#include <QTimer>
#include "ui_MainWindow.h"
使用命名空间
为了简化代码,我们使用 Qt Charts 模块的命名空间,这样就可以直接引用模块中的类,而无需每次都加上 QtCharts::
前缀。
using namespace QtCharts;
类定义
MainWindow
类继承自 QMainWindow
,是我们应用程序的主窗口。它包含了图表、数据序列和定时器的声明。
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void slotTimeOut();
private:
Ui::MainWindow ui;
QChartView* _chartView;
QSplineSeries* _lineSeries0;
QLineSeries* _lineSeries1;
QDateTimeAxis* _axisX;
QValueAxis* _axisY0;
QValueAxis* _axisY1;
QTimer* _fakeDataTimer;
};
构造函数
在构造函数中,我们进行了图表的初始设置,包括创建图表视图、设置标题、配置图例和序列以及初始化坐标轴。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
ui.setupUi(this);
// 图表初始化代码
// 创建
_chartView = new QChartView();
_chart = new QChart();
//_lineSeries0 = new QLineSeries();
_lineSeries0 = new QSplineSeries();
_lineSeries1 = new QLineSeries();
// 设置字体
_chart->setTitle(QString::fromLocal8Bit("统计图"));
QFont title;
title.setFamily("Arial");
title.setPointSize(25);
title.setBold(true);
_chart->setTitleFont(title);
// 设置主题
//_chartView->chart()->setTheme(QChart::ChartThemeLight);
//_chartView->chart()->setTheme(QChart::ChartThemeDark);
_chartView->chart()->setTheme(QChart::ChartThemeBlueIcy);
// 设置动画效果
//_chartView->chart()->setAnimationOptions(QChart::AllAnimations);
_chartView->chart()->setAnimationOptions(QChart::SeriesAnimations);
// 设置图例
_chart->legend()->setVisible(true);
//_chart->legend()->setBackgroundVisible(false);
_chart->legend()->setAlignment(Qt::AlignRight);
// 设置序列
_lineSeries0->setName(QString::fromLocal8Bit("一分钟负载"));
_lineSeries1->setName(QString::fromLocal8Bit("五分钟负载"));
QPen pen;
//pen.setStyle(Qt::DotLine);
pen.setStyle(Qt::SolidLine);
pen.setWidth(2);
pen.setColor(Qt::red);
_lineSeries0->setPen(pen);
pen.setStyle(Qt::SolidLine);
pen.setColor(Qt::blue);
pen.setWidth(2);
_lineSeries1->setPen(pen);
// 添加序列
_chart->addSeries(_lineSeries0);
_chart->addSeries(_lineSeries1);
_lineSeries0->setVisible(true);
_lineSeries0->setPointsVisible(true);
//_lineSeries0->setPointLabelsFormat("(@xPoint,@yPoint)");
_lineSeries0->setPointLabelsVisible(true);
_lineSeries0->setPointLabelsFormat("[@yPoint]");
_lineSeries0->setPointLabelsColor(_lineSeries0->color());
_lineSeries1->setVisible(true);
_lineSeries1->setPointsVisible(true);
_lineSeries1->setPointLabelsVisible(true);
_lineSeries1->setPointLabelsFormat("@yPoint");
_lineSeries1->setPointLabelsColor(_lineSeries1->color());
// 设置坐标轴
_axisX = new QDateTimeAxis();
_axisX->setTickCount(10); // 设置主刻度的数量
_axisX->setFormat("ss"); // 设置显示格式为秒
_axisX->setTitleText(QString::fromLocal8Bit("X轴"));
_axisY0 = new QValueAxis();
_axisY0->setLineVisible(true);
_axisY0->setTitleVisible(true);
_axisY0->setLabelsVisible(true);
_axisY0->setGridLineVisible(true);
//_axisY0->setMinorGridLineVisible(true);
_axisY0->setRange(0, 20);
_axisY0->setTitleText(QString::fromLocal8Bit("Y0轴"));
_axisY0->setTickCount(5);
_axisY0->setLabelFormat("%.2f");
//_axisY0->setMinorTickCount(5);
_axisY1 = new QValueAxis();
_axisY1->setLineVisible(true);
_axisY1->setTitleVisible(true);
_axisY1->setLabelsVisible(true);
_axisY1->setGridLineVisible(true);
//_axisY1->setMinorGridLineVisible(true);
_axisY1->setRange(-50, 50);
_axisY1->setTitleText(QString::fromLocal8Bit("Y1轴"));
_axisY1->setTickCount(20);
_axisY1->setLabelFormat("%.2f");
//_axisY1->setMinorTickCount(5);
_chart->addAxis(_axisX, Qt::AlignBottom);
_chart->addAxis(_axisY0, Qt::AlignLeft);
_chart->addAxis(_axisY1, Qt::AlignRight);
_lineSeries0->attachAxis(_axisX);
_lineSeries0->attachAxis(_axisY0);
_lineSeries1->attachAxis(_axisX);
_lineSeries1->attachAxis(_axisY1);
//_chartView->setParent(this);
this->setCentralWidget(_chartView);
_chartView->setChart(_chart);
_chartView->setRenderHint(QPainter::Antialiasing);
// 填充数据
_fakeDataTimer = new QTimer(this);
connect(_fakeDataTimer, &QTimer::timeout, this, &MainWindow::slotTimeOut);
_fakeDataTimer->start(200);
}
定时器和更新槽
我们使用 QTimer
来周期性地调用更新槽 slotTimeOut
。这个槽函数负责生成新的数据点并更新图表。
void MainWindow::slotTimeOut()
{
// ... 添加新数据点和移除旧数据点的代码 ...
// ... 更新坐标轴范围的代码 ...
}
数据系列和坐标轴
我们创建了两个数据系列:一个 QSplineSeries
用于显示平滑曲线,一个 QLineSeries
用于显示直线。两个系列都绑定到了同一个 X 轴(时间轴)和各自的 Y 轴。
_lineSeries0 = new QSplineSeries();
_lineSeries1 = new QLineSeries();
我们使用 QDateTimeAxis
作为 X 轴,因为我们希望在 X 轴上显示时间。Y 轴是常规的数值轴 QValueAxis
。
_axisX = new QDateTimeAxis();
_axisX->setTickCount(10);
_axisX->setFormat("ss");
定时器
定时器设置为每200毫秒触发一次,这意味着图表每200毫秒更新一次。
_fakeDataTimer = new QTimer(this);
_fakeDataTimer->start(200);
实时数据更新
在 slotTimeOut
函数中,我们首先获取当前的时间戳,然后生成新的数据点并将其添加到序列中。为了保持图表中只显示最近10秒的数据,我们移除了序列中超出时间窗口的旧数据点。然后,我们更新 X 轴的范围,以滚动显示最新的数据。
void MainWindow::slotTimeOut()
{
// 当前时间
qreal currentTime = QDateTime::currentDateTime().toMSecsSinceEpoch();
qreal timeWindow = 10000; // 显示的时间范围,例如 10 秒
// 生成新的数据点
qreal rnd = qrand() % 15 + 5;
qreal y1 = rnd;
qreal y2 = qrand() % 95 - 45;
// 添加到序列
_lineSeries0->append(currentTime, y1);
_lineSeries1->append(currentTime, y2);
// 移除超出时间窗口的旧数据点
while (_lineSeries0->count() > 0 && _lineSeries0->at(0).x() < currentTime - timeWindow) {
_lineSeries0->remove(0);
}
while (_lineSeries1->count() > 0 && _lineSeries1->at(0).x() < currentTime - timeWindow) {
_lineSeries1->remove(0);
}
_axisX->setMin(QDateTime::fromMSecsSinceEpoch(currentTime - timeWindow));
_axisX->setMax(QDateTime::fromMSecsSinceEpoch(currentTime));
}