当前位置: 首页 > ops >正文

使用Qt Charts实现高效多系列数据可视化

概述

这是一个基于 Qt Charts 的多系列数据可视化组件,支持同时显示多条数据曲线,每一条曲线可以有不同的颜色、样式和标签。组件采用暗色主题设计,具有良好的视觉效果和数据展示能力。

功能特性

  • 支持多数据系列同时显示
  • 自动分配不同颜色区分数据系列
  • 智能图例显示与管理
  • 暗色主题设计,减少视觉疲劳
  • 高性能数据更新与渲染
  • 自适应坐标轴范围
  • 完善的错误处理机制

类结构

ChartView 类

主要成员变量
QChart *m_chart;                    // 图表对象
QChartView *m_chartView;           // 图表视图
QVector<QLineSeries*> m_seriesList; // 数据系列容器
QValueAxis *m_axisX;               // X轴
QValueAxis *m_axisY;               // Y轴
核心方法
1. 构造函数与析构函数
explicit ChartView(QWidget *parent = nullptr);
~ChartView();
2. 数据更新接口
// 更新单个数据系列
void updateChart(const QStringList &listData, const QString &name, int flag);// 更新多个数据系列
void updateCharts(const QVector<QStringList> &dataLists, const QStringList &names, int flag);
3. 辅助方法
void initData();                    // 初始化数据
void initUI();                      // 初始化界面
void setupAxes();                   // 设置坐标轴
void applyDarkTheme();              // 应用暗色主题
void clearAllSeries();              // 清除所有数据系列
QColor getSeriesColor(int index) const; // 获取系列颜色

使用示例

基本用法

#include "chartview.h"
#include <QApplication>
#include <QMainWindow>int main(int argc, char *argv[])
{QApplication a(argc, argv);QMainWindow window;// 创建图表视图ChartView *chartView = new ChartView(&window);window.setCentralWidget(chartView);window.resize(800, 600);window.show();// 准备测试数据QVector<QStringList> dataLists;QStringList names;// 温度数据系列QStringList temperatureData;for (int i = 0; i < 50; ++i) {temperatureData << QString::number(20 + 10 * sin(i * 0.1));}dataLists.append(temperatureData);names.append("温度 (°C)");// 湿度数据系列QStringList humidityData;for (int i = 0; i < 50; ++i) {humidityData << QString::number(50 + 30 * cos(i * 0.15));}dataLists.append(humidityData);names.append("湿度 (%)");// 压力数据系列QStringList pressureData;for (int i = 0; i < 50; ++i) {pressureData << QString::number(1000 + 50 * sin(i * 0.2));}dataLists.append(pressureData);names.append("压力 (hPa)");// 更新图表chartView->updateCharts(dataLists, names, 0);return a.exec();
}

动态更新数据

// 模拟实时数据更新
void updateRealTimeData(ChartView *chartView)
{QTimer *timer = new QTimer(chartView);QObject::connect(timer, &QTimer::timeout, [chartView]() {static int counter = 0;QVector<QStringList> realTimeData;QStringList realTimeNames;// 生成随机数据for (int series = 0; series < 3; ++series) {QStringList seriesData;for (int i = 0; i < 20; ++i) {double value = 0;if (series == 0) {value = 50 + 30 * sin((counter + i) * 0.1);} else if (series == 1) {value = 70 + 20 * cos((counter + i) * 0.15);} else {value = 30 + 40 * sin((counter + i) * 0.2 + 0.5);}seriesData << QString::number(value);}realTimeData.append(seriesData);realTimeNames.append(QString("传感器 %1").arg(series + 1));}chartView->updateCharts(realTimeData, realTimeNames, 0);counter++;});timer->start(1000); // 每秒更新一次
}

自定义样式

// 自定义系列颜色和样式
void customizeChart(ChartView *chartView)
{// 可以通过修改 getSeriesColor 方法来自定义颜色// 或者继承 ChartView 类并重写相关方法QVector<QStringList> dataLists;QStringList names;// 添加示例数据QStringList data1, data2, data3;for (int i = 0; i < 30; ++i) {data1 << QString::number(10 + i * 0.5);data2 << QString::number(20 + i * 0.3);data3 << QString::number(30 + i * 0.7);}dataLists << data1 << data2 << data3;names << "线性增长" << "缓慢增长" << "快速增长";chartView->updateCharts(dataLists, names, 0);
}

高级功能

1. 数据验证与错误处理

组件内置数据验证机制,能够处理:

  • 空数据列表
  • 数据转换错误(非数字字符串)
  • 无效的数据点

2. 智能坐标轴调整

自动计算所有数据系列的最小值和最大值,并智能调整坐标轴范围,确保所有数据都能正确显示。

3. 性能优化

  • 使用 replace() 而不是 append() 更新数据点
  • 预分配内存空间
  • 批量处理数据更新

4. 主题定制

通过修改 applyDarkTheme() 方法可以轻松切换主题风格:

void ChartView::applyLightTheme()
{m_chart->setBackgroundBrush(Qt::white);m_chart->setTitleBrush(Qt::black);m_axisX->setTitleBrush(Qt::black);m_axisY->setTitleBrush(Qt::black);m_axisX->setLabelsColor(Qt::black);m_axisY->setLabelsColor(Qt::black);m_chartView->setBackgroundBrush(Qt::white);
}

集成指南

1. 项目配置

.pro 文件中添加:

QT += charts

2. 包含头文件

#include "chartview.h"

3. 基本使用流程

  1. 创建 ChartView 实例
  2. 准备数据(QVector<QStringList>
  3. 准备系列名称(QStringList
  4. 调用 updateCharts() 方法更新显示

注意事项

  1. 内存管理:组件会自动管理数据系列的内存,无需手动释放
  2. 线程安全:不建议在多线程中直接调用更新方法,如需跨线程更新,应使用信号槽机制
  3. 性能考虑:对于大量数据点(>10000),建议进行数据采样或使用更高效的数据结构
  4. 错误处理:始终检查输入数据的有效性,避免传入空列表或无效数据

扩展建议

  1. 添加交互功能:可以实现数据点提示、缩放、平移等交互功能
  2. 支持多种图表类型:可以扩展支持柱状图、散点图等其他图表类型
  3. 数据导出:添加数据导出为图片或CS文件的功能
  4. 主题切换:实现亮色/暗色主题切换功能

这个多系列数据可视化组件提供了强大而灵活的数据展示能力,适合各种监控、分析和数据可视化应用场景。

完整代码

chartview.h

#pragma once#include <QWidget>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
#include <QVector>QT_CHARTS_USE_NAMESPACEclass ChartView : public QWidget
{Q_OBJECTpublic:explicit ChartView(QWidget *parent = nullptr);~ChartView();// 更新单个数据系列void updateChart(const QStringList &listData, const QString &name, int flag);// 更新多个数据系列void updateCharts(const QVector<QStringList> &dataLists, const QStringList &names, int flag);private:void initData();void initUI();void setupAxes();void applyDarkTheme();void clearAllSeries();QColor getSeriesColor(int index) const;private slots:void slotsUpdateChart(const QStringList &listData, const QString &name, int flag);void slotsUpdateCharts(const QVector<QStringList> &dataLists, const QStringList &names, int flag);private:QChart *m_chart;QChartView *m_chartView;QVector<QLineSeries*> m_seriesList;  // 改为存储多个系列QValueAxis *m_axisX;QValueAxis *m_axisY;
};

chartview.cpp

#include "chartview.h"
#include <QHBoxLayout>
#include <QDebug>
#include <QDateTime>
#include <QPen>#pragma execution_character_set("utf-8")ChartView::ChartView(QWidget* parent): QWidget(parent)
{initData();initUI();
}ChartView::~ChartView()
{clearAllSeries();if (m_chart) {delete m_chart;m_chart = nullptr;}
}void ChartView::updateChart(const QStringList &listData, const QString &name, int flag)
{slotsUpdateChart(listData, name, flag);
}void ChartView::updateCharts(const QVector<QStringList> &dataLists, const QStringList &names, int flag)
{slotsUpdateCharts(dataLists, names, flag);
}void ChartView::initData()
{m_chartView = new QChartView(this);m_chart = new QChart();m_chart->legend()->setVisible(true);  // 显示图例m_chart->legend()->setAlignment(Qt::AlignBottom);m_chart->setTitle("等待数据...");setupAxes();applyDarkTheme();m_chartView->setChart(m_chart);m_chartView->setRenderHint(QPainter::Antialiasing, true);QHBoxLayout* layout = new QHBoxLayout(this);layout->addWidget(m_chartView);layout->setContentsMargins(0, 0, 0, 0);setLayout(layout);
}void ChartView::initUI()
{setMinimumSize(600, 400);
}void ChartView::setupAxes()
{m_axisX = new QValueAxis();m_axisY = new QValueAxis();m_axisX->setTickCount(6);m_axisX->setTitleText("时间 (t)");m_axisY->setTitleText("数值 (s)");m_chart->addAxis(m_axisX, Qt::AlignBottom);m_chart->addAxis(m_axisY, Qt::AlignLeft);
}void ChartView::applyDarkTheme()
{m_chart->setBackgroundBrush(QColor(25, 25, 35));m_chart->setTitleBrush(QColor(220, 220, 220));m_axisX->setTitleBrush(QColor(200, 200, 200));m_axisY->setTitleBrush(QColor(200, 200, 200));m_axisX->setLabelsColor(QColor(200, 200, 200));m_axisY->setLabelsColor(QColor(200, 200, 200));m_axisX->setGridLineColor(QColor(80, 80, 90));m_axisY->setGridLineColor(QColor(80, 80, 90));m_axisX->setLinePenColor(QColor(120, 120, 130));m_axisY->setLinePenColor(QColor(120, 120, 130));m_chartView->setBackgroundBrush(QColor(35, 35, 45));// 设置图例样式m_chart->legend()->setLabelColor(QColor(200, 200, 200));m_chart->legend()->setBackgroundVisible(true);m_chart->legend()->setBrush(QColor(35, 35, 45, 200));m_chart->legend()->setBorderColor(QColor(80, 80, 90));
}void ChartView::clearAllSeries()
{// 清除所有系列for (QLineSeries* series : m_seriesList) {m_chart->removeSeries(series);delete series;}m_seriesList.clear();
}QColor ChartView::getSeriesColor(int index) const
{// 为不同系列提供不同的颜色static const QVector<QColor> colors = {QColor(65, 105, 225),   // 皇家蓝QColor(220, 20, 60),    // 深红QColor(50, 205, 50),    // 酸橙绿QColor(255, 165, 0),    // 橙色QColor(138, 43, 226),   // 紫罗兰QColor(32, 178, 170),   // 浅海绿QColor(255, 99, 71),    // 番茄红QColor(147, 112, 219),  // 中紫色QColor(60, 179, 113),   // 中海洋绿QColor(255, 215, 0)     // 金色};return colors[index % colors.size()];
}void ChartView::slotsUpdateChart(const QStringList &listData, const QString &name, int flag)
{// 为了兼容性,将单个数据转换为多个数据的调用QVector<QStringList> dataLists = {listData};QStringList names = {name};slotsUpdateCharts(dataLists, names, flag);
}void ChartView::slotsUpdateCharts(const QVector<QStringList> &dataLists, const QStringList &names, int flag)
{if (dataLists.isEmpty()) {qWarning() << "数据列表为空!";m_chart->setTitle("无有效数据");return;}qreal yMin = std::numeric_limits<qreal>::max();qreal yMax = std::numeric_limits<qreal>::lowest();int maxDataLength = 0;// 清除现有系列clearAllSeries();// 处理每个数据系列for (int seriesIndex = 0; seriesIndex < dataLists.size(); ++seriesIndex) {const QStringList &listData = dataLists[seriesIndex];if (listData.isEmpty()) {qWarning() << "系列" << seriesIndex << "数据为空!";continue;}QLineSeries *series = new QLineSeries();// 设置系列名称QString seriesName;if (seriesIndex < names.size() && !names[seriesIndex].isEmpty()) {seriesName = names[seriesIndex];} else {seriesName = QString("系列 %1").arg(seriesIndex + 1);}series->setName(seriesName);// 设置系列颜色QPen pen(getSeriesColor(seriesIndex));pen.setWidth(2);series->setPen(pen);bool conversionOk = true;QVector<QPointF> points;points.reserve(listData.size());for (int i = 0; i < listData.size(); ++i) {bool ok;qreal y = listData[i].toDouble(&ok);if (!ok) {qWarning() << "数据转换失败:" << listData[i];conversionOk = false;continue;}qreal x = i;points.append(QPointF(x, y));yMin = qMin(yMin, y);yMax = qMax(yMax, y);}if (!conversionOk) {seriesName += " (部分数据错误)";series->setName(seriesName);}if (points.isEmpty()) {qWarning() << "系列" << seriesIndex << "没有有效的数据点";delete series;continue;}series->replace(points);m_chart->addSeries(series);m_seriesList.append(series);// 附加坐标轴series->attachAxis(m_axisX);series->attachAxis(m_axisY);// 更新最大数据长度maxDataLength = qMax(maxDataLength, listData.size());}if (m_seriesList.isEmpty()) {qWarning() << "没有有效的系列数据";m_chart->setTitle("无有效数据");return;}// 处理特殊情况if (qFuzzyCompare(yMin, yMax)) {if (qFuzzyIsNull(yMin)) {yMin = -1.0;yMax = 1.0;} else {yMin -= qAbs(yMin) * 0.1;yMax += qAbs(yMax) * 0.1;}} else {// 添加边距qreal margin = (yMax - yMin) * 0.1;yMin -= margin;yMax += margin;}// 设置坐标轴范围m_axisX->setRange(0, maxDataLength - 1);m_axisY->setRange(yMin, yMax);// 设置图表标题QString title;if (dataLists.size() == 1 && !names.isEmpty() && !names.first().isEmpty()) {title = names.first();} else {title = QString("多数据系列 (%1 个系列)").arg(dataLists.size());}// 添加时间戳到标题QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");m_chart->setTitle(QString("%1\n更新时间: %2").arg(title).arg(timestamp));
}

使用示例

// 创建图表视图
ChartView *chartView = new ChartView(this);// 准备多个数据系列
QVector<QStringList> dataLists;
QStringList names;// 第一个数据系列
QStringList data1;
data1 << "1.2" << "2.5" << "3.7" << "4.1" << "5.9";
dataLists.append(data1);
names.append("温度数据");// 第二个数据系列
QStringList data2;
data2 << "0.8" << "1.9" << "2.8" << "3.5" << "4.2";
dataLists.append(data2);
names.append("湿度数据");// 第三个数据系列
QStringList data3;
data3 << "2.1" << "3.2" << "4.5" << "5.1" << "6.8";
dataLists.append(data3);
names.append("压力数据");// 更新图表显示多个系列
chartView->updateCharts(dataLists, names, 0);

单个图表代码

chartview.h

#pragma once#include <QWidget>#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>QT_CHARTS_USE_NAMESPACEclass ChartView : public QWidget
{Q_OBJECTpublic:ChartView(QWidget *parent);~ChartView();void updateChart(QStringList listData, QString name, int flag);private:void initData();void initUI();private slots:void slotsUpdateChart(QStringList listData, QString name, int flag);private:QChart *m_chart;QChartView *m_chartView;QLineSeries *m_lineSeries;QValueAxis *m_axisX;QValueAxis *m_axisY;
};

chartview.cpp

#include "chartview.h"  // 引入自定义的 ChartView 头文件#include <QHBoxLayout>  // 引入水平布局类
#include <QDebug>		// 引入调试输出类#pragma execution_character_set("utf-8")  // 设置源代码文件的字符集为 utf-8// ChartView 类的构造函数,继承自 QWidget
ChartView::ChartView(QWidget* parent): QWidget(parent)  // 调用父类 QWidget 的构造函数,传入父控件
{initData();  // 调用初始化数据函数initUI();    // 调用初始化 UI 函数
}// ChartView 类的析构函数
ChartView::~ChartView()
{
}// 更新图表数据
void ChartView::updateChart(QStringList listData, QString name, int flag)
{slotsUpdateChart(listData, name, flag);  // 调用图表更新的槽函数
}// 初始化数据
void ChartView::initData()
{m_lineSeries = new QLineSeries();  // 创建一个新的线条序列(曲线数据)m_chartView = new QChartView(this);  // 创建图表视图m_chart = new QChart();  // 创建一个新的图表对象m_chart->addSeries(m_lineSeries);  // 将线条序列添加到图表中m_chart->legend()->hide();  // 隐藏图例m_chart->setTitle("--");  // 设置图表标题m_axisX = new QValueAxis();  // 创建 X 轴m_axisY = new QValueAxis();  // 创建 Y 轴m_axisX->setTickCount(6);  // 设置 X 轴上刻度的数量为 6m_axisX->setTitleText("t");  // 设置 X 轴的标题为 "t"m_axisY->setTitleText("s");  // 设置 Y 轴的标题为 "s"m_chart->addAxis(m_axisX, Qt::AlignBottom);  // 将 X 轴添加到图表并设置位置为底部m_chart->addAxis(m_axisY, Qt::AlignLeft);  // 将 Y 轴添加到图表并设置位置为左侧m_lineSeries->attachAxis(m_axisX);  // 将 X 轴附加到线条序列m_lineSeries->attachAxis(m_axisY);  // 将 Y 轴附加到线条序列m_chartView->setChart(m_chart);  // 设置图表视图的图表为当前图表m_chartView->setRenderHints(QPainter::Antialiasing);  // 设置图表渲染时使用抗锯齿QHBoxLayout* layout = new QHBoxLayout(this);  // 创建一个水平布局管理器layout->addWidget(m_chartView);  // 将图表视图添加到布局中layout->setContentsMargins(0, 0, 0, 0);  // 设置布局的内容边距为0setLayout(layout);  // 设置当前窗口的布局为刚创建的布局
}// 初始化 UI
void ChartView::initUI()
{m_lineSeries->setColor(QColor(255, 165, 0));  // 设置线条颜色为橙色m_chart->setTitleBrush(QColor(255, 99, 71));  // 设置图表标题文字颜色为番茄红m_chart->setBackgroundBrush(QColor(0, 0, 0));  // 设置图表背景颜色为黑色// 设置 X 轴和 Y 轴的标题文字和网格线的颜色m_axisX->setTitleBrush(QColor(255, 99, 71));m_axisY->setTitleBrush(QColor(255, 99, 71));m_axisX->setLabelsColor(QColor(255, 255, 255));  // 设置标签文字颜色为白色m_axisY->setLabelsColor(QColor(255, 255, 255));  // 设置标签文字颜色为白色m_axisX->setGridLineColor(QColor(105, 105, 105));  // 设置网格线为灰色m_axisY->setGridLineColor(QColor(105, 105, 105));  // 设置网格线为灰色// 设置坐标轴线的颜色为深灰色m_axisX->setLinePenColor(QColor(169, 169, 169));m_axisY->setLinePenColor(QColor(169, 169, 169));// 设置图表背景为深黑色m_chartView->setBackgroundBrush(QColor(0, 0, 0));}// 更新图表的数据显示
void ChartView::slotsUpdateChart(QStringList listData, QString name, int flag)
{qreal yMin = 100.0;  // 设置 Y 轴最小值初始为 100qreal yMax = -100.0;  // 设置 Y 轴最大值初始为 -100m_lineSeries->clear();  // 清除现有的线条数据// 遍历数据列表,更新图表数据for (int i = 0; i < listData.length(); ++i){qreal x = i;  // X 轴的值为数据的索引qreal y = listData[i].toDouble();  // Y 轴的值为数据列表中的值(转换为 double)// 更新 Y 轴的最小值和最大值yMin = qMin(yMin, y);yMax = qMax(yMax, y);// 将数据点添加到线条序列中m_lineSeries->append(x, y);}// 如果最小值与最大值相差非常小,扩大 Y 轴范围if (fabs(yMin - yMax) <= 0.0001){yMin -= fabs(yMin) * 0.2;  // 最小值再减去一定比例yMax += fabs(yMax) * 0.2;  // 最大值再加上一部分}// 如果最小值和最大值都接近 0,设置一个固定范围if (fabs(yMax) <= 0.0001 && fabs(yMin) <= 0.0001){yMin = -0.1;  // 最小值设为 -0.1yMax = 0.1;   // 最大值设为 0.1}// 为了留出一些空间,扩展 Y 轴的显示范围yMin = yMin - (yMax - yMin) * 0.1;yMax = yMax + (yMax - yMin) * 0.1;// 设置 X 轴和 Y 轴的显示范围m_axisX->setRange(0, listData.size()/10);  // X 轴范围为数据的数量m_axisY->setRange(yMin, yMax);  // Y 轴范围根据数据计算得出// 设置图表标题m_chart->setTitle(name);m_chart->update();  // 更新图表显示
}

使用示例

// 创建图表视图
ChartView *chartView = new ChartView(this);
QVBoxLayout* layout = new QVBoxLayout(ui->widget);
layout->setContentsMargins(2, 2, 2, 2);
layout->addWidget(chartView);
ui->widget->setLayout(layout);if (chartView) {chartView->updateChart(dataList, "Channel 4 Wavelength (nm)", 1);
}
http://www.xdnf.cn/news/19865.html

相关文章:

  • RabbitMQ模型详解与常见问题
  • 大数据开发/工程核心目标
  • 文心iRAG - 百度推出的检索增强的文生图技术,支持生成超真实图片
  • “AI 正回应时,也可随时打断?”揭秘 GPT Realtime × Gemini 的“全双工魔力”,都离不开它!
  • Python快速入门专业版(一):Windows/macOS/Linux 系统环境搭建(附常见报错解决)
  • postgresql9.2.4 跨版本升级14.6
  • 25高教社杯数模国赛【B题超高质量思路+问题分析】
  • 渲染是否伤电脑?从根源减少损伤的技巧
  • 字符串(1)
  • Bug 排查日记:一次曲折的技术解谜之旅
  • matlab 数据分析教程
  • 科学研究系统性思维的方法体系:质量控制
  • Redis C++ 实现笔记(F篇)
  • C/C++关键字——union
  • Python开篇撬动未来的万能钥匙 从入门到架构的全链路指南
  • 《IC验证必看|semaphore与mailbox的核心区别》
  • [从零开始面试算法] (11/100) LeetCode 226. 反转二叉树:递归的“镜像”魔法
  • RabbitMQ学习笔记
  • 找活招工系统源码 雇员雇主小程序 后端JAVA前端uniapp
  • 《云原生深坑实录:让团队卡壳的不是配置,是底层逻辑盲区》
  • 基于扣子平台构造AutoGen框架的多智能体使用-----封装成FastAPI接口供调用
  • JVM:程序计数器
  • 基于Matlab狭窄空间环境中多无人机自重构V字队形方法研究
  • 《清远市市级政务信息化服务项目立项审批细则(试行)》标准解读
  • Jenkins调用Ansible构建LNMP平台
  • 深入探索 WebSocket:构建实时应用的核心技术
  • DarkHole: 2靶场渗透
  • 用 SPL 编写阿里云 FC2.0 函数
  • AntdesignVue 的月份区间组件用法
  • mysql分页SQL