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点比较多时,拖动卡顿

相关推荐
脱脱克克22 分钟前
2025.4.9 华为机考 第1题-补丁版本升级
python·算法·华为
喜欢便码1 小时前
JS小练习0.1——弹出姓名
java·前端·javascript
王磊鑫2 小时前
重返JAVA之路-初识JAVA
java·开发语言
半兽先生3 小时前
WebRtc 视频流卡顿黑屏解决方案
java·前端·webrtc
liuluyang5303 小时前
C语言C11支持的结构体嵌套的用法
c语言·开发语言·算法·编译·c11
南星沐4 小时前
Spring Boot 常用依赖介绍
java·前端·spring boot
勤劳的进取家4 小时前
贪心算法之最小生成树问题
数据结构·python·算法·贪心算法·排序算法·动态规划
代码不停4 小时前
Java中的异常
java·开发语言
牛奶咖啡.8544 小时前
第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 A 组真题
c语言·数据结构·c++·算法·蓝桥杯
亓才孓5 小时前
[leetcode]stack的基本操作的回顾
算法