一、介绍
Qt的QChart是一个用于绘制图表和可视化数据的类。提供了一个灵活的、可扩展的、跨平台的图表绘制解决方案,可以用于各种应用程序,如数据分析、科学计算、金融交易等。
QChart支持多种类型的图表,包括折线图、散点图、柱状图、饼图等。它还支持多个数据系列(datasets)在同一个图表中显示,并且可以自定义各种图表属性和样式,如坐标轴标签、标题、图例等。
QChart还支持多种数据源(data sources),可以来自Qt的数据模型(data models)、CSV文件、JSON文件等。数据源可以是任何支持迭代器(iterator)的类型,因此可以轻松地与其他Qt组件集成。
使用QChart可以轻松地创建交互式图表,如鼠标悬停提示(hover tooltip)、数据选择(data selection)等。此外,QChart还支持多种主题(themes)和自定义CSS样式,使得图表外观可以灵活地定制。
二、实现代码(1)QMainWindow
以下是使用Qt(C++)的QChart模块显示3个设备的动态温度曲线的代码实现:
【1】实现温度动态更新
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtCharts/QChart>
#include <QtCharts/QLineSeries>
#include <QTimer>
QT_CHARTS_USE_NAMESPACE
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void updateChartData(); // 更新数据槽函数
private:
Ui::MainWindow *ui;
QTimer *m_timer; // 定时器
QChart *m_chart; // 图表指针
QLineSeries *m_series1; // 设备1温度曲线
QLineSeries *m_series2; // 设备2温度曲线
QLineSeries *m_series3; // 设备3温度曲线
int m_timeCount; // 时间计数
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
m_timer(new QTimer(this)),
m_chart(new QChart()),
m_series1(new QLineSeries()),
m_series2(new QLineSeries()),
m_series3(new QLineSeries()),
m_timeCount(0)
{
ui->setupUi(this);
// 设置图表标题
m_chart->setTitle("Temperature Data");
// 创建温度曲线图1并设置属性
m_series1->setName(tr("Device 1"));
m_series1->setColor(Qt::red);
m_series1->setPen(QPen(Qt::red, 2));
m_chart->addSeries(m_series1);
// 创建温度曲线图2并设置属性
m_series2->setName(tr("Device 2"));
m_series2->setColor(Qt::green);
m_series2->setPen(QPen(Qt::green, 2));
m_chart->addSeries(m_series2);
// 创建温度曲线图3并设置属性
m_series3->setName(tr("Device 3"));
m_series3->setColor(Qt::blue);
m_series3->setPen(QPen(Qt::blue, 2));
m_chart->addSeries(m_series3);
// 设置横轴属性
QValueAxis *axisX = new QValueAxis;
axisX->setRange(0, 30);
axisX->setTitleText("Time (s)");
m_chart->addAxis(axisX, Qt::AlignBottom);
m_series1->attachAxis(axisX);
m_series2->attachAxis(axisX);
m_series3->attachAxis(axisX);
// 设置纵轴属性
QValueAxis *axisY = new QValueAxis;
axisY->setRange(0, 60);
axisY->setTitleText("Temperature (℃)");
m_chart->addAxis(axisY, Qt::AlignLeft);
m_series1->attachAxis(axisY);
m_series2->attachAxis(axisY);
m_series3->attachAxis(axisY);
// 定时更新数据
connect(m_timer, &QTimer::timeout, this, &MainWindow::updateChartData);
m_timer->start(1000); // 每隔1秒钟更新一次数据
// 将图表添加到ChartView中
ui->chartView->setChart(m_chart);
ui->chartView->setRenderHint(QPainter::Antialiasing);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateChartData()
{
// 更新时间计数
m_timeCount++;
// 在温度曲线上增加一个点,模拟温度数据变化
QPointF p1(m_timeCount, qrand() % 10 + 20);
QPointF p2(m_timeCount, qrand() % 10 + 30);
QPointF p3(m_timeCount, qrand() % 10 + 40);
m_series1->append(p1);
m_series2->append(p2);
m_series3->append(p3);
// 清除多余的点,只保留最新的30个数据点
if (m_series1->count() > 30) {
m_series1->removePoints(0, 1);
}
if (m_series2->count() > 30) {
m_series2->removePoints(0, 1);
}
if (m_series3->count() > 30) {
m_series3->removePoints(0, 1);
}
}
在此代码中,定义了一个QTimer定时器对象,用于每隔一段时间更新温度曲线数据。在定时器的timeout信号触发时,调用updateChartData()槽函数来更新温度曲线数据,同时控制数据量不超过30个点。
在updateChartData()函数中,使用了qrand()函数来生成随机的温度数据,模拟动态变化的效果。可以根据实际情况修改此函数的实现方式。
最后,将图表添加到QChartView控件中,并启用抗锯齿功能以提高显示质量。
【2】设置曲线可见范围
为了保证曲线显示一直在可见范围内,可以添加如下代码:
// 使图表自适应大小,确保曲线始终可见
m_chart->createDefaultAxes();
m_chart->axisX()->setRange(0, 30);
m_chart->axisY()->setRange(0, 60);
这段代码的作用是让图表自适应大小,并设置横轴范围为0到30,纵轴范围为0到60。这样当新数据点增加到图表之外时,图表会自动调整大小和范围,以确保曲线始终可见。
完整的mainwindow.cpp
代码如下所示:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
m_timer(new QTimer(this)),
m_chart(new QChart()),
m_series1(new QLineSeries()),
m_series2(new QLineSeries()),
m_series3(new QLineSeries()),
m_timeCount(0)
{
ui->setupUi(this);
// 设置图表标题
m_chart->setTitle("Temperature Data");
// 创建温度曲线图1并设置属性
m_series1->setName(tr("Device 1"));
m_series1->setColor(Qt::red);
m_series1->setPen(QPen(Qt::red, 2));
m_chart->addSeries(m_series1);
// 创建温度曲线图2并设置属性
m_series2->setName(tr("Device 2"));
m_series2->setColor(Qt::green);
m_series2->setPen(QPen(Qt::green, 2));
m_chart->addSeries(m_series2);
// 创建温度曲线图3并设置属性
m_series3->setName(tr("Device 3"));
m_series3->setColor(Qt::blue);
m_series3->setPen(QPen(Qt::blue, 2));
m_chart->addSeries(m_series3);
// 设置横轴属性
QValueAxis *axisX = new QValueAxis;
axisX->setRange(0, 30);
axisX->setTitleText("Time (s)");
m_chart->addAxis(axisX, Qt::AlignBottom);
m_series1->attachAxis(axisX);
m_series2->attachAxis(axisX);
m_series3->attachAxis(axisX);
// 设置纵轴属性
QValueAxis *axisY = new QValueAxis;
axisY->setRange(0, 60);
axisY->setTitleText("Temperature (℃)");
m_chart->addAxis(axisY, Qt::AlignLeft);
m_series1->attachAxis(axisY);
m_series2->attachAxis(axisY);
m_series3->attachAxis(axisY);
// 使图表自适应大小,确保曲线始终可见
m_chart->createDefaultAxes();
m_chart->axisX()->setRange(0, 30);
m_chart->axisY()->setRange(0, 60);
// 定时更新数据
connect(m_timer, &QTimer::timeout, this, &MainWindow::updateChartData);
m_timer->start(1000); // 每隔1秒钟更新一次数据
// 将图表添加到ChartView中
ui->chartView->setChart(m_chart);
ui->chartView->setRenderHint(QPainter::Antialiasing);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateChartData()
{
// 更新时间计数
m_timeCount++;
// 在温度曲线上增加一个点,模拟温度数据变化
QPointF p1(m_timeCount, qrand() % 10 + 20);
QPointF p2(m_timeCount, qrand() % 10 + 30);
QPointF p3(m_timeCount, qrand() % 10 + 40);
m_series1->append(p1);
m_series2->append(p2);
m_series3->append(p3);
// 清除多余的点,只保留最新的30个数据点
if (m_series1->count() > 30) {
m_series1->removePoints(0, 1);
}
if (m_series2->count() > 30) {
m_series2->removePoints(0, 1);
}
if (m_series3->count() > 30) {
m_series3->removePoints(0, 1);
}
}
【3】实现鼠标交互拖动
要实现折线图的横坐标可以拖动,可以设置QChartView的交互模式为拖拽,在构造函数中添加如下代码:
// 设置 ChartView 交互模式为拖拽
ui->chartView->setRubberBand(QChartView::HorizontalRubberBand);
ui->chartView->setRenderHint(QPainter::Antialiasing);
ui->chartView->setDragMode(QGraphicsView::ScrollHandDrag);
这样用户就可以通过鼠标左键在横轴上拖拽来改变曲线图的可见范围。同时,还需要在mainwindow.cpp
中添加横坐标的范围更新函数updateAxisRange()
,用于在拖拽时更新横坐标的范围。
完整的mainwindow.cpp
代码如下所示:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
m_timer(new QTimer(this)),
m_chart(new QChart()),
m_series1(new QLineSeries()),
m_series2(new QLineSeries()),
m_series3(new QLineSeries()),
m_timeCount(0)
{
ui->setupUi(this);
// 设置图表标题
m_chart->setTitle("Temperature Data");
// 创建温度曲线图1并设置属性
m_series1->setName(tr("Device 1"));
m_series1->setColor(Qt::red);
m_series1->setPen(QPen(Qt::red, 2));
m_chart->addSeries(m_series1);
// 创建温度曲线图2并设置属性
m_series2->setName(tr("Device 2"));
m_series2->setColor(Qt::green);
m_series2->setPen(QPen(Qt::green, 2));
m_chart->addSeries(m_series2);
// 创建温度曲线图3并设置属性
m_series3->setName(tr("Device 3"));
m_series3->setColor(Qt::blue);
m_series3->setPen(QPen(Qt::blue, 2));
m_chart->addSeries(m_series3);
// 设置横轴属性
QValueAxis *axisX = new QValueAxis;
axisX->setRange(0, 30);
axisX->setTitleText("Time (s)");
m_chart->addAxis(axisX, Qt::AlignBottom);
m_series1->attachAxis(axisX);
m_series2->attachAxis(axisX);
m_series3->attachAxis(axisX);
// 设置纵轴属性
QValueAxis *axisY = new QValueAxis;
axisY->setRange(0, 60);
axisY->setTitleText("Temperature (℃)");
m_chart->addAxis(axisY, Qt::AlignLeft);
m_series1->attachAxis(axisY);
m_series2->attachAxis(axisY);
m_series3->attachAxis(axisY);
// 使图表自适应大小,确保曲线始终可见
m_chart->createDefaultAxes();
m_chart->axisX()->setRange(0, 30);
m_chart->axisY()->setRange(0, 60);
// 设置 ChartView 交互模式为拖拽
ui->chartView->setRubberBand(QChartView::HorizontalRubberBand);
ui->chartView->setRenderHint(QPainter::Antialiasing);
ui->chartView->setDragMode(QGraphicsView::ScrollHandDrag);
// 定时更新数据
connect(m_timer, &QTimer::timeout, this, &MainWindow::updateChartData);
m_timer->start(1000); // 每隔1秒钟更新一次数据
// 将图表添加到ChartView中
ui->chartView->setChart(m_chart);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateChartData()
{
// 更新时间计数
m_timeCount++;
// 在温度曲线上增加一个点,模拟温度数据变化
QPointF p1(m_timeCount, qrand() % 10 + 20);
QPointF p2(m_timeCount, qrand() % 10 + 30);
QPointF p3(m_timeCount, qrand() % 10 + 40);
m_series1->append(p1);
m_series2->append(p2);
m_series3->append(p3);
// 清除多余的点,只保留最新的30个数据点
if (m_series1->count() > 30) {
m_series1->removePoints(0, 1);
}
if (m_series2->count() > 30) {
m_series2->removePoints(0, 1);
}
if (m_series3->count() > 30) {
m_series3->removePoints(0, 1);
}
// 更新横轴范围
updateAxisRange();
}
void MainWindow::updateAxisRange()
{
// 获取横轴范围
qreal minX = std::numeric_limits<qreal>::max();
qreal maxX = std::numeric_limits<qreal>::min();
foreach (QAbstractSeries *series, m_chart->series()) {
QXYSeries *xySeries = static_cast<QXYSeries*>(series);
QPointF p1 = xySeries->at(0);
QPointF p2 = xySeries->at(xySeries->count() - 1);
if (p1.x() < minX) {
minX = p1.x();
}
if (p2.x() > maxX) {
maxX = p2.x();
}
}
// 更新横轴范围
m_chart->axisX()->setRange(minX, maxX);
}
为了更新横坐标的范围,需要在MainWindow
中添加了一个新函数updateAxisRange()
。该函数会在数据更新时被调用来计算最新的横轴范围,以更新折线图的显示。
三、实现代码(2)QWidget
当前这份完整代码实现了一个动态折线图的绘制,是一个典型的Qt Charts应用程序。通过使用QLineSeries类、QValueAxis类和QChart类来创建并显示温度随时间变化的折线图。
第一步:先创建了主窗口,其中包含一个QChartView控件,用于显示温度曲线图。在构造函数中,设置了一些基本属性,比如标题、横纵坐标的范围和名称等,并为每个设备创建了一个QLineSeries对象,用于存储温度数据。
第二步:将这些QLineSeries对象添加到QChart对象中。接着,将QValueAxis对象添加到QChart中,设置其范围和名称,并将QLineSeries对象与其关联。最后,将QChart对象添加到QChartView中,以便在界面上显示折线图。
第三步:在updateChartData()函数中,定时器每隔1秒钟触发一次,用于更新温度数据,并通过调用QLineSeries类的append()函数向QLineSeries对象中添加新的温度数据点。同时,使用removePoints()函数删除旧的数据点,以保持折线图中显示的数据点不超过30个。在添加或删除数据时,使用updateAxisRange()函数更新横坐标的范围,以便将折线图自适应地缩放到当前数据范围内。
第四步:重载了updateAxisRange()函数,根据QLineSeries对象的数据点计算出横坐标的最小值和最大值,并通过调用QChart类的axisX()->setRange()函数更新QValueAxis对象的范围。
【1】widget.cpp代码
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle("温度数据可视化采集系统");
m_timer=new QTimer(this);
m_chart=new QChart();
m_series1=new QLineSeries();
m_series2=new QLineSeries();
m_series3=new QLineSeries();
m_timeCount=0;
// 创建温度曲线图1并设置属性
m_series1->setName(tr("设备1"));
// m_series1->setColor(Qt::red);
// m_series1->setPen(QPen(Qt::red, 2));
m_chart->addSeries(m_series1);
// 创建温度曲线图2并设置属性
m_series2->setName(tr("设备2"));
// m_series2->setColor(Qt::green);
// m_series2->setPen(QPen(Qt::green, 2));
m_chart->addSeries(m_series2);
// 创建温度曲线图3并设置属性
m_series3->setName(tr("设备3"));
// m_series3->setColor(Qt::blue);
// m_series3->setPen(QPen(Qt::blue, 2));
m_chart->addSeries(m_series3);
// 设置横轴属性
QValueAxis *axisX = new QValueAxis;
axisX->setRange(0, 30);
axisX->setTitleText("时间 (s)");
m_chart->addAxis(axisX, Qt::AlignBottom);
m_series1->attachAxis(axisX);
m_series2->attachAxis(axisX);
m_series3->attachAxis(axisX);
// 设置纵轴属性
QValueAxis *axisY = new QValueAxis;
axisY->setRange(0, 60);
axisY->setTitleText("温度 (℃)");
m_chart->addAxis(axisY, Qt::AlignLeft);
m_series1->attachAxis(axisY);
m_series2->attachAxis(axisY);
m_series3->attachAxis(axisY);
// 使图表自适应大小,确保曲线始终可见
m_chart->createDefaultAxes();
m_chart->axisX()->setRange(0, 30);
m_chart->axisY()->setRange(0, 60);
// 定时更新数据
connect(m_timer, &QTimer::timeout, this, &Widget::updateChartData);
// 将图表添加到ChartView中
QChartView* chartView = new QChartView(m_chart);
// 设置 ChartView 交互模式为拖拽
chartView->setRubberBand(QChartView::HorizontalRubberBand);
chartView->setRenderHint(QPainter::Antialiasing);
chartView->setDragMode(QGraphicsView::ScrollHandDrag);
chartView->setRenderHint(QPainter::Antialiasing);
//将视图添加到布局
ui->view_verticalLayout->addWidget(chartView);
}
Widget::~Widget()
{
delete ui;
}
void Widget::updateChartData()
{
// 更新时间计数
m_timeCount++;
// 在温度曲线上增加一个点,模拟温度数据变化
QPointF p1(m_timeCount, qrand() % 10 + 20);
QPointF p2(m_timeCount, qrand() % 10 + 30);
QPointF p3(m_timeCount, qrand() % 10 + 40);
m_series1->append(p1);
m_series2->append(p2);
m_series3->append(p3);
// 清除多余的点,只保留最新的30个数据点
if (m_series1->count() > 30) {
m_series1->removePoints(0, 1);
}
if (m_series2->count() > 30) {
m_series2->removePoints(0, 1);
}
if (m_series3->count() > 30) {
m_series3->removePoints(0, 1);
}
// 更新横轴范围
updateAxisRange();
}
void Widget::updateAxisRange()
{
// 获取横轴范围
qreal minX = std::numeric_limits<qreal>::max();
qreal maxX = std::numeric_limits<qreal>::min();
foreach (QAbstractSeries *series, m_chart->series()) {
QXYSeries *xySeries = static_cast<QXYSeries*>(series);
QPointF p1 = xySeries->at(0);
QPointF p2 = xySeries->at(xySeries->count() - 1);
if (p1.x() < minX) {
minX = p1.x();
}
if (p2.x() > maxX) {
maxX = p2.x();
}
}
// 更新横轴范围
m_chart->axisX()->setRange(minX, maxX);
}
//开始采集
void Widget::on_pushButton_start_clicked()
{
m_timer->start(1000); // 每隔1秒钟更新一次数据
}
//停止采集
void Widget::on_pushButton_stop_clicked()
{
m_timer->stop(); //停止定时器
}
【2】widget.h代码
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
// 包含line chart需要的头文件
#include <QtCharts/QChart>
#include <QtCharts/QLineSeries>
#include <QtCharts/QChartView>
#include <QtCore/QRandomGenerator>
#include <QValueAxis>
#include <QScatterSeries>
#include <QTimer>
// 引用命名空间
QT_CHARTS_USE_NAMESPACE
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void updateChartData(); // 更新数据槽函数
void updateAxisRange();
void on_pushButton_start_clicked();
void on_pushButton_stop_clicked();
private:
Ui::Widget *ui;
QTimer *m_timer; // 定时器
QChart *m_chart; // 图表指针
QLineSeries *m_series1; // 设备1温度曲线
QLineSeries *m_series2; // 设备2温度曲线
QLineSeries *m_series3; // 设备3温度曲线
int m_timeCount; // 时间计数
};
#endif // WIDGET_H
【3】UI文件代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>444</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="widget" native="true">
<property name="minimumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="view_verticalLayout"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>40</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="pushButton_start">
<property name="text">
<string>开始采集</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_stop">
<property name="text">
<string>停止采集</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
【4】pro工程文件
QT += core gui
QT += charts
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
# Default rules for deployment.
qnx: target.path = /tmp/{
mathJaxContainer[0]}{
TARGET}/bin
!isEmpty(target.path): INSTALLS += target