QChart控件:显示数据设置

一、数据清除

在 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() 直接指定颜色(支持 QColorQt::GlobalColor 或十六进制值)。
  • 复杂样式:通过 QPen 同时设置颜色、宽度和线条样式,调用 series->setPen(pen) 应用。
  • 动态修改:在程序运行中随时调用上述方法,线条颜色会实时更新。

这些方法适用于所有基于线条的系列(如 QLineSeriesQSplineSeries 等),通过灵活的样式设置可以使图表更具可读性。

三、导入数据显示提速

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. 第一步:做数据降采样(优先自适应降采样),将点数控制在1万以内(人眼完全可分辨,且渲染无压力)。
  2. 第二步:禁用点标记+简化线条样式,减少绘制元素。
  3. 第三步:批量更新数据+冻结视图,避免无效重绘。
  4. 第四步:超大数据量(百万级)时启用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);
}

通过以上步骤,即使原始数据达千万级,也能让界面保持流畅响应,同时不影响数据的视觉呈现效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值