Qt 图表:从实时数据到多样化的视图

发布时间 2024-01-03 14:08:07作者: 非法关键字

引言

在现代软件开发中,实时数据的可视化处理是一个常见的需求。Qt Charts 提供了一系列工具,不仅可以实时更新图表,还能展示多种类型的数据。本文将通过一个示例来介绍如何在 Qt 应用程序中使用 Qt Charts 创建实时更新的图表,并探讨继承自 QAbstractAxisQAbstractSeries 的不同类型,以及如何使用它们来丰富您的图表应用程序。

初始设置与主窗口类

在设置主窗口类时,我们包含了构建图表所需的 Qt Charts 模块相关头文件,并使用了 Qt Charts 命名空间。主窗口类中包含了图表视图、数据序列、坐标轴和定时器的声明。构造函数中进行了图表的初始设置,包括创建图表视图、设置标题、配置图例和序列以及初始化坐标轴。

图表的动态更新

我们使用 QTimer 来周期性调用更新槽 slotTimeOut,该槽函数负责生成新的数据点并更新图表。使用 QSplineSeriesQLineSeries,我们可以展示平滑的曲线和直线,而 QDateTimeAxis 作为 X 轴,允许我们在 X 轴上显示时间。

多样化的坐标轴类型

Qt Charts 通过继承自 QAbstractAxis 的类来支持多种类型的坐标轴,为开发者提供了灵活的数据展示方式:

  • QValueAxis: 展示数值数据,可以定义刻度数、间隔和格式。
  • QCategoryAxis: 用于显示具有特定名称或类别的数据点。
  • QDateTimeAxis: 专门用于显示日期和时间数据,自动调整刻度以适应时间范围。
  • QLogValueAxis: 提供对数刻度,适用于显示广范围的数值数据。
  • QBarCategoryAxis: 通常与条形图一起使用,适合将类别与条形图的条形对应起来。
  • QPolarChartAxisAngularQPolarChartAxisRadial: 用于极坐标图表,分别表示角度和半径。

多样化的数据系列类型

类似地,Qt Charts 提供了多种数据系列类型,通过继承自 QAbstractSeries 的类,可以实现丰富的图表展示:

  • QLineSeriesQSplineSeries: 分别用于绘制线形图和平滑曲线。
  • QScatterSeries: 展示散点图,用于分析变量间的关系。
  • QBarSeries, QStackedBarSeriesQPercentBarSeries: 分别表示分组条形图、堆叠条形图和百分比条形图。
  • QHorizontalBarSeries, QHorizontalStackedBarSeriesQHorizontalPercentBarSeries: 水平展示的条形图系列。
  • QPieSeries: 用于绘制饼图。
  • QAreaSeries: 展示面积图。
  • QCandlestickSeriesQBoxPlotSeries: 常用于金融和统计领域。

示例实现

数据可视化是现代软件开发的重要组成部分,特别是当涉及到实时数据显示时。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));
}