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图层比较多的时候,删除所有图层很慢如何优化。
删除很慢的原因:
-
释放所有图层的资源(数据、样式、信号槽连接等)。
-
调整剩余图层的索引 (如删除
graph(2)
后,原来的graph(3)
变为graph(2)
)。 -
触发关联信号(如
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_pCustomPlot
或deleteLater()
,而它还挂在 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;
}