PDA

View Full Version : Issue deleting and replacing 3Dscatter and 3DSurface plots in Qt



Stophe
9th July 2020, 15:13
Hi everybody,
I'm working with QtDataVisualization in C++ and I've been stuck for half a year with the following problem : when I try to replace a Q3DScatter graph A by a Q3DScatter graph B, the deletion of graph A makes graph B unreadable if B is built before A is destroyed.

I'm trying to figure out at which point graph A and graph B interact with each other while they are not supposed to. I have a sample code here that is close to a MWE. I have let the function "build" in, but what it does is just initializing the graph and storing its address in a pointer, m3DGraph.

If PROBLEM is set to false, deletion step and creation of the next data are exchanged, and the issue does not happen, but it's not what I'm looking for since I want to update my display once the data is computed, and to be able to change the type of graph in the m3DImageHolder.

I think it's worth mentionning that I've done the test with a Q3DSurface graph and there is a similar issue : with Q3DScatter, the graph doesn't display, with Q3DSurface, the grid disappears.

Here is a bit of code below, and I let a link to my StackOverflow (https://stackoverflow.com/questions/62719592/issue-deleting-and-replacing-3dscatter-and-3dsurface-plots-in-qt-c) question in case anyone gives an answer months after I stopped working on this.

Here is the header:

#include <QDialog>
#include <QtWidgets/QLabel>
#include <QtWidgets/QVBoxLayout>
#include <QtDataVisualization/Q3DScatter>
#include <QtDataVisualization/Q3DSurface>

namespace QtD = QtDataVisualization;

class ViewerTest : public QDialog{
private:
QPushButton *mHide,*mNewScat,*mNewSurf;

QVBoxLayout *m3DLayoutHolder;
QHBoxLayout *mButtonsLayout;
QVBoxLayout *mMainLayout;

QWidget *m3DImageHolder;

QtD::Q3DScatter *m3DGraph;
QtD::Q3DSurface *m3DGraphSurface;
public:
enum TYPE{HIDE_BOTH,SCATTER,SURF};

ViewerTest();
void buildScatter(){manageGraphs(TYPE::SCATTER);};
void buildSurface(){manageGraphs(TYPE::SURF);};
void hideAll(){manageGraphs();};
void build(TYPE type);
void manageGraphs(TYPE type=HIDE_BOTH); //Shows or hide graphs
};
Here is the .cpp

#include "test3DvisuData.h"

#include <QPushButton>
#include <iostream>
#include <QtDataVisualization/qcustom3dlabel.h>
#include <QtDataVisualization/q3dscatter.h>
#include <QtDataVisualization/q3dsurface.h>
#include <QtDataVisualization/QCustom3DVolume>
#include <QtDataVisualization/Q3DInputHandler>
#include <QtDataVisualization/QHeightMapSurfaceDataProxy>
#include <QApplication>

#define PROBLEM true
/// Short class for debugging objects creation
template<typename T> class GraphTest: public T{
const int mLabel;
static int label;
std::string name;
public:
GraphTest(const std::string & in):T(),mLabel(label++),name(in){
std::cout<<" ctor "<< name <<mLabel<< std::endl;
};
~GraphTest(){
std::cout<<" ~dtor "<< name << mLabel<<std::endl;
};
};

template <typename T>int GraphTest<T>::label = 0;


ViewerTest::ViewerTest():m3DImageHolder(nullptr),m 3DGraph(nullptr),m3DGraphSurface(nullptr){
mHide = new QPushButton("hide",this);
mNewScat = new QPushButton("scat",this);
mNewSurf = new QPushButton("surf",this);

m3DLayoutHolder = new QVBoxLayout();
mButtonsLayout = new QHBoxLayout();
mMainLayout = new QVBoxLayout(this);
setLayout(mMainLayout);
mMainLayout->addLayout(mButtonsLayout);
mMainLayout->addLayout(m3DLayoutHolder);

mButtonsLayout->addWidget(mHide);
mButtonsLayout->addWidget(mNewScat);
mButtonsLayout->addWidget(mNewSurf);

connect(mNewSurf,&QPushButton::released,this,&ViewerTest::buildSurface);
connect(mNewScat,&QPushButton::released,this,&ViewerTest::buildScatter);
connect(mHide,&QPushButton::released,this,&ViewerTest::hideAll);

}

///This part is just the data construction

void ViewerTest::build(TYPE type) {
if(type==SCATTER) {
m3DGraph = new /*QtD::Q3DScatter();*/GraphTest<QtD::Q3DScatter>("inData3D");
m3DGraph->scene()->activeCamera()->setCameraPreset( QtD::Q3DCamera::CameraPresetIsometricRight );
m3DGraph->setOrthoProjection(true);
QtD::QCustom3DVolume *vol = new QtD::QCustom3DVolume;
vol->setScaling(
QVector3D(m3DGraph->axisX()->max() - m3DGraph->axisX()->min(),
(m3DGraph->axisY()->max() - m3DGraph->axisY()->min()) * 1.0f,
m3DGraph->axisZ()->max() - m3DGraph->axisZ()->min()));
vol->setScalingAbsolute(false);

// Build the data
unsigned int height(20),width(20),depth(20);
QVector<uchar> *vectorData = new QVector<uchar>(height * width * depth * 4, 0);
vol->setTextureWidth(width);vol->setTextureHeight(height);vol->setTextureDepth(depth);

vol->setTextureFormat(QImage::Format_ARGB32);
for (int z = 0; z < height; z++) {
for (int y = 0; y < depth; y++) {
for (int x = 0; x < width; x++) {
for( int c = 0; c<4; c++){
(*vectorData)[(x + width * z + width * height * y) * 4 + c] =
c==0?x*5:(c==1?y*5:(c==2?z*5:20));
}
}
}
}
vol->setTextureData(vectorData);
m3DGraph->addCustomItem(vol);
}

///// ------------------
else if(type==SURF){
QImage image = QImage(20,20,QImage::Format_Grayscale8);
for(uint x=0; x<20; x++){
for(uint y=0; y<20; y++){
image.setPixelColor(x,y,QColor(x*y));
}
}
QtD::QHeightMapSurfaceDataProxy *proxy = new QtD::QHeightMapSurfaceDataProxy(image);
QtD::QSurface3DSeries *serie = new QtD::QSurface3DSeries(proxy);
serie->setDrawMode(QtD::QSurface3DSeries::DrawSurface);

m3DGraphSurface = new GraphTest<QtD::Q3DSurface>("inData3DSurface");
m3DGraphSurface->addSeries(serie);
m3DGraphSurface->seriesList().at(0)->setColorStyle( QtD::Q3DTheme::ColorStyleRangeGradient );

}

}

and here is the problematic function

void ViewerTest::manageGraphs(TYPE type) {
#if PROBLEM
build(type); //---> no error thrown, but graph don't display
#endif

// delete previous
m3DLayoutHolder->removeWidget(m3DImageHolder);
delete m3DImageHolder;
m3DImageHolder= nullptr;

#if PROBLEM
#else
build(type); ///---> display works perfectly
#endif

if(type == SCATTER) {
m3DImageHolder = QWidget::createWindowContainer(m3DGraph, this);
} else if(type==SURF){
m3DImageHolder = QWidget::createWindowContainer(m3DGraphSurface,thi s);
}
if(type != TYPE::HIDE_BOTH) {
m3DLayoutHolder->addWidget(m3DImageHolder);
m3DImageHolder->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}

}

// Just a Widget with two buttons (hide/new)
// OnClick, load a new3D dataset

int main(int argc, char** argv){
QApplication qApplication( argc, argv);

ViewerTest viewerTest;
return viewerTest.exec();
}


If you have any explication of how could I get this to work...? Big thanks in advance, cheers !

d_stranz
9th July 2020, 16:59
With just a quick read, the only thing that sticks out is this in line 8 of your last code block:


delete m3DImageHolder;

instead of:


m3DImageHolder->deleteLater();

Calling delete on a QWidget instance that may still be in use (even though in the previous statement you removed it from what is presumably a parent object) does not allow the Qt event loop to run and process any events associated with either removing the instance or deleting it because delete immediately invalidates the instance and its contents. Calling QObject::deleteLater() schedules the instance for deletion during a future cycle through the event loop, but also allows Qt to first process any events the removal causes using the still-valid instance.

Stophe
13th July 2020, 11:58
With just a quick read, the only thing that sticks out is this in line 8 of your last code block:


m3DImageHolder->deleteLater();



Thanks for this advice ! I'll be carefull about that now.
I've applied this patch, but unfortunately... I still get the same error.
I changed to

if(m3DImageHolder) {
m3DLayoutHolder->removeWidget(m3DImageHolder);
m3DImageHolder->deleteLater();
m3DImageHolder = nullptr;
}