class CrossProfile2D : public QQuickVtkItem { Q_OBJECT public: ~CrossProfile2D(); vtkUserData initializeVTK(vtkRenderWindow *renderWindow) override; struct MyVtkData : vtkObject { static MyVtkData* New(); vtkTypeMacro(MyVtkData, vtkObject); vtkSmartPointer view = vtkSmartPointer::New(); vtkSmartPointer chart = vtkSmartPointer::New(); // ——— Not Valid Samples Related VTK Items ——————————————— vtkSmartPointer tableNull = vtkSmartPointer::New(); vtkSmartPointer xArrayNull = vtkSmartPointer::New(); vtkSmartPointer yArrayNull = vtkSmartPointer::New(); vtkSmartPointer plotNull = vtkSmartPointer::New(); // ——— Ping 0 Related VTK Items ——————————————— vtkSmartPointer table0 = vtkSmartPointer::New(); vtkSmartPointer xArray0 = vtkSmartPointer::New(); vtkSmartPointer yArray0 = vtkSmartPointer::New(); vtkSmartPointer plot0 = vtkSmartPointer::New(); // ——— Ping 1 Related VTK Items ——————————————— vtkSmartPointer table1 = vtkSmartPointer::New(); vtkSmartPointer xArray1 = vtkSmartPointer::New(); vtkSmartPointer yArray1 = vtkSmartPointer::New(); vtkSmartPointer plot1 = vtkSmartPointer::New(); }; private: // VTK Data structure — SmartPointer keeps MyVtkData alive during destructor vtkSmartPointer m_vtkData; ... }; #endif // CROSS_PROFILE_2D_H #include "CrossProfile2D.h" vtkStandardNewMacro(CrossProfile2D::MyVtkData); QQuickVtkItem::vtkUserData CrossProfile2D::initializeVTK(vtkRenderWindow* renderWindow) { connect(&RenderCoordinator::instance(), &RenderCoordinator::dataAvailable_Bathymetry, this, &CrossProfile2D::onPingReceived, Qt::UniqueConnection); m_vtkData = vtkSmartPointer::New(); // Setup view with the existing render window renderWindow->SetMultiSamples(0); m_vtkData->view->SetRenderWindow(renderWindow); // --- Background / renderer styling --- m_vtkData->view->GetRenderer()->SetBackground( m_settings.backgroundColor[0], m_settings.backgroundColor[1], m_settings.backgroundColor[2] ); // Setup chart m_vtkData->view->GetScene()->AddItem(m_vtkData->chart); // Configure axis labels m_vtkData->chart->GetAxis(vtkAxis::BOTTOM)->SetTitle(" "); m_vtkData->chart->GetAxis(vtkAxis::LEFT)->SetTitle(" "); m_vtkData->chart->SetShowLegend(false); // Set fixed ranges for X and Y axes AND disable auto-range m_vtkData->chart->GetAxis(vtkAxis::BOTTOM)->SetRange(m_settings.range2DValues[0], m_settings.range2DValues[1]); m_vtkData->chart->GetAxis(vtkAxis::BOTTOM)->SetBehavior(vtkAxis::FIXED); m_vtkData->chart->GetAxis(vtkAxis::LEFT)->SetRange(m_settings.rangeYaxis[0], m_settings.rangeYaxis[1]); m_vtkData->chart->GetAxis(vtkAxis::LEFT)->SetBehavior(vtkAxis::FIXED); m_vtkData->chart->SetAutoAxes(false); // --- Grid & axis styling --- vtkAxis* bottom = m_vtkData->chart->GetAxis(vtkAxis::BOTTOM); vtkAxis* left = m_vtkData->chart->GetAxis(vtkAxis::LEFT); // Enable grid lines bottom->SetGridVisible(true); left->SetGridVisible(true); // Grid pen color and width (RGB 0-255) bottom->GetGridPen()->SetColor(m_settings.gridColor[0], m_settings.gridColor[1], m_settings.gridColor[2]); bottom->GetGridPen()->SetWidth(1.0); left->GetGridPen()->SetColor(m_settings.gridColor[0], m_settings.gridColor[1], m_settings.gridColor[2]); left->GetGridPen()->SetWidth(1.0); // Axis color for numbers bottom->GetLabelProperties()->SetColor(1.0, 1.0, 1.0); left->GetLabelProperties()->SetColor(1.0, 1.0, 1.0); // --- Setup VTK data structures for plotting (empty for now) --- // Setup arrays for ping 0 m_vtkData->xArrayNull->SetName("XNull"); m_vtkData->yArrayNull->SetName("YNull"); m_vtkData->tableNull->AddColumn(m_vtkData->xArrayNull); m_vtkData->tableNull->AddColumn(m_vtkData->yArrayNull); // Setup arrays for ping 0 m_vtkData->xArray0->SetName("X0"); m_vtkData->yArray0->SetName("Y0"); m_vtkData->table0->AddColumn(m_vtkData->xArray0); m_vtkData->table0->AddColumn(m_vtkData->yArray0); // Setup arrays for ping 1 m_vtkData->xArray1->SetName("X1"); m_vtkData->yArray1->SetName("Y1"); m_vtkData->table1->AddColumn(m_vtkData->xArray1); m_vtkData->table1->AddColumn(m_vtkData->yArray1); } /* This is the main loop where the VTK plot and tables are being recreated. The tables are cleared and repopulated with the new data, and then the plots are cleared and recreated with the updated tables. This ensures that the VTK scene is always in sync with the latest data received from the backend. */ void CrossProfile2D::plotCross(const CrossProfileArrays &readyData, vtkRenderWindow* renderWindow, vtkUserData userData) { auto vtk = MyVtkData::SafeDownCast(userData.Get()); if (!vtk) return; // ================ Update VTK Tables ================ vtk->xArrayNull->Reset(); vtk->yArrayNull->Reset(); vtk->xArrayNull->SetNumberOfTuples(readyData.XNull.size()); vtk->yArrayNull->SetNumberOfTuples(readyData.YNull.size()); for (int i = 0; i < readyData.XNull.size(); ++i) { vtk->xArrayNull->SetValue(i, readyData.XNull[i]); vtk->yArrayNull->SetValue(i, readyData.YNull[i]); } vtk->tableNull->SetNumberOfRows(vtk->xArrayNull->GetNumberOfTuples()); vtk->tableNull->Modified(); vtk->xArray0->Reset(); vtk->yArray0->Reset(); vtk->xArray0->SetNumberOfTuples(readyData.X0.size()); vtk->yArray0->SetNumberOfTuples(readyData.Y0.size()); for (int i = 0; i < readyData.X0.size(); ++i) { vtk->xArray0->SetValue(i, readyData.X0[i]); vtk->yArray0->SetValue(i, readyData.Y0[i]); } vtk->table0->SetNumberOfRows(vtk->xArray0->GetNumberOfTuples()); vtk->table0->Modified(); vtk->xArray1->Reset(); vtk->yArray1->Reset(); vtk->xArray1->SetNumberOfTuples(readyData.X1.size()); vtk->yArray1->SetNumberOfTuples(readyData.Y1.size()); for (int i = 0; i < readyData.X1.size(); ++i) { vtk->xArray1->SetValue(i, readyData.X1[i]); vtk->yArray1->SetValue(i, readyData.Y1[i]); } vtk->table1->SetNumberOfRows(vtk->xArray1->GetNumberOfTuples()); vtk->table1->Modified(); // ============== Update Plots ============== // ClearPlots releases all internal scene references atomically, // avoiding the ref-count leak that the RemovePlot loop causes. vtk->chart->ClearPlots(); // ============== Recreate Plots ============== vtk->plotNull = vtkPlotPoints::SafeDownCast(vtk->chart->AddPlot(m_lineStyle)); if (vtk->plotNull) { vtk->plotNull->SetInputData(vtk->tableNull, 0, 1); vtk->plotNull->SetMarkerStyle(m_markerStyle); vtk->plotNull->SetMarkerSize(m_pointSize); vtk->plotNull->SetColor(m_notValidColor[0], m_notValidColor[1], m_notValidColor[2], m_notValidColor[3]); } // Recreate plot for ping 0 vtk->plot0 = vtkPlotPoints::SafeDownCast(vtk->chart->AddPlot(m_lineStyle)); if (vtk->plot0) { vtk->plot0->SetInputData(vtk->table0, 0, 1); vtk->plot0->SetMarkerStyle(m_markerStyle); vtk->plot0->SetMarkerSize(m_pointSize); vtk->plot0->SetColor(m_tdcr01Color[0], m_tdcr01Color[1], m_tdcr01Color[2], m_tdcr01Color[3]); } // Recreate plot for ping 1 vtk->plot1 = vtkPlotPoints::SafeDownCast(vtk->chart->AddPlot(m_lineStyle)); if (vtk->plot1) { vtk->plot1->SetInputData(vtk->table1, 0, 1); vtk->plot1->SetMarkerStyle(m_markerStyle); vtk->plot1->SetMarkerSize(m_pointSize); vtk->plot1->SetColor(m_tdcr02Color[0], m_tdcr02Color[1], m_tdcr02Color[2], m_tdcr02Color[3]); } // ... }