QCustomPlot-相关优化

QCustomPlot循环上图

cpp 复制代码
for(int i=0;i<GraphNum; i++)
    {

        int startPosX = selectDataX1[i];
        int endPosX = selectDataX2[i];

        m_pCustomPlot->addGraph();
        m_pCustomPlot->graph(mGraphNum)->setPen(QPen(QColor(255,255,0)));
        m_pCustomPlot->graph(mGraphNum)->data()->vCoreData=CoreData;
        qDebug()<<"m_pCustomPlot->graph(mGraphNum)->data()->vCoreData:"<<m_pCustomPlot->graph(mGraphNum)->data()->vCoreData<<CoreData;
        m_pCustomPlot->graph(mGraphNum)->setSmooth(true);   // 开启平滑曲线
        int nCount=endPosX-startPosX+1;

        mData2 = m_pCustomPlot->graph(mGraphNum)->data()->coreData();
        mData2->resize(nCount);

        memcpy(mData2->data(), mData->data()+startPosX, sizeof(QCPGraphData)*nCount);

        mGraphNum++;
        sum += mData2->size();
    }

排查后发现是这个循环很耗时,需要循环将数据上图,这个循环上图比较慢。

想法是只addGraph添加一个图层,然后将数据拼接到数据中上图,

1、然后实现后又出现一个问题,创建一个图层,但是把所有图层数据都拼接进去,理想应该这一个图层但是显示的是所有图层的数据曲线,但结果只显示最后一个。

由于QCustomPlot 可能无法自动处理不连续的数据,即使你把所有数据拼接到 mData2,QCustomPlot 默认会 按顺序连接所有点,如果数据区间之间有间隙,可能会画出一条错误的连接线,QCustomPlot 的 QCPGraph 是按 连续点序列 绘制的,不能自动识别多段数据。需要 手动插入 NaN 来分隔不同区间的数据使其连续,避免错误的连线。

cpp 复制代码
片段1: (x=1, y=2), (x=2, y=3), (x=3, y=1)  
片段2: (x=5, y=4), (x=6, y=2), (x=7, y=5)  
片段3: (x=9, y=1), (x=10, y=3), (x=11, y=4)  

片段1和片段2不连续,需要插入x=4

下面是完整的代码:

cpp 复制代码
int sumCount = 0;
for (int i = 0; i < GraphNum; ++i)
{
    int startPosX = selectDataX1[i];
    int endPosX = selectDataX2[i];

    // 不同区间需要插入补充的元素个数
    if(i > 0){
        int bc = selectDataX1[i] - selectDataX2[i - 1] - 1;
        sumCount += bc;
    }

    sumCount += endPosX - startPosX + 1;
}

// 只添加一个图层
m_pCustomPlot->addGraph();
m_pCustomPlot->graph(mGraphNum)->setPen(QPen(QColor(255,255,0)));
m_pCustomPlot->graph(mGraphNum)->data()->vCoreData=CoreData;
m_pCustomPlot->graph(mGraphNum)->setSmooth(true);   // 开启平滑曲线
mData2 = m_pCustomPlot->graph(mGraphNum)->data()->coreData();
mData2->resize(sumCount);
mGraphNum++;


// 拼接数据
int mData2Pointer = 0;
for (int i = 0; i < GraphNum; ++i)
{
    int startPosX = selectDataX1[i];
    int endPosX = selectDataX2[i];
    int nCount = endPosX - startPosX + 1;

//        if (startPosX < 0 || endPosX >= mData->size() || startPosX > endPosX)
//            continue;

    // 插入nan使其连续
    if(i > 0) {
        int bc = selectDataX1[i] - selectDataX2[i - 1] - 1;
        for (int j = 1; j <= bc; ++j) {
            mData2->data()[mData2Pointer].key = selectDataX2[i - 1] + j;
//                mData2->data()[mData2Pointer].key = qQNaN();
//                mData2->data()[mData2Pointer].value = qQNaN();
            mData2->data()[mData2Pointer].value = 1000;
            mData2Pointer++;
        }
    }


    memcpy(mData2->data() + mData2Pointer, mData->data()+startPosX, sizeof(QCPGraphData)*nCount);
    mData2Pointer += nCount;


}

这样弄完发现,所有区间以及区间间的缝隙都画出来了。。显然还是不符合,考虑到这个方法会导致mData2会多出很多缝隙内存,而且也需要for循环设置数据,可能速度也快不了多少。

2、最后方案

IFShow-start QTime("09:11:56.182")

sum: 47993172

IFShow-end QTime("09:12:03.794")

合并比较连续的 忽略较小缝隙 否则图形添加的太多

片段1: (x=1, y=2), (x=2, y=3), (x=3, y=1)

片段2: (x=5, y=4), (x=6, y=2), (x=7, y=5)

片段3: (x=9, y=1), (x=10, y=3), (x=11, y=4)

比如上面有三个区间,原本要画三个图的,现在片段1和片段2合并成一个图,因为缝隙较小,因为原本片段1和片段2缝隙很小,QCustomPlot 平滑连成一条曲线。

IFShow-start QTime("09:14:36.704")

sum: 48016548

IFShow-end QTime("09:14:36.942")

QCustomPlot删除图层速度

QCustomPlot图层变化时,需要删除旧QCustomPlot的图层,当QCustomPlot图层比较多的时候,删除所有图层很慢如何优化。

删除很慢的原因:

  1. 释放所有图层的资源(数据、样式、信号槽连接等)。

  2. 调整剩余图层的索引 (如删除 graph(2) 后,原来的 graph(3) 变为 graph(2))。

  3. 触发关联信号(如 legendChanged),可能导致额外计算。

cpp 复制代码
if(graphCount > 1){

        // 1. 保存第一个图层的数据(可选)
        QCPGraph *firstGraph = m_pCustomPlot->graph(0);
        QSharedPointer<QCPGraphDataContainer> firstGraphData = firstGraph->data();

        // 2. 清空所有图层
        qDebug()<<"clear-allGraph-start"<< QTime::currentTime();
        m_pCustomPlot->clearGraphs();
        qDebug()<<"clear-allGraph-end"<< QTime::currentTime();

        // 3. 重新添加第一个图层
        m_pCustomPlot->addGraph();
        m_pCustomPlot->graph(0)->setData(firstGraphData); // 恢复数据
        m_pCustomPlot->graph(0)->setPen(QColor(75, 243, 167)); // 恢复样式
    }

上面那个还是很慢,

2、考虑销毁原本的QCustomPlot(带有很多图层的),然后重建

cpp 复制代码
    if(m_pCustomPlot){

        delete m_pCustomPlot;  // 调用析构函数
        m_pCustomPlot = nullptr;  // 避免悬空指针
    }

    m_pCustomPlot = new LXCustomPlot();

当时销毁时由于需要:释放图层、图表、图例(比如 QCPLegend)等多个子对象;触发一堆 Qt 事件(比如 QObject::destroyed 信号);如果图表数据很大,还可能释放很多内存;导致销毁时也很慢。想到了异步销毁,但因为 m_pCustomPlot 是一个继承自 QWidget 的 UI 对象 ,而 Qt 明确规定所有 QWidget GUI必须在主线程中创建、操作、销毁(因为 UI 对象只能在主线程操作)。

cpp 复制代码
// 异步销毁旧LDrawSpectrum
QFuture<void> future = QtConcurrent::run([this]() {
    qDebug()<<"delete m_pCustomPlot-start"<<QTime::currentTime();
    delete m_pCustomPlot;  // 调用析构函数
    qDebug()<<"delete m_pCustomPlot-end"<<QTime::currentTime();
    m_pCustomPlot = nullptr;  // 避免悬空指针
});

3、销毁的路不太行,直接在原QCustomPlot的布局中移除旧QCustomPlot,然后添加新的QCustomPlot

cpp 复制代码
if(m_pCustomPlot){
    minValue = m_pCustomPlot->m_Yaxis.lower;
    maxValue = m_pCustomPlot->m_Yaxis.upper;
    old_pCustomPlot = m_pCustomPlot; // 记录旧的

}

m_pCustomPlot = new LXCustomPlot();

emit customPlotChanged(old_pCustomPlot); // 告知变更 并传递旧的


connect(SpectrumIF,&LDrawSpectrum::customPlotChanged,[this](LXCustomPlot *old_pCustomPlot){
    qDebug()<<"SpectrumIF,&LDrawSpectrum::customPlotChanged";
    this->SpectrumIF->layout()->removeWidget(old_pCustomPlot); // 移除旧的
    this->SpectrumIF->layout()->addWidget(SpectrumIF->m_pCustomPlot); // 添加新的

    // 销毁旧的 防止内存泄漏
    old_pCustomPlot->setParent(nullptr);  // 防止它还在 layout 里被 Qt 管
    old_pCustomPlot->deleteLater();       // 延迟删除,安全释放
});

old_pCustomPlot->setParent(nullptr);

✅ 意思:解除旧控件和父控件之间的父子关系

在 Qt 中:

  • 每个 QWidget(比如你的 LXCustomPlot)都有一个 父对象parent);

  • 如果它加入了某个 layout,layout 背后的父窗口通常会自动成为它的父;

  • Qt 会自动管理带 parent 的子对象的生命周期 (也就是:父对象析构时,自动 delete 子对象)。

目的是防止二次销毁,如果我们使用delete old_pCustomPlotdeleteLater(),而它还挂在 layout 或 parent 上,可能导致二次销毁。
old_pCustomPlot->deleteLater();

✅ 意思:不是现在就删,而是等 Qt 当前事件处理完之后再删

这个函数实际上是:

  • 把这个对象加入 Qt 的删除队列;

  • 一旦事件循环空闲,它就会调用 delete this

  • 避免了立即销毁引发的线程或事件崩溃。

4、最终还是没办法解决,QT中删除图层,每个删除都会触发一次图层的析构(可能涉及父对象指针解绑、legend 刷新、layout 重排等),目前待发现更好的方法。

有趣的是发现C#中删除同样图层个数,几乎是秒完成,无感,调用 Remove() → 仅清除引用,GC 延迟释放,而且是延迟释放、由垃圾回收器管理

cs 复制代码
private void ClearPositionSelect()
{
    Console.WriteLine("删除图个数:" + positionSelectedPlotList.Count);
    for (int i = 0; i < positionSelectedPlotList.Count; i++)
    {
        Plot.Remove(positionSelectedPlotList[i]);
        positionSelectedPlotList[i] = null;
    }
    positionSelectedPlotList.Clear();

    SelectedData = null;
}

QCustomPlot的QCPGraph点比较多时,拖动卡顿

相关推荐
卡尔特斯4 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
白鲸开源4 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole4 小时前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫4 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide5 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户3721574261355 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源5 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
CoovallyAIHub5 小时前
中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
深度学习·算法·计算机视觉
Java中文社群6 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心6 小时前
从零开始学Flink:数据源
java·大数据·后端·flink