Files
ResInsight/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimRftCorrelationReportPlot.cpp
T
Magne Sjaastad 07b82a52a9 Support MD and TVD depth filtering in RFT cross plot and tornado plot
Propagates the Depth Unit selection from RimRftCorrelationReportPlot to the parameter cross plot and tornado plot depth-range filters, not just the RFT pressure profile sub-plot.

- RimWellLogRftCurve::rftCurveDepthValues now takes a RiaDefines::DepthType parameter and fetches the TVD channel directly when TVD is requested (falling back to MD if unavailable), instead of always preferring MD.
- RimParameterRftCrossPlot and RimRftTornadoPlot gain a depth type field (driven by the parent report plot, hidden in their own UI) and computeMeanPressurePerCase() takes the depth type so the depth-range filter operates on the selected unit.
- Axis labels, group titles, and auto-generated plot titles now show MD/TVD instead of being hardcoded to MD.

Stop silently substituting TVD for MD (or vice versa) in RFT correlation depth filter

rftCurveDepthValues() previously fell back to the other depth type when the requested one (MD or TVD) had no data, causing MD and TVD selections to silently produce identical results. Now it strictly returns only the requested depth type, returning an empty vector if unavailable. This naturally excludes such cases from the correlation cross plot and tornado plot, showing a blank plot instead of misleading data.
2026-10-06 11:18:06 +02:00

585 lines
23 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimRftCorrelationReportPlot.h"
#include "RimParameterRftCrossPlot.h"
#include "RimRftTornadoPlot.h"
#include "RimWellLogTrack.h"
#include "RimWellRftPlot.h"
#include "RiuInterfaceToViewWindow.h"
#include "RiuPlotWidget.h"
#include "RiuQwtPlotWidget.h"
#include "DockAreaTitleBar.h"
#include "DockAreaWidget.h"
#include "DockManager.h"
#include "DockWidget.h"
#include "cafPdmOptionItemInfo.h"
#include "cafPdmUiCheckBoxEditor.h"
#include "cafPdmUiTreeOrdering.h"
#include "cafSelectionManager.h"
#include <QContextMenuEvent>
#include <QFrame>
#include <QSettings>
#include <QVBoxLayout>
static const char* RFT_DOCK_LAYOUT_REGISTRY_KEY = "RftCorrelationReportPlot/defaultDockLayout";
//--------------------------------------------------------------------------------------------------
/// Thin wrapper that implements RiuInterfaceToViewWindow for the dock manager frame.
//--------------------------------------------------------------------------------------------------
class RiuRftCorrelationReportPlotWidget : public QFrame, public RiuInterfaceToViewWindow
{
public:
RiuRftCorrelationReportPlotWidget( RimViewWindow* viewWindow, QWidget* parent = nullptr )
: QFrame( parent )
, m_viewWindow( viewWindow )
{
setLayout( new QVBoxLayout );
layout()->setContentsMargins( 0, 0, 0, 0 );
layout()->setSpacing( 0 );
}
RimViewWindow* ownerViewWindow() const override { return m_viewWindow; }
private:
RimViewWindow* m_viewWindow;
};
//--------------------------------------------------------------------------------------------------
/// Sets the report plot as the CAF-selected item whenever a context menu fires
/// anywhere inside the dock manager.
//--------------------------------------------------------------------------------------------------
class RftSelectionFixerOnContextMenu : public QObject
{
public:
RftSelectionFixerOnContextMenu( caf::PdmObject* item, QObject* parent )
: QObject( parent )
, m_item( item )
{
}
bool eventFilter( QObject*, QEvent* event ) override
{
if ( event->type() == QEvent::ContextMenu ) caf::SelectionManager::instance()->setSelectedItem( m_item );
return false;
}
private:
caf::PdmObject* m_item;
};
//==================================================================================================
//
//
//
//==================================================================================================
CAF_PDM_SOURCE_INIT( RimRftCorrelationReportPlot, "RftCorrelationReportPlot" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimRftCorrelationReportPlot::RimRftCorrelationReportPlot()
{
CAF_PDM_InitObject( "RFT Correlation Report Plot", ":/CorrelationReportPlot16x16.png" );
setDeletable( true );
CAF_PDM_InitFieldNoDefault( &m_name, "PlotWindowTitle", "Title" );
m_name.registerGetMethod( this, &RimRftCorrelationReportPlot::createDescription );
CAF_PDM_InitFieldNoDefault( &m_wellRftPlot, "WellRftPlot", "RFT Plot" );
CAF_PDM_InitFieldNoDefault( &m_parameterRftCrossPlot, "ParameterRftCrossPlot", "Cross Plot" );
CAF_PDM_InitFieldNoDefault( &m_tornadoPlot, "TornadoPlot", "Tornado Plot" );
CAF_PDM_InitField( &m_depthType, "DepthType", caf::AppEnum<RiaDefines::DepthType>( RiaDefines::DepthType::TRUE_VERTICAL_DEPTH ), "Depth Unit" );
CAF_PDM_InitField( &m_showDockTitleBars, "ShowDockTitleBars", false, "Show Title Bars" );
caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_showDockTitleBars );
CAF_PDM_InitField( &m_dockState, "DockState", QString(), "Dock State" );
m_dockState.uiCapability()->setUiHidden( true );
dockAsPlotWindow();
m_showWindow = true;
m_showPlotLegends = false;
m_wellRftPlot = new RimWellRftPlot;
m_wellRftPlot->detachFromDockPermanently();
m_wellRftPlot->setShowWindow( true );
m_parameterRftCrossPlot = new RimParameterRftCrossPlot;
m_tornadoPlot = new RimRftTornadoPlot;
m_tornadoPlot->setParameterSelectedCallback( [this]( const QString& paramName ) { onTornadoParameterSelected( paramName ); } );
applyDepthTypeToSubPlots();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimRftCorrelationReportPlot::~RimRftCorrelationReportPlot()
{
removeWindowFromDock();
cleanupBeforeClose();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QWidget* RimRftCorrelationReportPlot::viewWidget()
{
return m_viewWidget;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimRftCorrelationReportPlot::description() const
{
return createDescription();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmFieldHandle* RimRftCorrelationReportPlot::userDescriptionField()
{
return &m_name;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellRftPlot* RimRftCorrelationReportPlot::wellRftPlot() const
{
return m_wellRftPlot();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimParameterRftCrossPlot* RimRftCorrelationReportPlot::crossPlot() const
{
return m_parameterRftCrossPlot();
}
//--------------------------------------------------------------------------------------------------
/// Initialize the owned RimWellRftPlot from the source plot's selected data sources.
/// We keep the fresh (curve-free) child plot and call initializeDataSources(source) so
/// syncCurvesFromUiSelection builds curves from scratch without touching unresolved copies.
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::initializeFromSourcePlot( RimWellRftPlot* source )
{
if ( !source ) return;
applyDepthTypeToSubPlots();
m_wellRftPlot->setSimWellOrWellPathName( source->simWellOrWellPathName() );
// A fresh RimWellRftPlot has no tracks; syncCurvesFromUiSelection exits early without one.
// Guard against duplicate track creation if this is called more than once.
if ( m_wellRftPlot->plotCount() == 0 )
{
auto* track = new RimWellLogTrack();
m_wellRftPlot->addPlot( track );
track->setDescription( QString( "Track %1" ).arg( m_wellRftPlot->plotCount() ) );
}
m_wellRftPlot->initializeDataSources( source );
// The correlation report operates on a single time step; trim any extras that
// initializeDataSources may have preselected when only a few were available.
auto selectedTimeSteps = m_wellRftPlot->selectedTimeSteps();
if ( selectedTimeSteps.size() > 1 )
{
m_wellRftPlot->setSelectedTimeSteps( { selectedTimeSteps.front() } );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimRftCorrelationReportPlot::createDescription() const
{
if ( m_parameterRftCrossPlot() )
{
const QString wellName = m_parameterRftCrossPlot()->wellName();
const QString param = m_parameterRftCrossPlot()->ensembleParameter();
if ( !wellName.isEmpty() && !param.isEmpty() )
{
return QString( "RFT Correlation: %1 vs %2" ).arg( param ).arg( wellName );
}
}
return "RFT Correlation Report";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::recreatePlotWidgets()
{
CAF_ASSERT( m_dockManager );
m_wellRftPlot->createPlotWidget( m_dockManager );
m_tornadoPlot->createPlotWidget( m_dockManager );
m_parameterRftCrossPlot->createPlotWidget( m_dockManager );
// Context menu fixer — ensures this report is selected in CAF on any right-click
delete m_contextMenuFilter;
m_contextMenuFilter = new RftSelectionFixerOnContextMenu( this, this );
if ( auto* w = m_wellRftPlot->viewWidget() ) w->installEventFilter( m_contextMenuFilter );
if ( auto* w = m_tornadoPlot->viewer() ) w->installEventFilter( m_contextMenuFilter );
if ( auto* w = m_parameterRftCrossPlot->viewer() ) w->installEventFilter( m_contextMenuFilter );
auto makeDockWidget = [&]( const QString& title, RimPlotWindow* plot, QWidget* widget ) -> ads::CDockWidget*
{
auto* dock = new ads::CDockWidget( title, m_dockManager );
dock->setWidget( widget, ads::CDockWidget::ForceNoScrollArea );
connect( dock,
&ads::CDockWidget::closed,
this,
[this, plot]()
{
plot->setShowWindow( false );
updateConnectedEditors();
} );
return dock;
};
m_rftDockWidget = makeDockWidget( "RFT Plot", m_wellRftPlot(), m_wellRftPlot->viewWidget() );
m_correlationDockWidget = makeDockWidget( "Tornado Plot", m_tornadoPlot(), m_tornadoPlot->viewer() );
m_crossPlotDockWidget = makeDockWidget( "Cross Plot", m_parameterRftCrossPlot(), m_parameterRftCrossPlot->viewer() );
// Restore saved dock state or apply hard-coded default layout
QByteArray stateToRestore;
if ( !m_dockState().isEmpty() )
{
stateToRestore = QByteArray::fromBase64( m_dockState().toLatin1() );
}
else
{
QSettings settings;
QVariant v = settings.value( RFT_DOCK_LAYOUT_REGISTRY_KEY );
if ( v.isValid() ) stateToRestore = v.toByteArray();
}
if ( !stateToRestore.isEmpty() )
{
m_dockManager->addDockWidget( ads::LeftDockWidgetArea, m_rftDockWidget );
auto* rightArea = m_dockManager->addDockWidget( ads::RightDockWidgetArea, m_correlationDockWidget );
m_dockManager->addDockWidget( ads::BottomDockWidgetArea, m_crossPlotDockWidget, rightArea );
m_dockManager->restoreState( stateToRestore, 1 );
}
else
{
// Default: RFT plot on the left, tornado top-right, cross plot bottom-right
m_dockManager->addDockWidget( ads::LeftDockWidgetArea, m_rftDockWidget );
auto* rightArea = m_dockManager->addDockWidget( ads::RightDockWidgetArea, m_correlationDockWidget );
m_dockManager->addDockWidget( ads::BottomDockWidgetArea, m_crossPlotDockWidget, rightArea );
}
m_rftDockWidget->toggleView( m_wellRftPlot->showWindow() );
m_correlationDockWidget->toggleView( m_tornadoPlot->showWindow() );
m_crossPlotDockWidget->toggleView( m_parameterRftCrossPlot->showWindow() );
updateDockTitleBarsVisibility();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::cleanupBeforeClose()
{
// Detach and delete legend curves before the QwtPlot inside the track widget is destroyed.
// QwtPlot has autoDelete=true, so any curves still attached when it is deleted are freed by QWT
// — leaving m_legendPlotCurves with dangling pointers on the next loadDataAndUpdate().
if ( m_wellRftPlot() ) m_wellRftPlot->cleanupLegendCurves();
if ( m_tornadoPlot() ) m_tornadoPlot->detachAllCurves();
if ( m_parameterRftCrossPlot() ) m_parameterRftCrossPlot->detachAllCurves();
m_rftDockWidget = nullptr;
m_correlationDockWidget = nullptr;
m_crossPlotDockWidget = nullptr;
if ( m_dockManager )
{
delete m_dockManager;
m_dockManager = nullptr;
}
if ( m_viewWidget )
{
m_viewWidget->setParent( nullptr );
delete m_viewWidget;
m_viewWidget = nullptr;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::setupBeforeSave()
{
if ( m_dockManager )
{
m_dockState = QString::fromLatin1( m_dockManager->saveState( 1 ).toBase64() );
}
}
//--------------------------------------------------------------------------------------------------
/// Re-apply dock detachment after project load, since the PDM factory bypasses the constructor.
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::initAfterRead()
{
if ( m_wellRftPlot() )
{
m_wellRftPlot->detachFromDockPermanently();
m_wellRftPlot->setShowWindow( true );
}
applyDepthTypeToSubPlots();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::doRenderWindowContent( QPaintDevice* paintDevice )
{
if ( m_viewWidget ) m_viewWidget->render( paintDevice );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QWidget* RimRftCorrelationReportPlot::createViewWidget( QWidget* mainWindowParent )
{
auto* wrapper = new RiuRftCorrelationReportPlotWidget( this, mainWindowParent );
m_viewWidget = wrapper;
m_dockManager = new ads::CDockManager( wrapper );
m_dockManager->setStyleSheet( "ads--CDockSplitter::handle { width: 2px; height: 2px; background-color: #a0a0a0; }" );
wrapper->layout()->addWidget( m_dockManager );
recreatePlotWidgets();
return m_viewWidget;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::deleteViewWidget()
{
cleanupBeforeClose();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::onLoadDataAndUpdate()
{
updateDockWindowVisibility();
if ( m_showWindow )
{
m_wellRftPlot->loadDataAndUpdate();
syncTornadoInputsFromCrossPlot();
m_tornadoPlot->loadDataAndUpdate();
m_parameterRftCrossPlot->loadDataAndUpdate();
}
updateLayout();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &m_depthType );
// Delegate cross-plot settings (ensemble, well, depth range, parameter) to the cross plot
m_parameterRftCrossPlot->uiOrdering( uiConfigName, uiOrdering );
auto* layoutGroup = uiOrdering.addNewGroup( "Dock Layout" );
layoutGroup->setCollapsedByDefault();
layoutGroup->add( &m_showDockTitleBars );
uiOrdering.skipRemainingFields( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString /*uiConfigName*/ )
{
uiTreeOrdering.add( m_wellRftPlot() );
uiTreeOrdering.add( m_tornadoPlot() );
uiTreeOrdering.add( m_parameterRftCrossPlot() );
uiTreeOrdering.skipRemainingChildren();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
if ( changedField == &m_showDockTitleBars )
{
updateDockTitleBarsVisibility();
return;
}
if ( changedField == &m_depthType )
{
applyDepthTypeToSubPlots();
}
loadDataAndUpdate();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::childFieldChangedByUi( const caf::PdmFieldHandle* changedChildField )
{
if ( m_rftDockWidget && changedChildField == &m_wellRftPlot )
m_rftDockWidget->toggleView( m_wellRftPlot->showWindow() );
else if ( m_correlationDockWidget && changedChildField == &m_tornadoPlot )
m_correlationDockWidget->toggleView( m_tornadoPlot->showWindow() );
else if ( m_crossPlotDockWidget && changedChildField == &m_parameterRftCrossPlot )
{
m_crossPlotDockWidget->toggleView( m_parameterRftCrossPlot->showWindow() );
syncCrossPlotSelectionToRftPlot();
}
updateDockTitleBarsVisibility();
loadDataAndUpdate();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::syncCrossPlotSelectionToRftPlot()
{
if ( !m_wellRftPlot() || !m_parameterRftCrossPlot() ) return;
const QString wellName = m_parameterRftCrossPlot->wellName();
const QDateTime timeStep = m_parameterRftCrossPlot->selectedTimeStep();
if ( !wellName.isEmpty() ) m_wellRftPlot->setSimWellOrWellPathName( wellName );
if ( timeStep.isValid() ) m_wellRftPlot->setSelectedTimeSteps( { timeStep } );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimRftTornadoPlot* RimRftCorrelationReportPlot::tornadoPlot() const
{
return m_tornadoPlot();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimPlot*> RimRftCorrelationReportPlot::childPlotsForTextExport() const
{
return { crossPlot(), tornadoPlot() };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::onTornadoParameterSelected( const QString& paramName )
{
if ( m_tornadoPlot() )
{
m_tornadoPlot->setSelectedParameter( paramName );
m_tornadoPlot->loadDataAndUpdate();
}
if ( m_parameterRftCrossPlot() )
{
m_parameterRftCrossPlot->setEnsembleParameter( paramName );
m_parameterRftCrossPlot->loadDataAndUpdate();
}
updateConnectedEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::syncTornadoInputsFromCrossPlot()
{
if ( !m_tornadoPlot() || !m_parameterRftCrossPlot() ) return;
m_tornadoPlot->setEnsemble( m_parameterRftCrossPlot->ensemble() );
m_tornadoPlot->setWellName( m_parameterRftCrossPlot->wellName() );
m_tornadoPlot->setTimeStep( m_parameterRftCrossPlot->selectedTimeStep() );
m_tornadoPlot->setSelectedParameter( m_parameterRftCrossPlot->ensembleParameter() );
m_tornadoPlot->setEclipseCase( m_parameterRftCrossPlot->eclipseCase() );
m_tornadoPlot->setUseDepthRange( m_parameterRftCrossPlot->useDepthRange() );
m_tornadoPlot->setDepthRange( m_parameterRftCrossPlot->depthRangeMin(), m_parameterRftCrossPlot->depthRangeMax() );
m_tornadoPlot->setDepthType( m_parameterRftCrossPlot->depthType() );
}
//--------------------------------------------------------------------------------------------------
/// Pushes the selected depth unit (MD/TVD) down to the sub plots that present a depth axis or
/// filter samples by depth.
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::applyDepthTypeToSubPlots()
{
if ( m_wellRftPlot() )
{
m_wellRftPlot->setAvailableDepthTypes( { RiaDefines::DepthType::MEASURED_DEPTH, RiaDefines::DepthType::TRUE_VERTICAL_DEPTH } );
m_wellRftPlot->setDepthType( m_depthType() );
}
if ( m_parameterRftCrossPlot() ) m_parameterRftCrossPlot->setDepthType( m_depthType() );
if ( m_tornadoPlot() ) m_tornadoPlot->setDepthType( m_depthType() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<caf::PdmOptionItemInfo> RimRftCorrelationReportPlot::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
QList<caf::PdmOptionItemInfo> options;
if ( fieldNeedingOptions == &m_depthType )
{
using DepthAppEnum = caf::AppEnum<RiaDefines::DepthType>;
for ( auto depthType : { RiaDefines::DepthType::MEASURED_DEPTH, RiaDefines::DepthType::TRUE_VERTICAL_DEPTH } )
{
options.push_back( caf::PdmOptionItemInfo( DepthAppEnum::uiText( depthType ), depthType ) );
}
}
return options;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::updateDockTitleBarsVisibility()
{
if ( !m_dockManager ) return;
for ( auto* area : m_dockManager->openedDockAreas() )
area->titleBar()->setVisible( m_showDockTitleBars() );
}