#14889: RFT Ensemble: Additional UI fixes

* #14889: Do not change main window when importing well picks
* #14889: Fix clipped bottom of plots in dock windows
* #14889: Add Create RFT Correlation Report to Ensemble Correlation Plots menu
* #14889: Show imported observed FMU RFT data as table
* #14889: Use time step from source RFT plot in RFT correlation report
* #14889: Show observed data in RFT cross plot when no RFT samples are available
* #14889: Keep RFT plot zoom when toggling formation in RFT correlation report
* #14889: Do not change main window when importing formations
* #14889: Use general delete for observed data and improve FMU RFT data name
This commit is contained in:
Magne Sjaastad authored and GitHub committed 2026-10-12 18:50:57 +02:00
1 parent fb566f112c
commit c57864df51
36 files changed
+373 -319

No files matched your search

@@ -36,6 +36,8 @@
#include "RimWellRftEnsembleCurveSet.h"
#include "RimWellRftPlot.h"
#include "cafCmdFeatureMenuBuilder.h"
CAF_PDM_SOURCE_INIT( RimCorrelationPlotCollection, "CorrelationPlotCollection" );
namespace
@@ -211,12 +213,12 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR
{
auto* report = new RimRftCorrelationReportPlot;
report->dockAsPlotWindow();
report->initializeFromSourcePlot( source );
if ( source )
auto* rftPlot = report->wellRftPlot();
if ( rftPlot )
{
report->initializeFromSourcePlot( source );
const auto ensembles = source->selectedEnsembles();
const auto ensembles = rftPlot->selectedEnsembles();
if ( !ensembles.empty() )
{
auto* ensemble = ensembles.front();
@@ -229,7 +231,7 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR
{
report->crossPlot()->setEnsembleParameter( param.name );
if ( auto* curveSet = report->wellRftPlot()->findEnsembleCurveSet( ensemble ) )
if ( auto* curveSet = rftPlot->findEnsembleCurveSet( ensemble ) )
{
curveSet->setColorByEnsembleParameter( param.name );
}
@@ -237,7 +239,8 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR
}
}
}
report->crossPlot()->setWellName( source->simWellOrWellPathName() );
const QString wellName = rftPlot->simWellOrWellPathName();
report->crossPlot()->setWellName( wellName );
// Default to filtering by formation when a formations file has data for the well
if ( auto* project = RimProject::current();
@@ -245,7 +248,7 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR
{
for ( RimWellFormationsFile* file : project->activeOilField()->wellFormationsCollection()->wellFormationsFiles() )
{
if ( file->formationsForWell( source->simWellOrWellPathName() ) )
if ( file->formationsForWell( wellName ) )
{
report->crossPlot()->setWellFormations( file );
report->crossPlot()->setFilterMode( RimRftCrossPlotTools::DepthFilterMode::ZONES );
@@ -254,7 +257,7 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR
}
}
const auto timeSteps = source->selectedTimeSteps();
const auto timeSteps = rftPlot->selectedTimeSteps();
if ( !timeSteps.empty() ) report->crossPlot()->setTimeStep( timeSteps.front() );
}
@@ -270,6 +273,19 @@ std::vector<RimRftCorrelationReportPlot*> RimCorrelationPlotCollection::rftRepor
return m_rftCorrelationReports.childrenByType();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCorrelationPlotCollection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
{
menuBuilder << "RicNewCorrelationPlotFeature";
menuBuilder << "RicNewCorrelationMatrixPlotFeature";
menuBuilder << "RicNewParameterResultCrossPlotFeature";
menuBuilder << "RicNewCorrelationReportPlotFeature";
menuBuilder.addSeparator();
menuBuilder << "RicCreateRftCorrelationReportFeature";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -81,6 +81,8 @@ public:
std::vector<RimRftCorrelationReportPlot*> rftReports() const;
private:
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
void applyFirstEnsembleFieldAddressesToPlot( RimAbstractCorrelationPlot* plot, const std::vector<QString>& quantityNames = {} );
void applyEnsembleFieldAndTimeStepToPlot( RimAbstractCorrelationPlot* plot,
RimSummaryEnsemble* ensemble,
@@ -755,7 +755,6 @@ void RimParameterRftCrossPlot::createPoints()
caf::ColorTable colorTable = RiaColorTables::categoryPaletteColors();
auto caseData = createCaseData();
if ( caseData.empty() ) return;
std::set<RimSummaryCase*> selectedSummaryCases;
auto selectedTreeViewItems = RiuDockWidgetTools::selectedItemsInTreeView( RiuDockWidgetTools::plotMainWindowDataSourceTreeName() );
@@ -806,21 +805,14 @@ void RimParameterRftCrossPlot::createPoints()
//--------------------------------------------------------------------------------------------------
void RimParameterRftCrossPlot::attachObservedPressure()
{
const auto observedPressures =
RimRftCrossPlotTools::computeObservedPressures( m_wellName(),
m_selectedTimeStep(),
depthIntervals(),
m_depthType(),
RimRftCrossPlotTools::buildAllZoneIntervals( m_wellFormations(),
m_wellName(),
m_depthType() ) );
if ( observedPressures.empty() ) return;
const auto pressures = observedPressures();
if ( pressures.empty() ) return;
auto attachLine = [this]( double value, Qt::PenStyle style, const QString& label, const QColor& color )
{ RiuRftCorrelationPlotTools::attachHorizontalLine( m_plotWidget, value, style, label, color ); };
bool isFirst = true;
for ( const auto& observed : observedPressures )
for ( const auto& observed : pressures )
{
// Use the formation color when known; otherwise fall back to black lines and no shaded band
std::optional<QColor> zoneColor;
@@ -850,6 +842,20 @@ void RimParameterRftCrossPlot::attachObservedPressure()
}
}
//--------------------------------------------------------------------------------------------------
/// Shared by drawing and range computation so the axis range matches the drawn zone averages
//--------------------------------------------------------------------------------------------------
std::vector<RimRftCrossPlotTools::ObservedPressure> RimParameterRftCrossPlot::observedPressures() const
{
return RimRftCrossPlotTools::computeObservedPressures( m_wellName(),
m_selectedTimeStep(),
depthIntervals(),
m_depthType(),
RimRftCrossPlotTools::buildAllZoneIntervals( m_wellFormations(),
m_wellName(),
m_depthType() ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -895,25 +901,35 @@ void RimParameterRftCrossPlot::updateValueRanges()
yMax = std::max( yMax, pressureValue );
}
for ( const auto& observed :
RimRftCrossPlotTools::computeObservedPressures( m_wellName(), m_selectedTimeStep(), depthIntervals(), m_depthType() ) )
for ( const auto& observed : observedPressures() )
{
yMin = std::min( yMin, observed.rangeMin );
yMax = std::max( yMax, observed.rangeMax );
}
if ( xMin == std::numeric_limits<double>::infinity() )
// Without RFT samples, use the parameter range so the X axis does not keep the range of a previous time step
if ( xMin == std::numeric_limits<double>::infinity() && m_ensemble() && !m_ensembleParameter().isEmpty() )
{
m_xValueRange = std::nullopt;
m_yValueRange = std::nullopt;
return;
const RigEnsembleParameter parameter = m_ensemble->ensembleParameter( m_ensembleParameter );
if ( parameter.isNumeric() && parameter.isValid() )
{
xMin = parameter.minValue;
xMax = parameter.maxValue;
}
}
const double xRange = xMax - xMin;
const double yRange = yMax - yMin;
// Observed data is shown even when there are no RFT samples, so the ranges are computed independently
auto paddedRange = []( double minValue, double maxValue ) -> std::optional<std::pair<double, double>>
{
if ( minValue == std::numeric_limits<double>::infinity() ) return std::nullopt;
m_xValueRange = std::make_pair( xMin - xRange * 0.1, xMax + xRange * 0.1 );
m_yValueRange = std::make_pair( yMin - yRange * 0.1, yMax + yRange * 0.1 );
const double range = maxValue - minValue;
const double padding = range > 0.0 ? range * 0.1 : std::max( std::abs( minValue ) * 0.01, 1.0 );
return std::make_pair( minValue - padding, maxValue + padding );
};
m_xValueRange = paddedRange( xMin, xMax );
m_yValueRange = paddedRange( yMin, yMax );
}
//--------------------------------------------------------------------------------------------------
@@ -132,7 +132,8 @@ private:
void cleanupBeforeClose();
RimSummaryCase* findClosestCase( const QPoint& canvasPos );
std::vector<RimRftCrossPlotTools::DepthInterval> depthIntervals() const;
std::vector<RimRftCrossPlotTools::DepthInterval> depthIntervals() const;
std::vector<RimRftCrossPlotTools::ObservedPressure> observedPressures() const;
private:
caf::PdmPtrField<RimSummaryEnsemble*> m_ensemble;
@@ -25,6 +25,7 @@
#include "RimRftCrossPlotTools.h"
#include "RimRftTornadoPlot.h"
#include "RimWellLogTrack.h"
#include "RimWellPlotTools.h"
#include "RimWellRftEnsembleCurveSet.h"
#include "RimWellRftPlot.h"
@@ -207,14 +208,13 @@ RimParameterRftCrossPlot* RimRftCorrelationReportPlot::crossPlot() const
/// 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.
/// Without a source plot, the first well with ensemble RFT data is used with all available data sources.
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::initializeFromSourcePlot( RimWellRftPlot* source )
{
if ( !source ) return;
applyDepthTypeToSubPlots();
m_wellRftPlot->setSimWellOrWellPathName( source->simWellOrWellPathName() );
m_wellRftPlot->setSimWellOrWellPathName( source ? source->simWellOrWellPathName() : RimWellPlotTools::firstWellNameWithEnsembleRftData() );
// A fresh RimWellRftPlot has no tracks; syncCurvesFromUiSelection exits early without one.
// Guard against duplicate track creation if this is called more than once.
@@ -227,10 +227,14 @@ void RimRftCorrelationReportPlot::initializeFromSourcePlot( RimWellRftPlot* sour
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.
// The correlation report operates on a single time step; prefer the time step selected in the
// source plot, and trim any extras that initializeDataSources may have preselected.
auto selectedTimeSteps = m_wellRftPlot->selectedTimeSteps();
if ( selectedTimeSteps.size() > 1 )
if ( source && !source->selectedTimeSteps().empty() )
{
m_wellRftPlot->setSelectedTimeSteps( { source->selectedTimeSteps().front() } );
}
else if ( selectedTimeSteps.size() > 1 )
{
m_wellRftPlot->setSelectedTimeSteps( { selectedTimeSteps.front() } );
}
@@ -706,7 +710,12 @@ void RimRftCorrelationReportPlot::onRftTrackDepthClicked( RimWellLogTrack* track
m_parameterRftCrossPlot->setFilterMode( RimRftCrossPlotTools::DepthFilterMode::ZONES );
m_parameterRftCrossPlot->setSelectedZones( zones );
loadDataAndUpdate();
// The zone filter does not affect the RFT curves; skip reloading the RFT plot to keep its zoom
updateSelectedZoneHighlight();
syncTornadoInputsFromCrossPlot();
m_tornadoPlot->loadDataAndUpdate();
m_parameterRftCrossPlot->loadDataAndUpdate();
updateConnectedEditors();
return;
}