显示数据属性
一、数据清除
在 Qt 的 QChart 中,“清除数据”和“删除数据”通常针对两种操作:清除系列中的数据点(保留数据但保留系列本身)和删除整个数据系列(从图表中移除系列)。
1.1 清除系列中的数据点(保留系列)
如果需要清空某条线(QLineSeries)、散点(QScatterSeries)等数据系列中的所有数据点,但希望保留该系列在图表中的存在(例如后续可重新添加数据),可使用系列的 clear() 方法。
#include <QChart>
#include <QLineSeries>
#include <QChartView>
#include <QPushButton>
#include <QVBoxLayout>
// 创建图表和系列
QChart *chart = new QChart();
QLineSeries *series = new QLineSeries();
series->setName("数据系列1");
chart->addSeries(series);
// 向系列添加数据
*series << QPointF(0, 1) << QPointF(1, 3) << QPointF(2, 2);
// 清除系列中的所有数据点(保留系列本身)
QPushButton *clearBtn = new QPushButton("清除数据");
connect(clearBtn, &QPushButton::clicked, [series]() {
series->clear(); // 清空所有数据点
// 可选:清除后刷新图表视图(部分场景需要手动触发)
series->parentChart()->update();
});
// 显示图表
QChartView *chartView = new QChartView(chart);
- 操作后,系列仍在图表中(图例中仍可见),但无数据点。
- 可通过
append()重新向系列添加数据。
1.2 删除整个数据系列(从图表中移除)
如果需要彻底移除某条数据系列(包括其所有数据点和在图表中的显示),需先从图表中移除系列,再根据需求销毁该系列对象。
// 从图表中删除系列并销毁
QPushButton *removeBtn = new QPushButton("删除系列");
connect(removeBtn, &QPushButton::clicked, [chart, series]() {
// 步骤1:从图表中移除系列
chart->removeSeries(series);
// 步骤2:销毁系列对象(避免内存泄漏)
series->deleteLater(); // 安全销毁对象
// 或直接 delete series;(确保无其他地方引用)
});
- 操作后,系列从图表中完全消失(图例中也会移除)。
- 若需重新显示该系列,需重新创建系列并添加到图表。
1.3 删除指定范围的数据点(部分删除)
如果只需删除系列中某段范围的数据点(如特定 X 轴区间内的数据),需手动筛选数据并重新设置系列数据。
// 删除 X 轴小于 2 的数据点
void removeRange(QLineSeries *series, qreal maxX) {
QVector<QPointF> points = series->pointsVector(); // 获取所有数据点
QVector<QPointF> remainingPoints;
// 筛选保留的数据点(X > maxX)
for (const QPointF &p : points) {
if (p.x() > maxX) {
remainingPoints.append(p);
}
}
// 用筛选后的数据替换系列中的数据
series->replace(remainingPoints);
}
// 调用:删除 X < 2 的数据点
removeRange(series, 2.0);
1.4 清除图表中所有系列
如果需要删除图表中所有数据系列,可遍历所有系列并逐一移除:
// 清除图表中所有系列
void clearAllSeries(QChart *chart) {
// 先获取所有系列(需复制一份,避免遍历中修改容器)
QList<QAbstractSeries *> allSeries = chart->series();
foreach (QAbstractSeries *series, allSeries) {
chart->removeSeries(series);
series->deleteLater(); // 销毁系列
}
}
// 调用
clearAllSeries(chart);
1.5 总结
| 操作需求 | 方法 | 特点 |
|---|---|---|
| 清空系列数据(保留系列) | series->clear() | 系列仍在图表中,可重新添加数据 |
| 删除整个系列 | chart->removeSeries(series) + delete | 系列从图表中移除,彻底销毁 |
| 删除部分数据点 | 筛选数据后 series->replace() | 灵活保留指定范围数据,需手动实现筛选 |
| 清除所有系列 | 遍历 chart->series() 并移除 | 清空图表中所有数据系列 |
根据实际需求选择对应方法,注意删除系列后需销毁对象以避免内存泄漏。
二、 显示设置
2.1 颜色
在 Qt 的 QChart 中,设置线条(如 QLineSeries)的颜色可以通过 QPen 类实现,也可以直接使用系列提供的简化接口。
1. 直接设置线条颜色(简化方式)
QLineSeries 等系列提供了 setColor() 方法,可直接设置线条颜色:
#include <QLineSeries>
#include <QChart>
// 创建线系列
QLineSeries *series = new QLineSeries();
series->setName("数据曲线");
// 直接设置线条颜色(支持 QColor、RGB 值、预定义颜色名)
series->setColor(QColor(255, 0, 0)); // 红色(RGB值)
// 或
series->setColor(Qt::blue); // Qt 预定义颜色(蓝色)
// 或
series->setColor(QColor("#00FF00")); // 十六进制颜色(绿色)
// 添加数据并关联到图表
*series << QPointF(0, 1) << QPointF(1, 3) << QPointF(2, 2);
QChart *chart = new QChart();
chart->addSeries(series);
2. 通过 QPen 设置(更灵活)
如果需要同时设置线条颜色、宽度、样式(如实线、虚线),可以使用 QPen 类:
#include <QPen>
#include <QLineSeries>
QLineSeries *series = new QLineSeries();
// 创建画笔并设置属性
QPen pen;
pen.setColor(QColor(0, 0, 255)); // 蓝色
pen.setWidth(3); // 线宽 3 像素
pen.setStyle(Qt::DashLine); // 虚线样式(可选:实线、虚线、点线等)
// 将画笔应用到系列
series->setPen(pen);
- 常用线条样式(
Qt::PenStyle):Qt::SolidLine:实线(默认)Qt::DashLine:虚线(- - -)Qt::DotLine:点线(...)Qt::DashDotLine:点划线(- . - .)Qt::DashDotDotLine:双点划线(- . . - . .)
3. 动态修改线条颜色
如果需要在程序运行中动态改变线条颜色(如响应按钮点击),直接调用上述方法即可:
// 按钮点击事件:切换线条颜色
connect(changeColorBtn, &QPushButton::clicked, [series]() {
// 随机切换颜色
series->setColor(QColor(qrand() % 256, qrand() % 256, qrand() % 256));
});
#include <QApplication>
#include <QChartView>
#include <QLineSeries>
#include <QPen>
#include <QVBoxLayout>
#include <QPushButton>
#include <QWidget>
QT_CHARTS_USE_NAMESPACE // 启用Qt Charts命名空间
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// 创建主窗口
QWidget *window = new QWidget();
window->setWindowTitle("QChart 线条颜色设置");
QVBoxLayout *layout = new QVBoxLayout(window);
// 创建图表和视图
QChart *chart = new QChart();
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing); // 抗锯齿
layout->addWidget(chartView);
// 创建线系列并添加数据
QLineSeries *series = new QLineSeries();
*series << QPointF(0, 1) << QPointF(1, 3) << QPointF(2, 2)
<< QPointF(3, 5) << QPointF(4, 4);
chart->addSeries(series);
chart->createDefaultAxes(); // 创建默认坐标轴
// 1. 直接设置颜色(初始为红色)
series->setColor(Qt::red);
// 添加按钮:切换为蓝色虚线
QPushButton *btn1 = new QPushButton("设置为蓝色虚线");
connect(btn1, &QPushButton::clicked, [series]() {
QPen pen(Qt::blue, 2, Qt::DashLine); // 颜色、宽度、样式
series->setPen(pen);
});
layout->addWidget(btn1);
// 添加按钮:随机颜色
QPushButton *btn2 = new QPushButton("随机颜色");
connect(btn2, &QPushButton::clicked, [series]() {
series->setColor(QColor(qrand() % 256, qrand() % 256, qrand() % 256));
});
layout->addWidget(btn2);
window->resize(800, 600);
window->show();
return a.exec();
}
- 简单设置:使用
series->setColor()直接指定颜色(支持QColor、Qt::GlobalColor或十六进制值)。 - 复杂样式:通过
QPen同时设置颜色、宽度和线条样式,调用series->setPen(pen)应用。 - 动态修改:在程序运行中随时调用上述方法,线条颜色会实时更新。
这些方法适用于所有基于线条的系列(如 QLineSeries、QSplineSeries 等),通过灵活的样式设置可以使图表更具可读性。
三、导入数据显示提速
当 QLineSeries 中数据点过多导致界面无响应时,核心解决思路是减少实际渲染的数据量并优化绘制与数据更新逻辑,从根本上降低CPU/GPU负载。以下是分步骤的具体解决方案:
3.1 优先做数据降采样:减少绘制点数(最关键)
人眼无法区分高密度重叠的点,大量冗余点只会浪费资源。通过降采样保留关键特征点,既能保证视觉效果,又能大幅减少绘制压力。
1. 自适应降采样(推荐,适合非均匀数据)
使用 Douglas-Peucker算法,根据数据曲线的曲率动态保留拐点(如峰值、谷值),丢弃冗余的平缓点,是平衡“视觉精度”和“性能”的最优选择。
// 自适应降采样核心函数(Douglas-Peucker算法)
QVector<QPointF> adaptiveDownSample(const QVector<QPointF>& rawPoints, double epsilon) {
if (rawPoints.size() <= 2) return rawPoints; // 点数过少无需降采样
// 1. 找线段(首末点)的最远点
QPointF start = rawPoints.first();
QPointF end = rawPoints.last();
int maxDistIndex = 0;
double maxDist = 0.0;
for (int i = 1; i < rawPoints.size() - 1; ++i) {
// 计算点到线段的垂直距离(判断点是否“关键”)
double dist = QLineF(start, end).distanceToPoint(rawPoints[i]);
if (dist > maxDist) {
maxDist = dist;
maxDistIndex = i;
}
}
// 2. 距离超阈值则递归保留该点,否则只保留首末点
QVector<QPointF> result;
if (maxDist > epsilon) {
// 递归处理左半段(首点到最远点)和右半段(最远点到末点)
auto leftPart = adaptiveDownSample(QVector<QPointF>(rawPoints.begin(), rawPoints.begin() + maxDistIndex + 1), epsilon);
auto rightPart = adaptiveDownSample(QVector<QPointF>(rawPoints.begin() + maxDistIndex, rawPoints.end()), epsilon);
// 合并两段(去掉重复的最远点)
result = leftPart + rightPart.mid(1);
} else {
result << start << end; // 平缓线段只保留首尾,减少点数
}
return result;
}
// 使用方式:对原始数据降采样后再添加到系列
QVector<QPointF> rawData = ...; // 原始大量数据(如10万点)
// epsilon=0.5(可调整:值越小保留点数越多,值越大降采样越激进)
QVector<QPointF> sampledData = adaptiveDownSample(rawData, 0.5);
series->replace(sampledData); // 批量替换数据(避免逐条添加)
2. 均匀降采样(适合周期性数据,简单快速)
若数据是均匀分布的(如定时采集的传感器数据),可直接每隔N个点保留1个,实现简单且效率高。
QVector<QPointF> uniformDownSample(const QVector<QPointF>& rawPoints, int sampleStep) {
QVector<QPointF> result;
if (sampleStep <= 1) return rawPoints; // 步长≤1无意义
for (int i = 0; i < rawPoints.size(); i += sampleStep) {
result.append(rawPoints[i]);
}
// 确保最后一个点被保留(避免丢失数据尾部)
if (!result.isEmpty() && result.last() != rawPoints.last()) {
result.append(rawPoints.last());
}
return result;
}
// 使用方式:每10个点保留1个(点数减少到1/10)
QVector<QPointF> sampledData = uniformDownSample(rawData, 10);
series->replace(sampledData);
3.2 优化绘制逻辑:减少渲染开销
1. 禁用不必要的绘制元素
- 隐藏点标记:如果只需显示线条,无需显示单个点,直接关闭点标记(减少90%以上的绘制元素)。
QLineSeries *series = new QLineSeries(); series->setPointsVisible(false); // 关键:只画线,不画点(极大提升速度) - 简化线条样式:避免使用虚线、点线等复杂样式,改用实线;线宽设为1px(默认2px),减少GPU渲染负载。
QPen linePen(Qt::blue, 1); // 线宽1px,实线 linePen.setStyle(Qt::SolidLine); series->setPen(linePen);
2. 启用OpenGL加速渲染(超大数据量必备)
Qt Charts 支持 OpenGL 硬件加速,可将绘制压力从CPU转移到GPU,尤其适合百万级以上数据。
#include <QOpenGLWidget>
// 1. 为ChartView设置OpenGL视图(需在.pro中添加 QT += opengl)
QChartView *chartView = new QChartView(chart);
chartView->setViewport(new QOpenGLWidget()); // 启用OpenGL加速
// 2. 禁用抗锯齿(进一步提升速度,若画质可接受)
chartView->setRenderHint(QPainter::Antialiasing, false);
3.3 优化数据更新:避免主线程阻塞
1. 批量更新数据,禁止逐条添加
避免用 series->append() 逐条添加数据(每添加1个点触发1次重绘),改用 series->replace() 批量替换,仅触发1次重绘。
// 低效:逐条添加(10万点触发10万次重绘,必然卡顿)
for (const auto& p : rawData) {
series->append(p);
}
// 高效:批量替换(仅触发1次重绘)
series->replace(sampledData);
2. 数据更新时冻结视图,减少中间重绘
更新数据前暂时禁用视图刷新,避免中间状态的无效重绘。
chartView->setUpdatesEnabled(false); // 冻结视图,不响应重绘
// 执行数据降采样和替换
QVector<QPointF> sampledData = adaptiveDownSample(rawData, 0.5);
series->replace(sampledData);
chartView->setUpdatesEnabled(true); // 解冻,触发1次完整重绘
3. 耗时操作放后台线程,避免UI阻塞
若数据降采样(尤其是自适应降采样)耗时较长(如百万级原始数据),需将降采样逻辑放入后台线程,避免主线程(UI线程)被阻塞导致无响应。
// 1. 自定义后台处理器(继承QObject,处理降采样)
class DownSampleWorker : public QObject {
Q_OBJECT
public slots:
void doDownSample(const QVector<QPointF>& rawData, double epsilon) {
QVector<QPointF> sampledData = adaptiveDownSample(rawData, epsilon);
emit finished(sampledData); // 发送结果到主线程
}
signals:
void finished(const QVector<QPointF>& sampledData);
};
// 2. 在主线程中启动后台线程
DownSampleWorker *worker = new DownSampleWorker();
QThread *workerThread = new QThread();
worker->moveToThread(workerThread);
// 3. 线程完成后,在主线程更新图表(UI操作必须在主线程)
connect(worker, &DownSampleWorker::finished, this, [=](const QVector<QPointF>& sampledData) {
chartView->setUpdatesEnabled(false);
series->replace(sampledData);
chartView->setUpdatesEnabled(true);
// 释放线程资源
workerThread->quit();
workerThread->wait();
worker->deleteLater();
workerThread->deleteLater();
});
// 4. 启动后台降采样(主线程不阻塞,可正常响应UI)
workerThread->start();
QMetaObject::invokeMethod(worker, "doDownSample", Q_ARG(QVector<QPointF>, rawData), Q_ARG(double, 0.5));
3.4 总结:优化优先级
- 第一步:做数据降采样(优先自适应降采样),将点数控制在1万以内(人眼完全可分辨,且渲染无压力)。
- 第二步:禁用点标记+简化线条样式,减少绘制元素。
- 第三步:批量更新数据+冻结视图,避免无效重绘。
- 第四步:超大数据量(百万级)时启用OpenGL加速+后台线程处理。
#ifndef BIGDATACHARTWIDGET_H
#define BIGDATACHARTWIDGET_H
#include <QWidget>
#include <QChart>
#include <QLineSeries>
#include <QChartView>
#include <QThread>
#include <QVector>
#include <QPointF>
#include <QPen>
#include <QVBoxLayout>
#include <QPushButton>
QT_CHARTS_USE_NAMESPACE // 启用 Qt Charts 命名空间
// 后台降采样处理器(负责在子线程中执行耗时的降采样逻辑)
class DownSampleWorker : public QObject {
Q_OBJECT
public slots:
// 执行自适应降采样(Douglas-Peucker算法)
void doAdaptiveDownSample(const QVector<QPointF>& rawData, double epsilon) {
QVector<QPointF> sampledData = adaptiveDownSample(rawData, epsilon);
emit downSampleFinished(sampledData); // 发送结果到主线程
}
signals:
void downSampleFinished(const QVector<QPointF>& sampledData); // 降采样完成信号
private:
// 自适应降采样核心算法(Douglas-Peucker)
QVector<QPointF> adaptiveDownSample(const QVector<QPointF>& rawPoints, double epsilon) {
if (rawPoints.size() <= 2) return rawPoints;
// 找线段(首末点)的最远点
QPointF start = rawPoints.first();
QPointF end = rawPoints.last();
int maxDistIndex = 0;
double maxDist = 0.0;
for (int i = 1; i < rawPoints.size() - 1; ++i) {
double dist = QLineF(start, end).distanceToPoint(rawPoints[i]);
if (dist > maxDist) {
maxDist = dist;
maxDistIndex = i;
}
}
// 递归保留关键节点
QVector<QPointF> result;
if (maxDist > epsilon) {
auto left = adaptiveDownSample(QVector<QPointF>(rawPoints.begin(), rawPoints.begin() + maxDistIndex + 1), epsilon);
auto right = adaptiveDownSample(QVector<QPointF>(rawPoints.begin() + maxDistIndex, rawPoints.end()), epsilon);
result = left + right.mid(1); // 合并避免重复点
} else {
result << start << end;
}
return result;
}
};
// 主图表控件(整合所有优化逻辑)
class BigDataChartWidget : public QWidget {
Q_OBJECT
public:
explicit BigDataChartWidget(QWidget *parent = nullptr);
// 加载原始大数据(对外接口)
void loadRawData(const QVector<QPointF>& rawData);
private slots:
// 接收后台降采样结果,更新图表(主线程执行)
void onDownSampleFinished(const QVector<QPointF>& sampledData);
// 测试按钮:生成10万点原始数据并加载
void onTestLoadDataClicked();
private:
QChart* m_chart; // 图表对象
QLineSeries* m_series; // 线系列(存储降采样后的数据)
QChartView* m_chartView; // 图表视图(启用OpenGL加速)
QPushButton* m_testBtn; // 测试按钮
QVBoxLayout* m_layout; // 布局
};
#endif // BIGDATACHARTWIDGET_H
#include "BigDataChartWidget.h"
#include <QOpenGLWidget>
#include <QRandomGenerator>
#include <qDebug>
BigDataChartWidget::BigDataChartWidget(QWidget *parent) : QWidget(parent) {
// 1. 初始化布局
m_layout = new QVBoxLayout(this);
m_layout->setContentsMargins(10, 10, 10, 10);
// 2. 初始化图表和系列(核心优化:禁用点标记)
m_chart = new QChart();
m_series = new QLineSeries();
m_series->setName("大数据曲线(降采样后)");
m_series->setPointsVisible(false); // 关键:只画线,不画点(减少90%渲染负载)
// 3. 简化线条样式(线宽1px,实线,减少GPU压力)
QPen linePen(Qt::blue, 1);
linePen.setStyle(Qt::SolidLine);
m_series->setPen(linePen);
// 4. 初始化图表视图(启用OpenGL加速)
m_chartView = new QChartView(m_chart);
m_chartView->setRenderHint(QPainter::Antialiasing, false); // 禁用抗锯齿(进一步提速)
// 设置OpenGL视图(需在.pro中添加 QT += opengl)
m_chartView->setViewport(new QOpenGLWidget());
m_layout->addWidget(m_chartView);
// 5. 初始化测试按钮(生成10万点数据)
m_testBtn = new QPushButton("加载10万点原始数据", this);
connect(m_testBtn, &QPushButton::clicked, this, &BigDataChartWidget::onTestLoadDataClicked);
m_layout->addWidget(m_testBtn);
// 6. 将系列添加到图表并创建默认坐标轴
m_chart->addSeries(m_series);
m_chart->createDefaultAxes();
m_chart->setTitle("QChart 大数据优化示例");
}
// 加载原始大数据(对外接口)
void BigDataChartWidget::loadRawData(const QVector<QPointF>& rawData) {
qDebug() << "原始数据点数:" << rawData.size();
// 禁用按钮防止重复点击,避免多线程冲突
m_testBtn->setEnabled(false);
// 7. 启动后台线程执行降采样(避免主线程阻塞)
DownSampleWorker* worker = new DownSampleWorker();
QThread* workerThread = new QThread();
worker->moveToThread(workerThread);
// 8. 线程逻辑:执行降采样 → 主线程更新图表 → 释放资源
connect(workerThread, &QThread::started, [=]() {
// 调用降采样(epsilon=0.5,可根据需求调整:值越小保留点数越多)
worker->doAdaptiveDownSample(rawData, 0.5);
});
// 接收降采样结果,在主线程更新图表
connect(worker, &DownSampleWorker::downSampleFinished, this, &BigDataChartWidget::onDownSampleFinished);
// 释放线程和处理器资源
connect(worker, &DownSampleWorker::downSampleFinished, workerThread, &QThread::quit);
connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
// 启动线程
workerThread->start();
}
// 接收后台降采样结果,更新图表(主线程执行)
void BigDataChartWidget::onDownSampleFinished(const QVector<QPointF>& sampledData) {
qDebug() << "降采样后点数:" << sampledData.size();
// 9. 批量更新数据(冻结视图,避免中间重绘)
m_chartView->setUpdatesEnabled(false); // 冻结视图
m_series->replace(sampledData); // 批量替换(仅1次重绘)
m_chartView->setUpdatesEnabled(true); // 解冻视图,触发最终重绘
// 恢复按钮可用
m_testBtn->setEnabled(true);
}
// 测试按钮:生成10万点原始数据(模拟真实大数据场景)
void BigDataChartWidget::onTestLoadDataClicked() {
QVector<QPointF> rawData;
rawData.reserve(100000); // 预分配内存,避免动态扩容耗时
// 生成10万点随机曲线数据(模拟传感器、波形等场景)
for (int x = 0; x < 100000; ++x) {
// 正弦波+随机噪声(模拟真实数据的波动)
double y = qSin(x * 0.01) + QRandomGenerator::global()->bounded(-0.2, 0.2);
rawData.append(QPointF(x, y));
}
// 加载原始数据(触发后台降采样和图表更新)
loadRawData(rawData);
}
通过以上步骤,即使原始数据达千万级,也能让界面保持流畅响应,同时不影响数据的视觉呈现效果。

2641

被折叠的 条评论
为什么被折叠?



