概述
这是一个基于 Qt Charts 的多系列数据可视化组件,支持同时显示多条数据曲线,每一条曲线可以有不同的颜色、样式和标签。组件采用暗色主题设计,具有良好的视觉效果和数据展示能力。
功能特性
- 支持多数据系列同时显示
- 自动分配不同颜色区分数据系列
- 智能图例显示与管理
- 暗色主题设计,减少视觉疲劳
- 高性能数据更新与渲染
- 自适应坐标轴范围
- 完善的错误处理机制
类结构
ChartView 类
主要成员变量
cpp
QChart *m_chart; // 图表对象
QChartView *m_chartView; // 图表视图
QVector<QLineSeries*> m_seriesList; // 数据系列容器
QValueAxis *m_axisX; // X轴
QValueAxis *m_axisY; // Y轴
核心方法
1. 构造函数与析构函数
cpp
explicit ChartView(QWidget *parent = nullptr);
~ChartView();
2. 数据更新接口
cpp
// 更新单个数据系列
void updateChart(const QStringList &listData, const QString &name, int flag);
// 更新多个数据系列
void updateCharts(const QVector<QStringList> &dataLists,
const QStringList &names,
int flag);
3. 辅助方法
cpp
void initData(); // 初始化数据
void initUI(); // 初始化界面
void setupAxes(); // 设置坐标轴
void applyDarkTheme(); // 应用暗色主题
void clearAllSeries(); // 清除所有数据系列
QColor getSeriesColor(int index) const; // 获取系列颜色
使用示例
基本用法
cpp
#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();
}
动态更新数据
cpp
// 模拟实时数据更新
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); // 每秒更新一次
}
自定义样式
cpp
// 自定义系列颜色和样式
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()
方法可以轻松切换主题风格:
cpp
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
文件中添加:
makefile
QT += charts
2. 包含头文件
cpp
#include "chartview.h"
3. 基本使用流程
- 创建
ChartView
实例 - 准备数据(
QVector<QStringList>
) - 准备系列名称(
QStringList
) - 调用
updateCharts()
方法更新显示
注意事项
- 内存管理:组件会自动管理数据系列的内存,无需手动释放
- 线程安全:不建议在多线程中直接调用更新方法,如需跨线程更新,应使用信号槽机制
- 性能考虑:对于大量数据点(>10000),建议进行数据采样或使用更高效的数据结构
- 错误处理:始终检查输入数据的有效性,避免传入空列表或无效数据
扩展建议
- 添加交互功能:可以实现数据点提示、缩放、平移等交互功能
- 支持多种图表类型:可以扩展支持柱状图、散点图等其他图表类型
- 数据导出:添加数据导出为图片或CS文件的功能
- 主题切换:实现亮色/暗色主题切换功能
这个多系列数据可视化组件提供了强大而灵活的数据展示能力,适合各种监控、分析和数据可视化应用场景。
完整代码
chartview.h
cpp
#pragma once
#include <QWidget>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
#include <QVector>
QT_CHARTS_USE_NAMESPACE
class ChartView : public QWidget
{
Q_OBJECT
public:
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
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));
}
使用示例
cpp
// 创建图表视图
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
cpp
#pragma once
#include <QWidget>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
QT_CHARTS_USE_NAMESPACE
class ChartView : public QWidget
{
Q_OBJECT
public:
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
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 轴上刻度的数量为 6
m_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); // 设置布局的内容边距为0
setLayout(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 轴最小值初始为 100
qreal yMax = -100.0; // 设置 Y 轴最大值初始为 -100
m_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.1
yMax = 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(); // 更新图表显示
}
使用示例
cpp
// 创建图表视图
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);
}