mirror of
https://github.com/OPM/ResInsight.git
synced 2026-11-03 04:40:49 -06:00
#12276 Add RFT correlation report plot with cross-plot and tornado plot
* Fix defensive guards and TVD interpolation Guard against out-of-bounds IJK and invalid cells in RifReaderRftInterface. Guard against null eclCase in RimDataSourceForRftPlt::address. Guard against null plot widgets in RiuMultiPlotPage. Fix interpolateMdFromTvd corrupting valid entries after infinity TVD values by skipping infinity entries in the first pass and tracking only the last two valid MD values for the startMD adjustment. * Add RiuQwtCurveSelectorFilter for click-to-select realization in Qwt plots New shared event filter class that installs on a QwtPlot canvas and calls a user-supplied callback with the closest data point on each click. Migrate the local CurveSelectorFilter in RimParameterResultCrossPlot to use the new shared class. Add Z_HIGHLIGHTED_CURVE to ZIndex enum and replace all raw z-order integers in RiuQwtPlotCurveDefines with named constants. * Add RFT correlation report plot with cross-plot and tornado plot New classes: - RimRftCorrelationReportPlot: composite plot showing an RFT well log track alongside a parameter cross-plot and a tornado plot sub-plot. - RimParameterRftCrossPlot: cross-plot of ensemble parameters vs RFT-derived measured depth values, with click-to-select realization support. - RimRftTornadoPlot: tornado plot with correlation-sorted parameter list and click-to-select realization support. - RicCreateRftCorrelationReportFeature: context menu command on RimWellRftPlot that creates a RimRftCorrelationReportPlot in the correlation plot collection. Wire up in RimCorrelationPlotCollection and RimContextCommandBuilder. * Add click-to-select and highlight selected realization in RFT plots Install RiuQwtCurveSelectorFilter on RimWellRftPlot canvas to support click-to-select realization. Highlight the selected realization using a contrast color (orange-red) and increased line thickness. React to both click-to-select in the plot and selection changes in the Data Sources panel. Add Z_HIGHLIGHTED_CURVE z-order and replace raw z-order integers with named constants in RimWellRftPlot. Highlight selected realization with XCROSS symbol in RimParameterRftCrossPlot via RimSummaryEnsembleTools::findSummaryCase. * RimWellRftPlot: add Create RFT Correlation Report to canvas right-click menu Override appendMenuItems in RimWellRftPlot to add RicCreateRftCorrelationReportFeature. Call appendMenuItems from RiuMultiPlotPage::contextMenuEvent so any plot type can contribute canvas menu items without modifying RiuMultiPlotPage. * RicShowPlotDataFeature: support RimRftCorrelationReportPlot Add RimRftCorrelationReportPlot to the isCommandEnabled check and expand it into its cross-plot and tornado sub-plots in onActionTriggered, matching the pattern used for RimCorrelationReportPlot.
This commit is contained in:
1 parent
6d8092cd94
commit
387b19ef62
29 files changed
+2842
-100
No files matched your search
@@ -30,8 +30,11 @@
|
|||||||
#include "RimGridCrossPlot.h"
|
#include "RimGridCrossPlot.h"
|
||||||
#include "RimGridCrossPlotCurve.h"
|
#include "RimGridCrossPlotCurve.h"
|
||||||
#include "RimParameterResultCrossPlot.h"
|
#include "RimParameterResultCrossPlot.h"
|
||||||
|
#include "RimParameterRftCrossPlot.h"
|
||||||
#include "RimPlotWindow.h"
|
#include "RimPlotWindow.h"
|
||||||
#include "RimProject.h"
|
#include "RimProject.h"
|
||||||
|
#include "RimRftCorrelationReportPlot.h"
|
||||||
|
#include "RimRftTornadoPlot.h"
|
||||||
#include "RimSummaryPlot.h"
|
#include "RimSummaryPlot.h"
|
||||||
#include "RimWellAllocationOverTimePlot.h"
|
#include "RimWellAllocationOverTimePlot.h"
|
||||||
#include "RimWellLogPlot.h"
|
#include "RimWellLogPlot.h"
|
||||||
@@ -190,7 +193,7 @@ bool RicShowPlotDataFeature::isCommandEnabled() const
|
|||||||
dynamic_cast<RimGridCrossPlot*>( plot ) || dynamic_cast<RimCustomVfpPlot*>( plot ) ||
|
dynamic_cast<RimGridCrossPlot*>( plot ) || dynamic_cast<RimCustomVfpPlot*>( plot ) ||
|
||||||
dynamic_cast<RimWellAllocationOverTimePlot*>( plot ) || dynamic_cast<RimAnalysisPlot*>( plot ) ||
|
dynamic_cast<RimWellAllocationOverTimePlot*>( plot ) || dynamic_cast<RimAnalysisPlot*>( plot ) ||
|
||||||
dynamic_cast<RimCorrelationMatrixPlot*>( plot ) || dynamic_cast<RimAbstractCorrelationPlot*>( plot ) ||
|
dynamic_cast<RimCorrelationMatrixPlot*>( plot ) || dynamic_cast<RimAbstractCorrelationPlot*>( plot ) ||
|
||||||
dynamic_cast<RimCorrelationReportPlot*>( plot ) )
|
dynamic_cast<RimCorrelationReportPlot*>( plot ) || dynamic_cast<RimRftCorrelationReportPlot*>( plot ) )
|
||||||
{
|
{
|
||||||
validPlots++;
|
validPlots++;
|
||||||
}
|
}
|
||||||
@@ -265,6 +268,13 @@ void RicShowPlotDataFeature::onActionTriggered( bool isChecked )
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( auto rftReportPlot = dynamic_cast<RimRftCorrelationReportPlot*>( plot ) )
|
||||||
|
{
|
||||||
|
rimPlots.push_back( rftReportPlot->crossPlot() );
|
||||||
|
rimPlots.push_back( rftReportPlot->correlationPlot() );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ( auto rimPlot = dynamic_cast<RimPlot*>( plot ) )
|
if ( auto rimPlot = dynamic_cast<RimPlot*>( plot ) )
|
||||||
{
|
{
|
||||||
rimPlots.push_back( rimPlot );
|
rimPlots.push_back( rimPlot );
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ set(SOURCE_GROUP_HEADER_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RicNewWellLogCalculatedCurveFeature.h
|
${CMAKE_CURRENT_LIST_DIR}/RicNewWellLogCalculatedCurveFeature.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftPlotsFeature.h
|
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftPlotsFeature.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftPlotsFeatureUi.h
|
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftPlotsFeatureUi.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftCorrelationReportFeature.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCE_GROUP_SOURCE_FILES
|
set(SOURCE_GROUP_SOURCE_FILES
|
||||||
@@ -70,6 +71,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RicNewWellLogCalculatedCurveFeature.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RicNewWellLogCalculatedCurveFeature.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftPlotsFeature.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftPlotsFeature.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftPlotsFeatureUi.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftPlotsFeatureUi.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RicCreateRftCorrelationReportFeature.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 "RicCreateRftCorrelationReportFeature.h"
|
||||||
|
|
||||||
|
#include "RimCorrelationPlotCollection.h"
|
||||||
|
#include "RimMainPlotCollection.h"
|
||||||
|
#include "RimRftCorrelationReportPlot.h"
|
||||||
|
#include "RimWellRftPlot.h"
|
||||||
|
|
||||||
|
#include "RiuPlotMainWindowTools.h"
|
||||||
|
|
||||||
|
#include "cafSelectionManager.h"
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
|
||||||
|
CAF_CMD_SOURCE_INIT( RicCreateRftCorrelationReportFeature, "RicCreateRftCorrelationReportFeature" );
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
bool RicCreateRftCorrelationReportFeature::isCommandEnabled() const
|
||||||
|
{
|
||||||
|
return dynamic_cast<RimWellRftPlot*>( caf::SelectionManager::instance()->selectedItem() ) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RicCreateRftCorrelationReportFeature::onActionTriggered( bool /*isChecked*/ )
|
||||||
|
{
|
||||||
|
auto* sourcePlot = dynamic_cast<RimWellRftPlot*>( caf::SelectionManager::instance()->selectedItem() );
|
||||||
|
if ( !sourcePlot ) return;
|
||||||
|
|
||||||
|
auto* correlationColl = RimMainPlotCollection::current()->correlationPlotCollection();
|
||||||
|
if ( !correlationColl ) return;
|
||||||
|
|
||||||
|
auto* report = correlationColl->createRftCorrelationReportPlot( sourcePlot );
|
||||||
|
if ( !report ) return;
|
||||||
|
|
||||||
|
report->loadDataAndUpdate();
|
||||||
|
correlationColl->updateConnectedEditors();
|
||||||
|
|
||||||
|
RiuPlotMainWindowTools::showPlotMainWindow();
|
||||||
|
RiuPlotMainWindowTools::onObjectAppended( report );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RicCreateRftCorrelationReportFeature::setupActionLook( QAction* actionToSetup )
|
||||||
|
{
|
||||||
|
actionToSetup->setText( "Create RFT Correlation Report" );
|
||||||
|
actionToSetup->setIcon( QIcon( ":/CorrelationReportPlot16x16.png" ) );
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cafCmdFeature.h"
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
/// Creates a RimRftCorrelationReportPlot from the context menu of a RimWellRftPlot.
|
||||||
|
//==================================================================================================
|
||||||
|
class RicCreateRftCorrelationReportFeature : public caf::CmdFeature
|
||||||
|
{
|
||||||
|
CAF_CMD_HEADER_INIT;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isCommandEnabled() const override;
|
||||||
|
void onActionTriggered( bool isChecked ) override;
|
||||||
|
void setupActionLook( QAction* actionToSetup ) override;
|
||||||
|
};
|
||||||
@@ -69,6 +69,8 @@ std::vector<double>
|
|||||||
auto cellIjk = cellIndices( wellName, timeStep );
|
auto cellIjk = cellIndices( wellName, timeStep );
|
||||||
for ( const caf::VecIjk0& ijk : cellIjk )
|
for ( const caf::VecIjk0& ijk : cellIjk )
|
||||||
{
|
{
|
||||||
|
if ( ijk.i() >= mainGrid->cellCountI() || ijk.j() >= mainGrid->cellCountJ() || ijk.k() >= mainGrid->cellCountK() ) continue;
|
||||||
|
|
||||||
auto globalCellIndex = mainGrid->cellIndexFromIJK( ijk.i(), ijk.j(), ijk.k() );
|
auto globalCellIndex = mainGrid->cellIndexFromIJK( ijk.i(), ijk.j(), ijk.k() );
|
||||||
|
|
||||||
auto avgMd = eclExtractor->averageMdForCell( globalCellIndex );
|
auto avgMd = eclExtractor->averageMdForCell( globalCellIndex );
|
||||||
@@ -80,10 +82,17 @@ std::vector<double>
|
|||||||
{
|
{
|
||||||
// The RFT cell is not part of cells intersected by well path
|
// The RFT cell is not part of cells intersected by well path
|
||||||
// Use the TVD of cell center to estimate measured depth
|
// Use the TVD of cell center to estimate measured depth
|
||||||
|
|
||||||
avgMeasuredDepthForCells.push_back( std::numeric_limits<double>::infinity() );
|
avgMeasuredDepthForCells.push_back( std::numeric_limits<double>::infinity() );
|
||||||
auto center = mainGrid->cell( globalCellIndex ).center();
|
|
||||||
tvdValuesToEstimate.push_back( -center.z() );
|
const RigCell& cell = mainGrid->cell( globalCellIndex );
|
||||||
|
if ( cell.isInvalid() )
|
||||||
|
{
|
||||||
|
tvdValuesToEstimate.push_back( std::numeric_limits<double>::infinity() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tvdValuesToEstimate.push_back( -cell.center().z() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ set(SOURCE_GROUP_HEADER_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationPlot.h
|
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationPlot.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationMatrixPlot.h
|
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationMatrixPlot.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimParameterResultCrossPlot.h
|
${CMAKE_CURRENT_LIST_DIR}/RimParameterResultCrossPlot.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RimParameterRftCrossPlot.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationPlotCollection.h
|
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationPlotCollection.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationReportPlot.h
|
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationReportPlot.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RimRftCorrelationReportPlot.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RimRftTornadoPlot.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCE_GROUP_SOURCE_FILES
|
set(SOURCE_GROUP_SOURCE_FILES
|
||||||
@@ -12,8 +15,11 @@ set(SOURCE_GROUP_SOURCE_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationPlot.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationPlot.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationMatrixPlot.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationMatrixPlot.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimParameterResultCrossPlot.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RimParameterResultCrossPlot.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RimParameterRftCrossPlot.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationPlotCollection.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationPlotCollection.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationReportPlot.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationReportPlot.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RimRftCorrelationReportPlot.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RimRftTornadoPlot.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
||||||
|
|||||||
+56
-2
@@ -24,9 +24,12 @@
|
|||||||
#include "RimCorrelationPlot.h"
|
#include "RimCorrelationPlot.h"
|
||||||
#include "RimCorrelationReportPlot.h"
|
#include "RimCorrelationReportPlot.h"
|
||||||
#include "RimParameterResultCrossPlot.h"
|
#include "RimParameterResultCrossPlot.h"
|
||||||
|
#include "RimParameterRftCrossPlot.h"
|
||||||
#include "RimProject.h"
|
#include "RimProject.h"
|
||||||
|
#include "RimRftCorrelationReportPlot.h"
|
||||||
#include "RimSummaryEnsemble.h"
|
#include "RimSummaryEnsemble.h"
|
||||||
#include "RimSummaryEnsembleTools.h"
|
#include "RimSummaryEnsembleTools.h"
|
||||||
|
#include "RimWellRftPlot.h"
|
||||||
|
|
||||||
CAF_PDM_SOURCE_INIT( RimCorrelationPlotCollection, "CorrelationPlotCollection" );
|
CAF_PDM_SOURCE_INIT( RimCorrelationPlotCollection, "CorrelationPlotCollection" );
|
||||||
|
|
||||||
@@ -39,6 +42,7 @@ RimCorrelationPlotCollection::RimCorrelationPlotCollection()
|
|||||||
|
|
||||||
CAF_PDM_InitFieldNoDefault( &m_correlationPlots, "CorrelationPlots", "Correlation Plots" );
|
CAF_PDM_InitFieldNoDefault( &m_correlationPlots, "CorrelationPlots", "Correlation Plots" );
|
||||||
CAF_PDM_InitFieldNoDefault( &m_correlationReports, "CorrelationReports", "Correlation Reports" );
|
CAF_PDM_InitFieldNoDefault( &m_correlationReports, "CorrelationReports", "Correlation Reports" );
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_rftCorrelationReports, "RftCorrelationReports", "RFT Correlation Reports" );
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -174,6 +178,52 @@ RimCorrelationReportPlot* RimCorrelationPlotCollection::createCorrelationReportP
|
|||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationReportPlot( RimWellRftPlot* source )
|
||||||
|
{
|
||||||
|
auto* report = new RimRftCorrelationReportPlot;
|
||||||
|
report->setAsPlotMdiWindow();
|
||||||
|
|
||||||
|
if ( source )
|
||||||
|
{
|
||||||
|
report->initializeFromSourcePlot( source );
|
||||||
|
|
||||||
|
const auto ensembles = source->selectedEnsembles();
|
||||||
|
if ( !ensembles.empty() )
|
||||||
|
{
|
||||||
|
auto* ensemble = ensembles.front();
|
||||||
|
report->crossPlot()->setEnsemble( ensemble );
|
||||||
|
|
||||||
|
// Pick the first numeric ensemble parameter as a default
|
||||||
|
for ( const auto& param : RimSummaryEnsembleTools::alphabeticEnsembleParameters( ensemble->allSummaryCases() ) )
|
||||||
|
{
|
||||||
|
if ( param.isNumeric() )
|
||||||
|
{
|
||||||
|
report->crossPlot()->setEnsembleParameter( param.name );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report->crossPlot()->setWellName( source->simWellOrWellPathName() );
|
||||||
|
|
||||||
|
const auto timeSteps = source->selectedTimeSteps();
|
||||||
|
if ( !timeSteps.empty() ) report->crossPlot()->setTimeStep( timeSteps.front() );
|
||||||
|
}
|
||||||
|
|
||||||
|
m_rftCorrelationReports.push_back( report );
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::vector<RimRftCorrelationReportPlot*> RimCorrelationPlotCollection::rftReports() const
|
||||||
|
{
|
||||||
|
return m_rftCorrelationReports.childrenByType();
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
///
|
///
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -222,6 +272,7 @@ void RimCorrelationPlotCollection::deleteAllPlots()
|
|||||||
{
|
{
|
||||||
RimTypedPlotCollection<RimAbstractCorrelationPlot>::deleteAllPlots();
|
RimTypedPlotCollection<RimAbstractCorrelationPlot>::deleteAllPlots();
|
||||||
m_correlationReports.deleteChildren();
|
m_correlationReports.deleteChildren();
|
||||||
|
m_rftCorrelationReports.deleteChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -382,6 +433,9 @@ void RimCorrelationPlotCollection::loadDataAndUpdateAllPlots()
|
|||||||
for ( const auto& corrPlot : m_correlationPlots )
|
for ( const auto& corrPlot : m_correlationPlots )
|
||||||
corrPlot->loadDataAndUpdate();
|
corrPlot->loadDataAndUpdate();
|
||||||
|
|
||||||
for ( const auto& reports : m_correlationReports )
|
for ( const auto& report : m_correlationReports )
|
||||||
reports->loadDataAndUpdate();
|
report->loadDataAndUpdate();
|
||||||
|
|
||||||
|
for ( const auto& rftReport : m_rftCorrelationReports )
|
||||||
|
rftReport->loadDataAndUpdate();
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,9 @@ class RimCorrelationPlot;
|
|||||||
class RimCorrelationMatrixPlot;
|
class RimCorrelationMatrixPlot;
|
||||||
class RimCorrelationReportPlot;
|
class RimCorrelationReportPlot;
|
||||||
class RimParameterResultCrossPlot;
|
class RimParameterResultCrossPlot;
|
||||||
|
class RimRftCorrelationReportPlot;
|
||||||
class RimSummaryEnsemble;
|
class RimSummaryEnsemble;
|
||||||
|
class RimWellRftPlot;
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
///
|
///
|
||||||
@@ -66,6 +68,8 @@ public:
|
|||||||
|
|
||||||
void removeReport( RimCorrelationReportPlot* correlationReport );
|
void removeReport( RimCorrelationReportPlot* correlationReport );
|
||||||
|
|
||||||
|
RimRftCorrelationReportPlot* createRftCorrelationReportPlot( RimWellRftPlot* source );
|
||||||
|
|
||||||
std::vector<RimAbstractCorrelationPlot*> plots() const final;
|
std::vector<RimAbstractCorrelationPlot*> plots() const final;
|
||||||
size_t plotCount() const final;
|
size_t plotCount() const final;
|
||||||
void insertPlot( RimAbstractCorrelationPlot* plot, size_t index ) final;
|
void insertPlot( RimAbstractCorrelationPlot* plot, size_t index ) final;
|
||||||
@@ -73,7 +77,8 @@ public:
|
|||||||
void deleteAllPlots() final;
|
void deleteAllPlots() final;
|
||||||
void loadDataAndUpdateAllPlots() override;
|
void loadDataAndUpdateAllPlots() override;
|
||||||
|
|
||||||
std::vector<RimCorrelationReportPlot*> reports() const;
|
std::vector<RimCorrelationReportPlot*> reports() const;
|
||||||
|
std::vector<RimRftCorrelationReportPlot*> rftReports() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void applyFirstEnsembleFieldAddressesToPlot( RimAbstractCorrelationPlot* plot, const std::vector<QString>& quantityNames = {} );
|
void applyFirstEnsembleFieldAddressesToPlot( RimAbstractCorrelationPlot* plot, const std::vector<QString>& quantityNames = {} );
|
||||||
@@ -91,6 +96,7 @@ private:
|
|||||||
std::time_t timeStep );
|
std::time_t timeStep );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
caf::PdmChildArrayField<RimAbstractCorrelationPlot*> m_correlationPlots;
|
caf::PdmChildArrayField<RimAbstractCorrelationPlot*> m_correlationPlots;
|
||||||
caf::PdmChildArrayField<RimCorrelationReportPlot*> m_correlationReports;
|
caf::PdmChildArrayField<RimCorrelationReportPlot*> m_correlationReports;
|
||||||
|
caf::PdmChildArrayField<RimRftCorrelationReportPlot*> m_rftCorrelationReports;
|
||||||
};
|
};
|
||||||
+25
-65
@@ -33,11 +33,13 @@
|
|||||||
#include "RiuContextMenuLauncher.h"
|
#include "RiuContextMenuLauncher.h"
|
||||||
#include "RiuDockWidgetTools.h"
|
#include "RiuDockWidgetTools.h"
|
||||||
#include "RiuPlotCurve.h"
|
#include "RiuPlotCurve.h"
|
||||||
|
#include "RiuQwtCurveSelectorFilter.h"
|
||||||
#include "RiuQwtPlotCurve.h"
|
#include "RiuQwtPlotCurve.h"
|
||||||
#include "RiuQwtPlotRectAnnotation.h"
|
#include "RiuQwtPlotRectAnnotation.h"
|
||||||
#include "RiuQwtPlotWidget.h"
|
#include "RiuQwtPlotWidget.h"
|
||||||
#include "RiuQwtSymbol.h"
|
#include "RiuQwtSymbol.h"
|
||||||
|
|
||||||
|
#include "cafPdmPointer.h"
|
||||||
#include "cafPdmUiComboBoxEditor.h"
|
#include "cafPdmUiComboBoxEditor.h"
|
||||||
#include "cafPdmUiTextEditor.h"
|
#include "cafPdmUiTextEditor.h"
|
||||||
#include "cafPdmUiValueRangeEditor.h"
|
#include "cafPdmUiValueRangeEditor.h"
|
||||||
@@ -424,6 +426,25 @@ void RimParameterResultCrossPlot::updateValueRanges()
|
|||||||
m_yValueRange = { summaryMin - summaryRange * 0.1, summaryMax + summaryRange * 0.1 };
|
m_yValueRange = { summaryMin - summaryRange * 0.1, summaryMax + summaryRange * 0.1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimSummaryCase* RimParameterResultCrossPlot::findClosestCase( const QPoint& canvasPos )
|
||||||
|
{
|
||||||
|
auto* qwtWidget = dynamic_cast<RiuQwtPlotWidget*>( plotWidget() );
|
||||||
|
if ( !qwtWidget ) return nullptr;
|
||||||
|
|
||||||
|
auto caseData = createCaseData();
|
||||||
|
|
||||||
|
std::vector<std::pair<double, double>> points;
|
||||||
|
points.reserve( caseData.size() );
|
||||||
|
for ( const auto& d : caseData )
|
||||||
|
points.push_back( { d.parameterValue, d.summaryValue } );
|
||||||
|
|
||||||
|
int idx = RiuQwtCurveSelectorFilter::closestPointIndex( qwtWidget->qwtPlot(), canvasPos, points );
|
||||||
|
return idx >= 0 ? caseData[idx].summaryCase : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
///
|
///
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -599,70 +620,6 @@ protected:
|
|||||||
return QwtText();
|
return QwtText();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
class CurveSelectorFilter : public QObject
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CurveSelectorFilter( QwtPlot* plot, RimParameterResultCrossPlot* crossPlot )
|
|
||||||
: QObject( plot->canvas() )
|
|
||||||
, m_plot( plot )
|
|
||||||
, m_crossPlot( crossPlot )
|
|
||||||
{
|
|
||||||
plot->canvas()->installEventFilter( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool eventFilter( QObject*, QEvent* event ) override
|
|
||||||
{
|
|
||||||
if ( event->type() == QEvent::MouseButtonPress )
|
|
||||||
{
|
|
||||||
auto* mouseEvent = static_cast<QMouseEvent*>( event );
|
|
||||||
if ( mouseEvent->button() == Qt::LeftButton )
|
|
||||||
{
|
|
||||||
selectClosestCase( mouseEvent->pos() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void selectClosestCase( const QPoint& pixelPos )
|
|
||||||
{
|
|
||||||
const auto xMap = m_plot->canvasMap( QwtAxis::XBottom );
|
|
||||||
const auto yMap = m_plot->canvasMap( QwtAxis::YLeft );
|
|
||||||
|
|
||||||
const double clickX = xMap.invTransform( pixelPos.x() );
|
|
||||||
const double clickY = yMap.invTransform( pixelPos.y() );
|
|
||||||
|
|
||||||
// Normalise by axis range to get a dimensionless distance
|
|
||||||
const double xRange = std::abs( xMap.s2() - xMap.s1() );
|
|
||||||
const double yRange = std::abs( yMap.s2() - yMap.s1() );
|
|
||||||
if ( xRange == 0.0 || yRange == 0.0 ) return;
|
|
||||||
|
|
||||||
double minDist = std::numeric_limits<double>::max();
|
|
||||||
RimSummaryCase* closestCase = nullptr;
|
|
||||||
|
|
||||||
for ( const auto& [paramValue, summaryValue, summaryCase] : m_crossPlot->createCaseData() )
|
|
||||||
{
|
|
||||||
const double dx = ( paramValue - clickX ) / xRange;
|
|
||||||
const double dy = ( summaryValue - clickY ) / yRange;
|
|
||||||
const double dist = dx * dx + dy * dy;
|
|
||||||
if ( dist < minDist )
|
|
||||||
{
|
|
||||||
minDist = dist;
|
|
||||||
closestCase = summaryCase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept if within 3% of the total axis range
|
|
||||||
if ( closestCase && minDist < 0.03 * 0.03 )
|
|
||||||
{
|
|
||||||
RiuDockWidgetTools::selectItemsInTreeView( RiuDockWidgetTools::plotMainWindowDataSourceTreeName(), { closestCase } );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QwtPlot* m_plot = nullptr;
|
|
||||||
RimParameterResultCrossPlot* m_crossPlot = nullptr;
|
|
||||||
};
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -687,7 +644,10 @@ RiuPlotWidget* RimParameterResultCrossPlot::doCreatePlotViewWidget( QWidget* mai
|
|||||||
new internal::CurveTracker( m_plotWidget->qwtPlot() );
|
new internal::CurveTracker( m_plotWidget->qwtPlot() );
|
||||||
|
|
||||||
// Add a click filter to select the realization in the tree view when a point is clicked
|
// Add a click filter to select the realization in the tree view when a point is clicked
|
||||||
new internal::CurveSelectorFilter( m_plotWidget->qwtPlot(), this );
|
caf::PdmPointer<RimParameterResultCrossPlot> self( this );
|
||||||
|
new RiuQwtCurveSelectorFilter( m_plotWidget->qwtPlot(),
|
||||||
|
[self]( const QPoint& pos ) -> const caf::PdmUiItem*
|
||||||
|
{ return self ? self->findClosestCase( pos ) : nullptr; } );
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_plotWidget;
|
return m_plotWidget;
|
||||||
|
|||||||
@@ -67,8 +67,9 @@ private:
|
|||||||
void updatePlotTitle() override;
|
void updatePlotTitle() override;
|
||||||
void createPoints();
|
void createPoints();
|
||||||
|
|
||||||
void updateValueRanges();
|
void updateValueRanges();
|
||||||
void updateFilterRanges();
|
void updateFilterRanges();
|
||||||
|
RimSummaryCase* findClosestCase( const QPoint& canvasPos );
|
||||||
|
|
||||||
QString excludedCasesText() const;
|
QString excludedCasesText() const;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,796 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 "RimParameterRftCrossPlot.h"
|
||||||
|
|
||||||
|
#include "RiaColorTables.h"
|
||||||
|
#include "RiaPreferences.h"
|
||||||
|
|
||||||
|
#include "RifEclipseRftAddress.h"
|
||||||
|
#include "RifReaderRftInterface.h"
|
||||||
|
|
||||||
|
#include "RigEnsembleParameter.h"
|
||||||
|
#include "RigStatisticsTools.h"
|
||||||
|
|
||||||
|
#include "RiaExtractionTools.h"
|
||||||
|
|
||||||
|
#include "Well/RigEclipseWellLogExtractor.h"
|
||||||
|
|
||||||
|
#include "RimEclipseCase.h"
|
||||||
|
#include "RimEclipseResultCase.h"
|
||||||
|
#include "RimProject.h"
|
||||||
|
#include "RimSummaryCase.h"
|
||||||
|
#include "RimSummaryEnsemble.h"
|
||||||
|
#include "RimSummaryEnsembleTools.h"
|
||||||
|
#include "RimWellPath.h"
|
||||||
|
|
||||||
|
#include "RiuContextMenuLauncher.h"
|
||||||
|
#include "RiuDockWidgetTools.h"
|
||||||
|
#include "RiuPlotCurve.h"
|
||||||
|
#include "RiuQwtCurveSelectorFilter.h"
|
||||||
|
#include "RiuQwtPlotCurve.h"
|
||||||
|
#include "RiuQwtPlotWidget.h"
|
||||||
|
#include "RiuQwtSymbol.h"
|
||||||
|
|
||||||
|
#include "cafPdmPointer.h"
|
||||||
|
#include "cafPdmUiComboBoxEditor.h"
|
||||||
|
|
||||||
|
#include "qwt_picker_machine.h"
|
||||||
|
#include "qwt_plot.h"
|
||||||
|
#include "qwt_plot_curve.h"
|
||||||
|
#include "qwt_plot_marker.h"
|
||||||
|
#include "qwt_plot_picker.h"
|
||||||
|
#include "qwt_scale_map.h"
|
||||||
|
#include "qwt_text.h"
|
||||||
|
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QPaintDevice>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
CAF_PDM_SOURCE_INIT( RimParameterRftCrossPlot, "ParameterRftCrossPlot" );
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimParameterRftCrossPlot::RimParameterRftCrossPlot()
|
||||||
|
{
|
||||||
|
CAF_PDM_InitObject( "Parameter RFT Cross Plot", ":/CorrelationCrossPlot16x16.png" );
|
||||||
|
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_ensemble, "Ensemble", "Ensemble" );
|
||||||
|
CAF_PDM_InitField( &m_wellName, "WellName", QString(), "Well Name" );
|
||||||
|
m_wellName.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() );
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_selectedTimeStep, "TimeStep", "Time Step" );
|
||||||
|
m_selectedTimeStep.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() );
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_eclipseCase, "EclipseCase", "Eclipse Case (MD fallback)" );
|
||||||
|
CAF_PDM_InitField( &m_useDepthRange, "UseDepthRange", false, "Filter by Depth Range" );
|
||||||
|
CAF_PDM_InitField( &m_depthRangeMin, "DepthRangeMin", 0.0, "Min Depth (MD)" );
|
||||||
|
CAF_PDM_InitField( &m_depthRangeMax, "DepthRangeMax", 5000.0, "Max Depth (MD)" );
|
||||||
|
CAF_PDM_InitField( &m_ensembleParameter, "EnsembleParameter", QString(), "Ensemble Parameter" );
|
||||||
|
m_ensembleParameter.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() );
|
||||||
|
|
||||||
|
CAF_PDM_InitField( &m_useAutoPlotTitle, "UseAutoPlotTitle", true, "Auto Title" );
|
||||||
|
CAF_PDM_InitField( &m_description, "Description", QString( "RFT Cross Plot" ), "Title" );
|
||||||
|
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_axisTitleFontSize, "AxisTitleFontSize", "Axis Title Font Size" );
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_axisValueFontSize, "AxisValueFontSize", "Axis Value Font Size" );
|
||||||
|
|
||||||
|
m_axisTitleFontSize = caf::FontTools::RelativeSize::Small;
|
||||||
|
m_axisValueFontSize = caf::FontTools::RelativeSize::Small;
|
||||||
|
|
||||||
|
m_showPlotLegends = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimParameterRftCrossPlot::~RimParameterRftCrossPlot()
|
||||||
|
{
|
||||||
|
cleanupBeforeClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::setEnsemble( RimSummaryEnsemble* ensemble )
|
||||||
|
{
|
||||||
|
m_ensemble = ensemble;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::setWellName( const QString& wellName )
|
||||||
|
{
|
||||||
|
m_wellName = wellName;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::setTimeStep( const QDateTime& timeStep )
|
||||||
|
{
|
||||||
|
m_selectedTimeStep = timeStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::setDepthRange( double minMd, double maxMd )
|
||||||
|
{
|
||||||
|
m_depthRangeMin = minMd;
|
||||||
|
m_depthRangeMax = maxMd;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::setEnsembleParameter( const QString& paramName )
|
||||||
|
{
|
||||||
|
m_ensembleParameter = paramName;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QString RimParameterRftCrossPlot::ensembleParameter() const
|
||||||
|
{
|
||||||
|
return m_ensembleParameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QString RimParameterRftCrossPlot::wellName() const
|
||||||
|
{
|
||||||
|
return m_wellName;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QDateTime RimParameterRftCrossPlot::selectedTimeStep() const
|
||||||
|
{
|
||||||
|
return m_selectedTimeStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimSummaryEnsemble* RimParameterRftCrossPlot::ensemble() const
|
||||||
|
{
|
||||||
|
return m_ensemble();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RiuQwtPlotWidget* RimParameterRftCrossPlot::viewer()
|
||||||
|
{
|
||||||
|
return m_plotWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::vector<double> RimParameterRftCrossPlot::computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
|
||||||
|
const QString& wellName,
|
||||||
|
const QDateTime& timeStep,
|
||||||
|
RimEclipseResultCase* eclipseCase,
|
||||||
|
bool useDepthRange,
|
||||||
|
double depthRangeMin,
|
||||||
|
double depthRangeMax )
|
||||||
|
{
|
||||||
|
if ( !ensemble || wellName.isEmpty() || !timeStep.isValid() ) return {};
|
||||||
|
|
||||||
|
RigEclipseWellLogExtractor* extractor = nullptr;
|
||||||
|
if ( eclipseCase )
|
||||||
|
{
|
||||||
|
RimWellPath* wellPath = RimProject::current()->wellPathFromSimWellName( wellName );
|
||||||
|
extractor = RiaExtractionTools::findOrCreateWellLogExtractor( wellPath, eclipseCase );
|
||||||
|
if ( !extractor ) extractor = RiaExtractionTools::findOrCreateSimWellExtractor( eclipseCase, wellName, false, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& allCases = ensemble->allSummaryCases();
|
||||||
|
|
||||||
|
std::vector<double> pressurePerCase;
|
||||||
|
pressurePerCase.reserve( allCases.size() );
|
||||||
|
|
||||||
|
for ( RimSummaryCase* summaryCase : allCases )
|
||||||
|
{
|
||||||
|
if ( !summaryCase )
|
||||||
|
{
|
||||||
|
pressurePerCase.push_back( std::numeric_limits<double>::infinity() );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RifReaderRftInterface* reader = summaryCase->rftReader();
|
||||||
|
if ( !reader )
|
||||||
|
{
|
||||||
|
pressurePerCase.push_back( std::numeric_limits<double>::infinity() );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pressureAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::PRESSURE );
|
||||||
|
std::vector<double> pressures;
|
||||||
|
reader->values( pressureAddress, &pressures );
|
||||||
|
if ( pressures.empty() )
|
||||||
|
{
|
||||||
|
pressurePerCase.push_back( std::numeric_limits<double>::infinity() );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mdAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::MD );
|
||||||
|
std::vector<double> depths;
|
||||||
|
reader->values( mdAddress, &depths );
|
||||||
|
if ( depths.empty() && extractor ) depths = reader->computeMeasuredDepth( wellName, timeStep, extractor );
|
||||||
|
// If depths are still empty after the fallback (no MD channel and no extractor), depth range
|
||||||
|
// filtering is not possible for this case; fall through to use all pressures unfiltered.
|
||||||
|
|
||||||
|
std::vector<double> samplesInRange;
|
||||||
|
if ( useDepthRange && depths.size() == pressures.size() )
|
||||||
|
{
|
||||||
|
for ( size_t i = 0; i < depths.size(); ++i )
|
||||||
|
if ( depths[i] >= depthRangeMin && depths[i] <= depthRangeMax ) samplesInRange.push_back( pressures[i] );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
samplesInRange = pressures;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( samplesInRange.empty() )
|
||||||
|
pressurePerCase.push_back( std::numeric_limits<double>::infinity() );
|
||||||
|
else
|
||||||
|
pressurePerCase.push_back( std::accumulate( samplesInRange.begin(), samplesInRange.end(), 0.0 ) / samplesInRange.size() );
|
||||||
|
}
|
||||||
|
|
||||||
|
return pressurePerCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::vector<RimParameterRftCrossPlot::CaseData> RimParameterRftCrossPlot::createCaseData() const
|
||||||
|
{
|
||||||
|
if ( !m_ensemble() ) return {};
|
||||||
|
if ( m_wellName().isEmpty() ) return {};
|
||||||
|
if ( !m_selectedTimeStep().isValid() ) return {};
|
||||||
|
if ( m_ensembleParameter().isEmpty() ) return {};
|
||||||
|
|
||||||
|
RigEnsembleParameter parameter = m_ensemble->ensembleParameter( m_ensembleParameter );
|
||||||
|
if ( !parameter.isNumeric() || !parameter.isValid() ) return {};
|
||||||
|
|
||||||
|
const auto& allCases = m_ensemble->allSummaryCases();
|
||||||
|
|
||||||
|
const std::vector<double> pressurePerCase = computeMeanPressurePerCase( m_ensemble(),
|
||||||
|
m_wellName(),
|
||||||
|
m_selectedTimeStep(),
|
||||||
|
m_eclipseCase(),
|
||||||
|
m_useDepthRange(),
|
||||||
|
m_depthRangeMin(),
|
||||||
|
m_depthRangeMax() );
|
||||||
|
|
||||||
|
if ( pressurePerCase.size() != allCases.size() ) return {};
|
||||||
|
|
||||||
|
std::vector<CaseData> result;
|
||||||
|
result.reserve( allCases.size() );
|
||||||
|
|
||||||
|
for ( size_t caseIdx = 0; caseIdx < allCases.size(); ++caseIdx )
|
||||||
|
{
|
||||||
|
RimSummaryCase* summaryCase = allCases[caseIdx];
|
||||||
|
if ( !summaryCase ) continue;
|
||||||
|
if ( std::isinf( pressurePerCase[caseIdx] ) ) continue;
|
||||||
|
if ( caseIdx >= static_cast<size_t>( parameter.values.size() ) ) continue;
|
||||||
|
|
||||||
|
result.push_back(
|
||||||
|
{ .parameterValue = parameter.values[caseIdx].toDouble(), .pressureValue = pressurePerCase[caseIdx], .summaryCase = summaryCase } );
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RiuPlotWidget* RimParameterRftCrossPlot::plotWidget()
|
||||||
|
{
|
||||||
|
return m_plotWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::updateAxes()
|
||||||
|
{
|
||||||
|
if ( !m_plotWidget ) return;
|
||||||
|
|
||||||
|
const int axisTitleSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisTitleFontSize() );
|
||||||
|
const int axisValueSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisValueFontSize() );
|
||||||
|
|
||||||
|
const QString depthLabel = m_useDepthRange() ? QString( "Mean Pressure [MD %1 - %2]" ).arg( m_depthRangeMin() ).arg( m_depthRangeMax() )
|
||||||
|
: QString( "Mean Pressure" );
|
||||||
|
|
||||||
|
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultLeft(), depthLabel );
|
||||||
|
m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultLeft(), true );
|
||||||
|
m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultLeft(), axisTitleSize, axisValueSize, false, Qt::AlignCenter );
|
||||||
|
|
||||||
|
if ( m_yValueRange.has_value() )
|
||||||
|
{
|
||||||
|
m_plotWidget->setAxisRange( RiuPlotAxis::defaultLeft(), m_yValueRange->first, m_yValueRange->second );
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultBottom(), m_ensembleParameter() );
|
||||||
|
m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultBottom(), true );
|
||||||
|
m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultBottom(), axisTitleSize, axisValueSize, false, Qt::AlignCenter );
|
||||||
|
|
||||||
|
if ( m_xValueRange.has_value() )
|
||||||
|
{
|
||||||
|
m_plotWidget->setAxisRange( RiuPlotAxis::defaultBottom(), m_xValueRange->first, m_xValueRange->second );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QString RimParameterRftCrossPlot::asciiDataForPlotExport() const
|
||||||
|
{
|
||||||
|
QString asciiData;
|
||||||
|
asciiData += "Realization\tParameter\tMean Pressure\n";
|
||||||
|
for ( const auto& [paramValue, pressureValue, summaryCase] : createCaseData() )
|
||||||
|
{
|
||||||
|
asciiData += QString( "%1\t%2\t%3\n" ).arg( summaryCase->displayCaseName() ).arg( paramValue ).arg( pressureValue );
|
||||||
|
}
|
||||||
|
return asciiData;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::detachAllCurves()
|
||||||
|
{
|
||||||
|
if ( m_plotWidget ) m_plotWidget->qwtPlot()->detachItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QString RimParameterRftCrossPlot::description() const
|
||||||
|
{
|
||||||
|
return m_description();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QWidget* RimParameterRftCrossPlot::viewWidget()
|
||||||
|
{
|
||||||
|
return m_plotWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::deleteViewWidget()
|
||||||
|
{
|
||||||
|
cleanupBeforeClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::doRenderWindowContent( QPaintDevice* paintDevice )
|
||||||
|
{
|
||||||
|
if ( m_plotWidget ) m_plotWidget->render( paintDevice );
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class CurveTracker : public QwtPlotPicker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CurveTracker( QwtPlot* plot )
|
||||||
|
: QwtPlotPicker( plot->canvas() )
|
||||||
|
{
|
||||||
|
setStateMachine( new QwtPickerTrackerMachine() );
|
||||||
|
setRubberBand( QwtPicker::NoRubberBand );
|
||||||
|
setTrackerMode( QwtPicker::AlwaysOn );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QwtText trackerText( const QPoint& pos ) const override
|
||||||
|
{
|
||||||
|
double minDistance = std::numeric_limits<double>::max();
|
||||||
|
QString closestCurveLabel;
|
||||||
|
|
||||||
|
for ( QwtPlotItem* item : plot()->itemList() )
|
||||||
|
{
|
||||||
|
if ( item->rtti() == QwtPlotItem::Rtti_PlotCurve )
|
||||||
|
{
|
||||||
|
auto curve = static_cast<QwtPlotCurve*>( item );
|
||||||
|
double distance = std::numeric_limits<double>::max();
|
||||||
|
curve->closestPoint( pos, &distance );
|
||||||
|
|
||||||
|
if ( distance < minDistance )
|
||||||
|
{
|
||||||
|
minDistance = distance;
|
||||||
|
closestCurveLabel = curve->title().text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( minDistance < 20.0 )
|
||||||
|
{
|
||||||
|
QwtText text( closestCurveLabel );
|
||||||
|
text.setBackgroundBrush( QBrush( Qt::white ) );
|
||||||
|
text.setColor( Qt::black );
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return QwtText();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RiuPlotWidget* RimParameterRftCrossPlot::doCreatePlotViewWidget( QWidget* parent )
|
||||||
|
{
|
||||||
|
if ( !m_plotWidget )
|
||||||
|
{
|
||||||
|
m_plotWidget = new RiuQwtPlotWidget( this, parent );
|
||||||
|
updatePlotTitle();
|
||||||
|
new RiuContextMenuLauncher( m_plotWidget, { "RicShowPlotDataFeature" } );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( m_plotWidget )
|
||||||
|
{
|
||||||
|
new CurveTracker( m_plotWidget->qwtPlot() );
|
||||||
|
|
||||||
|
caf::PdmPointer<RimParameterRftCrossPlot> self( this );
|
||||||
|
new RiuQwtCurveSelectorFilter( m_plotWidget->qwtPlot(),
|
||||||
|
[self]( const QPoint& pos ) -> const caf::PdmUiItem*
|
||||||
|
{ return self ? self->findClosestCase( pos ) : nullptr; } );
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_plotWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::onLoadDataAndUpdate()
|
||||||
|
{
|
||||||
|
updateMdiWindowVisibility();
|
||||||
|
|
||||||
|
if ( m_plotWidget )
|
||||||
|
{
|
||||||
|
createPoints();
|
||||||
|
updateValueRanges();
|
||||||
|
updateAxes();
|
||||||
|
updatePlotTitle();
|
||||||
|
m_plotWidget->scheduleReplot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
||||||
|
{
|
||||||
|
auto* dataGroup = uiOrdering.addNewGroup( "Data Source" );
|
||||||
|
dataGroup->add( &m_ensemble );
|
||||||
|
dataGroup->add( &m_wellName );
|
||||||
|
dataGroup->add( &m_selectedTimeStep );
|
||||||
|
dataGroup->add( &m_eclipseCase );
|
||||||
|
|
||||||
|
auto* depthGroup = uiOrdering.addNewGroup( "Depth Range" );
|
||||||
|
depthGroup->add( &m_useDepthRange );
|
||||||
|
depthGroup->add( &m_depthRangeMin );
|
||||||
|
depthGroup->add( &m_depthRangeMax );
|
||||||
|
m_depthRangeMin.uiCapability()->setUiReadOnly( !m_useDepthRange() );
|
||||||
|
m_depthRangeMax.uiCapability()->setUiReadOnly( !m_useDepthRange() );
|
||||||
|
|
||||||
|
auto* crossPlotGroup = uiOrdering.addNewGroup( "Cross Plot Parameter" );
|
||||||
|
crossPlotGroup->add( &m_ensembleParameter );
|
||||||
|
|
||||||
|
auto* plotGroup = uiOrdering.addNewGroup( "Plot Settings" );
|
||||||
|
plotGroup->setCollapsedByDefault();
|
||||||
|
plotGroup->add( &m_useAutoPlotTitle );
|
||||||
|
plotGroup->add( &m_description );
|
||||||
|
plotGroup->add( &m_axisTitleFontSize );
|
||||||
|
plotGroup->add( &m_axisValueFontSize );
|
||||||
|
|
||||||
|
m_description.uiCapability()->setUiReadOnly( m_useAutoPlotTitle() );
|
||||||
|
|
||||||
|
uiOrdering.skipRemainingFields( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
|
||||||
|
{
|
||||||
|
if ( changedField == &m_ensemble || changedField == &m_wellName )
|
||||||
|
{
|
||||||
|
// Reset time step to the first available one for the new ensemble/well combination
|
||||||
|
std::set<QDateTime> timeSteps;
|
||||||
|
if ( m_ensemble() && !m_wellName().isEmpty() )
|
||||||
|
{
|
||||||
|
for ( RimSummaryCase* summaryCase : m_ensemble->allSummaryCases() )
|
||||||
|
{
|
||||||
|
RifReaderRftInterface* reader = summaryCase->rftReader();
|
||||||
|
if ( reader )
|
||||||
|
{
|
||||||
|
for ( const QDateTime& dt : reader->availableTimeSteps( m_wellName() ) )
|
||||||
|
timeSteps.insert( dt );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_selectedTimeStep = timeSteps.empty() ? QDateTime() : *timeSteps.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
RimPlot::fieldChangedByUi( changedField, oldValue, newValue );
|
||||||
|
loadDataAndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QList<caf::PdmOptionItemInfo> RimParameterRftCrossPlot::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
|
||||||
|
{
|
||||||
|
QList<caf::PdmOptionItemInfo> options;
|
||||||
|
if ( fieldNeedingOptions == &m_ensemble )
|
||||||
|
{
|
||||||
|
for ( RimSummaryEnsemble* ensemble : RimProject::current()->summaryEnsembles() )
|
||||||
|
{
|
||||||
|
if ( ensemble->isEnsemble() ) options.push_back( caf::PdmOptionItemInfo( ensemble->name(), ensemble ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( fieldNeedingOptions == &m_wellName )
|
||||||
|
{
|
||||||
|
std::set<QString> wellNames;
|
||||||
|
if ( m_ensemble() )
|
||||||
|
{
|
||||||
|
for ( RimSummaryCase* summaryCase : m_ensemble->allSummaryCases() )
|
||||||
|
{
|
||||||
|
RifReaderRftInterface* reader = summaryCase->rftReader();
|
||||||
|
if ( reader )
|
||||||
|
{
|
||||||
|
for ( const QString& name : reader->wellNames() )
|
||||||
|
wellNames.insert( name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ( const QString& name : wellNames )
|
||||||
|
options.push_back( caf::PdmOptionItemInfo( name, name ) );
|
||||||
|
}
|
||||||
|
else if ( fieldNeedingOptions == &m_selectedTimeStep )
|
||||||
|
{
|
||||||
|
std::set<QDateTime> timeSteps;
|
||||||
|
if ( m_ensemble() && !m_wellName().isEmpty() )
|
||||||
|
{
|
||||||
|
for ( RimSummaryCase* summaryCase : m_ensemble->allSummaryCases() )
|
||||||
|
{
|
||||||
|
RifReaderRftInterface* reader = summaryCase->rftReader();
|
||||||
|
if ( reader )
|
||||||
|
{
|
||||||
|
for ( const QDateTime& dt : reader->availableTimeSteps( m_wellName() ) )
|
||||||
|
timeSteps.insert( dt );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ( const QDateTime& dt : timeSteps )
|
||||||
|
options.push_back( caf::PdmOptionItemInfo( dt.toString( "yyyy-MM-dd" ), dt ) );
|
||||||
|
}
|
||||||
|
else if ( fieldNeedingOptions == &m_eclipseCase )
|
||||||
|
{
|
||||||
|
options.push_back( caf::PdmOptionItemInfo( "None", static_cast<RimEclipseResultCase*>( nullptr ) ) );
|
||||||
|
for ( RimEclipseCase* c : RimProject::current()->eclipseCases() )
|
||||||
|
{
|
||||||
|
if ( auto* rc = dynamic_cast<RimEclipseResultCase*>( c ) )
|
||||||
|
options.push_back( caf::PdmOptionItemInfo( rc->caseUserDescription(), rc ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( fieldNeedingOptions == &m_ensembleParameter )
|
||||||
|
{
|
||||||
|
if ( m_ensemble() )
|
||||||
|
{
|
||||||
|
const auto& allCases = m_ensemble->allSummaryCases();
|
||||||
|
|
||||||
|
// Build mean RFT pressure per case if enough context is available for correlation sorting
|
||||||
|
const bool canComputeCorrelation = !m_wellName().isEmpty() && m_selectedTimeStep().isValid();
|
||||||
|
std::vector<double> pressurePerCase;
|
||||||
|
if ( canComputeCorrelation )
|
||||||
|
{
|
||||||
|
pressurePerCase = computeMeanPressurePerCase( m_ensemble(),
|
||||||
|
m_wellName(),
|
||||||
|
m_selectedTimeStep(),
|
||||||
|
m_eclipseCase(),
|
||||||
|
m_useDepthRange(),
|
||||||
|
m_depthRangeMin(),
|
||||||
|
m_depthRangeMax() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute correlation for each numeric parameter, then sort by abs value descending
|
||||||
|
std::vector<std::pair<double, RigEnsembleParameter>> correlatedParams;
|
||||||
|
for ( const auto& param : RimSummaryEnsembleTools::alphabeticEnsembleParameters( allCases ) )
|
||||||
|
{
|
||||||
|
if ( !param.isNumeric() ) continue;
|
||||||
|
|
||||||
|
double absPearson = 0.0;
|
||||||
|
if ( canComputeCorrelation && static_cast<size_t>( param.values.size() ) == allCases.size() )
|
||||||
|
{
|
||||||
|
std::vector<double> paramValues, pressureValues;
|
||||||
|
for ( size_t i = 0; i < allCases.size(); ++i )
|
||||||
|
{
|
||||||
|
if ( std::isinf( pressurePerCase[i] ) ) continue;
|
||||||
|
paramValues.push_back( param.values[i].toDouble() );
|
||||||
|
pressureValues.push_back( pressurePerCase[i] );
|
||||||
|
}
|
||||||
|
if ( paramValues.size() >= 2 )
|
||||||
|
{
|
||||||
|
double r = RigStatisticsTools::pearsonCorrelation( paramValues, pressureValues );
|
||||||
|
if ( !std::isinf( r ) && !std::isnan( r ) ) absPearson = std::abs( r );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
correlatedParams.emplace_back( absPearson, param );
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stable_sort( correlatedParams.begin(),
|
||||||
|
correlatedParams.end(),
|
||||||
|
[]( const auto& a, const auto& b ) { return a.first > b.first; } );
|
||||||
|
|
||||||
|
for ( const auto& [corr, param] : correlatedParams )
|
||||||
|
options.push_back( caf::PdmOptionItemInfo( param.uiName(), param.name ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::createPoints()
|
||||||
|
{
|
||||||
|
detachAllCurves();
|
||||||
|
|
||||||
|
caf::ColorTable colorTable = RiaColorTables::categoryPaletteColors();
|
||||||
|
|
||||||
|
auto caseData = createCaseData();
|
||||||
|
if ( caseData.empty() ) return;
|
||||||
|
|
||||||
|
std::set<RimSummaryCase*> selectedSummaryCases;
|
||||||
|
auto selectedTreeViewItems = RiuDockWidgetTools::selectedItemsInTreeView( RiuDockWidgetTools::plotMainWindowDataSourceTreeName() );
|
||||||
|
for ( auto item : selectedTreeViewItems )
|
||||||
|
{
|
||||||
|
if ( auto summaryCase = dynamic_cast<RimSummaryCase*>( item ) )
|
||||||
|
{
|
||||||
|
selectedSummaryCases.insert( summaryCase );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
for ( const auto& [paramValue, pressureValue, summaryCase] : caseData )
|
||||||
|
{
|
||||||
|
auto* plotCurve = new RiuQwtPlotCurve;
|
||||||
|
plotCurve->setSamplesValues( { paramValue }, { pressureValue } );
|
||||||
|
plotCurve->setStyle( QwtPlotCurve::NoCurve );
|
||||||
|
|
||||||
|
const bool isSelected = selectedSummaryCases.contains( summaryCase );
|
||||||
|
auto* symbol = new RiuQwtSymbol( isSelected ? RiuPlotCurveSymbol::SYMBOL_XCROSS : RiuPlotCurveSymbol::SYMBOL_ELLIPSE );
|
||||||
|
symbol->setSize( 8, 8 );
|
||||||
|
symbol->setColor( colorTable.cycledQColor( idx++ ) );
|
||||||
|
plotCurve->setSymbol( symbol );
|
||||||
|
|
||||||
|
plotCurve->setTitle( summaryCase->displayCaseName() );
|
||||||
|
plotCurve->attach( m_plotWidget->qwtPlot() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::updatePlotTitle()
|
||||||
|
{
|
||||||
|
if ( !m_plotWidget ) return;
|
||||||
|
|
||||||
|
if ( m_useAutoPlotTitle && m_ensemble() )
|
||||||
|
{
|
||||||
|
if ( m_useDepthRange() )
|
||||||
|
{
|
||||||
|
m_description = QString( "%1 vs RFT Pressure [%2 - %3 m], %4" )
|
||||||
|
.arg( m_ensembleParameter() )
|
||||||
|
.arg( m_depthRangeMin() )
|
||||||
|
.arg( m_depthRangeMax() )
|
||||||
|
.arg( m_ensemble->name() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_description = QString( "%1 vs RFT Pressure, %2" ).arg( m_ensembleParameter() ).arg( m_ensemble->name() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plotWidget->setPlotTitle( m_description() );
|
||||||
|
m_plotWidget->setPlotTitleEnabled( m_showPlotTitle() );
|
||||||
|
m_plotWidget->setPlotTitleFontSize( titleFontSize() );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::updateValueRanges()
|
||||||
|
{
|
||||||
|
double xMin = std::numeric_limits<double>::infinity();
|
||||||
|
double xMax = -std::numeric_limits<double>::infinity();
|
||||||
|
double yMin = std::numeric_limits<double>::infinity();
|
||||||
|
double yMax = -std::numeric_limits<double>::infinity();
|
||||||
|
|
||||||
|
for ( const auto& [paramValue, pressureValue, summaryCase] : createCaseData() )
|
||||||
|
{
|
||||||
|
xMin = std::min( xMin, paramValue );
|
||||||
|
xMax = std::max( xMax, paramValue );
|
||||||
|
yMin = std::min( yMin, pressureValue );
|
||||||
|
yMax = std::max( yMax, pressureValue );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( xMin == std::numeric_limits<double>::infinity() )
|
||||||
|
{
|
||||||
|
m_xValueRange = std::nullopt;
|
||||||
|
m_yValueRange = std::nullopt;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double xRange = xMax - xMin;
|
||||||
|
const double yRange = yMax - yMin;
|
||||||
|
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimSummaryCase* RimParameterRftCrossPlot::findClosestCase( const QPoint& canvasPos )
|
||||||
|
{
|
||||||
|
auto caseData = createCaseData();
|
||||||
|
|
||||||
|
std::vector<std::pair<double, double>> points;
|
||||||
|
points.reserve( caseData.size() );
|
||||||
|
for ( const auto& d : caseData )
|
||||||
|
points.push_back( { d.parameterValue, d.pressureValue } );
|
||||||
|
|
||||||
|
int idx = RiuQwtCurveSelectorFilter::closestPointIndex( m_plotWidget->qwtPlot(), canvasPos, points );
|
||||||
|
return idx >= 0 ? caseData[idx].summaryCase : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimParameterRftCrossPlot::cleanupBeforeClose()
|
||||||
|
{
|
||||||
|
detachAllCurves();
|
||||||
|
|
||||||
|
if ( m_plotWidget )
|
||||||
|
{
|
||||||
|
m_plotWidget->setParent( nullptr );
|
||||||
|
delete m_plotWidget;
|
||||||
|
m_plotWidget = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Appearance/RimFontSizeField.h"
|
||||||
|
#include "RimPlot.h"
|
||||||
|
|
||||||
|
#include "cafPdmField.h"
|
||||||
|
#include "cafPdmPtrField.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class RimEclipseResultCase;
|
||||||
|
class RimSummaryEnsemble;
|
||||||
|
class RimSummaryCase;
|
||||||
|
class RiuQwtPlotWidget;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
///
|
||||||
|
/// Cross plot of ensemble parameter (X) vs mean RFT pressure (Y) within a MD depth range.
|
||||||
|
///
|
||||||
|
//==================================================================================================
|
||||||
|
class RimParameterRftCrossPlot : public RimPlot
|
||||||
|
{
|
||||||
|
Q_OBJECT;
|
||||||
|
CAF_PDM_HEADER_INIT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct CaseData
|
||||||
|
{
|
||||||
|
double parameterValue;
|
||||||
|
double pressureValue; // mean pressure in depth range
|
||||||
|
RimSummaryCase* summaryCase;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
RimParameterRftCrossPlot();
|
||||||
|
~RimParameterRftCrossPlot() override;
|
||||||
|
|
||||||
|
void setEnsemble( RimSummaryEnsemble* ensemble );
|
||||||
|
void setWellName( const QString& wellName );
|
||||||
|
void setTimeStep( const QDateTime& timeStep );
|
||||||
|
void setDepthRange( double minMd, double maxMd );
|
||||||
|
void setEnsembleParameter( const QString& paramName );
|
||||||
|
|
||||||
|
QString ensembleParameter() const;
|
||||||
|
QString wellName() const;
|
||||||
|
QDateTime selectedTimeStep() const;
|
||||||
|
RimSummaryEnsemble* ensemble() const;
|
||||||
|
|
||||||
|
RiuQwtPlotWidget* viewer();
|
||||||
|
|
||||||
|
std::vector<CaseData> createCaseData() const;
|
||||||
|
|
||||||
|
// Computes mean RFT pressure per ensemble case (one entry per case, infinity = no data).
|
||||||
|
// Indices match ensemble->allSummaryCases().
|
||||||
|
static std::vector<double> computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
|
||||||
|
const QString& wellName,
|
||||||
|
const QDateTime& timeStep,
|
||||||
|
RimEclipseResultCase* eclipseCase,
|
||||||
|
bool useDepthRange,
|
||||||
|
double depthRangeMin,
|
||||||
|
double depthRangeMax );
|
||||||
|
|
||||||
|
// RimPlot pure virtual overrides
|
||||||
|
RiuPlotWidget* plotWidget() override;
|
||||||
|
void setAutoScaleXEnabled( bool ) override {}
|
||||||
|
void setAutoScaleYEnabled( bool ) override {}
|
||||||
|
void updateAxes() override;
|
||||||
|
void updateLegend() override {}
|
||||||
|
QString asciiDataForPlotExport() const override;
|
||||||
|
void reattachAllCurves() override {}
|
||||||
|
void detachAllCurves() override;
|
||||||
|
QString description() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// RimViewWindow / RimPlotWindow overrides
|
||||||
|
QWidget* viewWidget() override;
|
||||||
|
void zoomAll() override {}
|
||||||
|
void deleteViewWidget() override;
|
||||||
|
void doUpdateLayout() override {}
|
||||||
|
void doRenderWindowContent( QPaintDevice* paintDevice ) override;
|
||||||
|
RiuPlotWidget* doCreatePlotViewWidget( QWidget* parent = nullptr ) override;
|
||||||
|
|
||||||
|
void onLoadDataAndUpdate() override;
|
||||||
|
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
|
||||||
|
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
||||||
|
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
|
||||||
|
|
||||||
|
void createPoints();
|
||||||
|
void updatePlotTitle();
|
||||||
|
void updateValueRanges();
|
||||||
|
void cleanupBeforeClose();
|
||||||
|
RimSummaryCase* findClosestCase( const QPoint& canvasPos );
|
||||||
|
|
||||||
|
private:
|
||||||
|
caf::PdmPtrField<RimSummaryEnsemble*> m_ensemble;
|
||||||
|
caf::PdmField<QString> m_wellName;
|
||||||
|
caf::PdmField<QDateTime> m_selectedTimeStep;
|
||||||
|
caf::PdmPtrField<RimEclipseResultCase*> m_eclipseCase;
|
||||||
|
caf::PdmField<bool> m_useDepthRange;
|
||||||
|
caf::PdmField<double> m_depthRangeMin;
|
||||||
|
caf::PdmField<double> m_depthRangeMax;
|
||||||
|
caf::PdmField<QString> m_ensembleParameter;
|
||||||
|
|
||||||
|
caf::PdmField<bool> m_useAutoPlotTitle;
|
||||||
|
caf::PdmField<QString> m_description;
|
||||||
|
|
||||||
|
RimFontSizeField m_axisTitleFontSize;
|
||||||
|
RimFontSizeField m_axisValueFontSize;
|
||||||
|
|
||||||
|
QPointer<RiuQwtPlotWidget> m_plotWidget;
|
||||||
|
|
||||||
|
std::optional<std::pair<double, double>> m_xValueRange;
|
||||||
|
std::optional<std::pair<double, double>> m_yValueRange;
|
||||||
|
};
|
||||||
@@ -0,0 +1,502 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 "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_correlationPlot, "CorrelationPlot", "Tornado Plot" );
|
||||||
|
|
||||||
|
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 );
|
||||||
|
|
||||||
|
setAsPlotMdiWindow();
|
||||||
|
|
||||||
|
m_showWindow = true;
|
||||||
|
m_showPlotLegends = false;
|
||||||
|
|
||||||
|
m_wellRftPlot = new RimWellRftPlot;
|
||||||
|
m_wellRftPlot->revokeMdiWindowStatus();
|
||||||
|
m_wellRftPlot->setShowWindow( true );
|
||||||
|
|
||||||
|
m_parameterRftCrossPlot = new RimParameterRftCrossPlot;
|
||||||
|
|
||||||
|
m_correlationPlot = new RimRftTornadoPlot;
|
||||||
|
m_correlationPlot->setParameterSelectedCallback( [this]( const QString& paramName ) { onTornadoParameterSelected( paramName ); } );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimRftCorrelationReportPlot::~RimRftCorrelationReportPlot()
|
||||||
|
{
|
||||||
|
removeMdiWindowFromMdiArea();
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
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_correlationPlot->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_correlationPlot->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_correlationPlot(), m_correlationPlot->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_correlationPlot->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_correlationPlot() ) m_correlationPlot->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() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
updateMdiWindowVisibility();
|
||||||
|
|
||||||
|
if ( m_showWindow )
|
||||||
|
{
|
||||||
|
m_wellRftPlot->loadDataAndUpdate();
|
||||||
|
syncTornadoInputsFromCrossPlot();
|
||||||
|
m_correlationPlot->loadDataAndUpdate();
|
||||||
|
m_parameterRftCrossPlot->loadDataAndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftCorrelationReportPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
||||||
|
{
|
||||||
|
// 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_correlationPlot() );
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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_correlationPlot )
|
||||||
|
m_correlationDockWidget->toggleView( m_correlationPlot->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::correlationPlot() const
|
||||||
|
{
|
||||||
|
return m_correlationPlot();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftCorrelationReportPlot::onTornadoParameterSelected( const QString& paramName )
|
||||||
|
{
|
||||||
|
if ( m_correlationPlot() )
|
||||||
|
{
|
||||||
|
m_correlationPlot->setSelectedParameter( paramName );
|
||||||
|
m_correlationPlot->loadDataAndUpdate();
|
||||||
|
}
|
||||||
|
if ( m_parameterRftCrossPlot() )
|
||||||
|
{
|
||||||
|
m_parameterRftCrossPlot->setEnsembleParameter( paramName );
|
||||||
|
m_parameterRftCrossPlot->loadDataAndUpdate();
|
||||||
|
}
|
||||||
|
updateConnectedEditors();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftCorrelationReportPlot::syncTornadoInputsFromCrossPlot()
|
||||||
|
{
|
||||||
|
if ( !m_correlationPlot() || !m_parameterRftCrossPlot() ) return;
|
||||||
|
|
||||||
|
m_correlationPlot->setEnsemble( m_parameterRftCrossPlot->ensemble() );
|
||||||
|
m_correlationPlot->setWellName( m_parameterRftCrossPlot->wellName() );
|
||||||
|
m_correlationPlot->setTimeStep( m_parameterRftCrossPlot->selectedTimeStep() );
|
||||||
|
m_correlationPlot->setSelectedParameter( m_parameterRftCrossPlot->ensembleParameter() );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftCorrelationReportPlot::updateDockTitleBarsVisibility()
|
||||||
|
{
|
||||||
|
if ( !m_dockManager ) return;
|
||||||
|
for ( auto* area : m_dockManager->openedDockAreas() )
|
||||||
|
area->titleBar()->setVisible( m_showDockTitleBars() );
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "RimPlotWindow.h"
|
||||||
|
|
||||||
|
#include "cafPdmChildField.h"
|
||||||
|
#include "cafPdmField.h"
|
||||||
|
#include "cafPdmProxyValueField.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class RimParameterRftCrossPlot;
|
||||||
|
class RimRftTornadoPlot;
|
||||||
|
class RimWellRftPlot;
|
||||||
|
|
||||||
|
namespace ads
|
||||||
|
{
|
||||||
|
class CDockManager;
|
||||||
|
class CDockWidget;
|
||||||
|
} // namespace ads
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
///
|
||||||
|
/// Composite report plot combining a RimWellRftPlot (pressure profile) and a
|
||||||
|
/// RimParameterRftCrossPlot (ensemble parameter vs mean pressure in a depth range).
|
||||||
|
///
|
||||||
|
//==================================================================================================
|
||||||
|
class RimRftCorrelationReportPlot : public QObject, public RimPlotWindow
|
||||||
|
{
|
||||||
|
CAF_PDM_HEADER_INIT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RimRftCorrelationReportPlot();
|
||||||
|
~RimRftCorrelationReportPlot() override;
|
||||||
|
|
||||||
|
QWidget* viewWidget() override;
|
||||||
|
QString description() const override;
|
||||||
|
void zoomAll() override {}
|
||||||
|
|
||||||
|
caf::PdmFieldHandle* userDescriptionField() override;
|
||||||
|
|
||||||
|
RimWellRftPlot* wellRftPlot() const;
|
||||||
|
RimParameterRftCrossPlot* crossPlot() const;
|
||||||
|
RimRftTornadoPlot* correlationPlot() const;
|
||||||
|
|
||||||
|
void initializeFromSourcePlot( RimWellRftPlot* source );
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString createDescription() const;
|
||||||
|
void recreatePlotWidgets();
|
||||||
|
void cleanupBeforeClose();
|
||||||
|
|
||||||
|
void setupBeforeSave() override;
|
||||||
|
void doRenderWindowContent( QPaintDevice* paintDevice ) override;
|
||||||
|
QWidget* createViewWidget( QWidget* mainWindowParent = nullptr ) override;
|
||||||
|
void deleteViewWidget() override;
|
||||||
|
void onLoadDataAndUpdate() override;
|
||||||
|
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
|
||||||
|
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override;
|
||||||
|
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
||||||
|
void childFieldChangedByUi( const caf::PdmFieldHandle* changedChildField ) override;
|
||||||
|
void doUpdateLayout() override {}
|
||||||
|
|
||||||
|
void updateDockTitleBarsVisibility();
|
||||||
|
void syncCrossPlotSelectionToRftPlot();
|
||||||
|
void onTornadoParameterSelected( const QString& paramName );
|
||||||
|
void syncTornadoInputsFromCrossPlot();
|
||||||
|
|
||||||
|
private:
|
||||||
|
caf::PdmProxyValueField<QString> m_name;
|
||||||
|
|
||||||
|
caf::PdmChildField<RimWellRftPlot*> m_wellRftPlot;
|
||||||
|
caf::PdmChildField<RimParameterRftCrossPlot*> m_parameterRftCrossPlot;
|
||||||
|
caf::PdmChildField<RimRftTornadoPlot*> m_correlationPlot;
|
||||||
|
|
||||||
|
caf::PdmField<bool> m_showDockTitleBars;
|
||||||
|
caf::PdmField<QString> m_dockState;
|
||||||
|
|
||||||
|
QWidget* m_viewWidget = nullptr;
|
||||||
|
QObject* m_contextMenuFilter = nullptr;
|
||||||
|
ads::CDockManager* m_dockManager = nullptr;
|
||||||
|
ads::CDockWidget* m_rftDockWidget = nullptr;
|
||||||
|
ads::CDockWidget* m_correlationDockWidget = nullptr;
|
||||||
|
ads::CDockWidget* m_crossPlotDockWidget = nullptr;
|
||||||
|
};
|
||||||
@@ -0,0 +1,478 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 "RimRftTornadoPlot.h"
|
||||||
|
|
||||||
|
#include "RiaColorTools.h"
|
||||||
|
#include "RiaPreferences.h"
|
||||||
|
|
||||||
|
#include "RigEnsembleParameter.h"
|
||||||
|
#include "RigStatisticsTools.h"
|
||||||
|
|
||||||
|
#include "RimEclipseResultCase.h"
|
||||||
|
#include "RimParameterRftCrossPlot.h"
|
||||||
|
#include "RimSummaryEnsemble.h"
|
||||||
|
#include "RimSummaryEnsembleTools.h"
|
||||||
|
|
||||||
|
#include "RiuContextMenuLauncher.h"
|
||||||
|
#include "RiuGroupedBarChartBuilder.h"
|
||||||
|
#include "RiuPlotItem.h"
|
||||||
|
#include "RiuQwtPlotItem.h"
|
||||||
|
#include "RiuQwtPlotWidget.h"
|
||||||
|
|
||||||
|
#include "cafPdmUiCheckBoxEditor.h"
|
||||||
|
|
||||||
|
#include "qwt_column_symbol.h"
|
||||||
|
#include "qwt_plot.h"
|
||||||
|
#include "qwt_plot_barchart.h"
|
||||||
|
#include "qwt_text.h"
|
||||||
|
|
||||||
|
#include <QPaintDevice>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <map>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
CAF_PDM_SOURCE_INIT( RimRftTornadoPlot, "RftTornadoPlot" );
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimRftTornadoPlot::RimRftTornadoPlot()
|
||||||
|
{
|
||||||
|
CAF_PDM_InitObject( "RFT Tornado Plot", ":/CorrelationTornadoPlot16x16.png" );
|
||||||
|
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_ensemble, "Ensemble", "Ensemble" );
|
||||||
|
CAF_PDM_InitField( &m_wellName, "WellName", QString(), "Well Name" );
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_selectedTimeStep, "TimeStep", "Time Step" );
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_eclipseCase, "EclipseCase", "Eclipse Case (MD fallback)" );
|
||||||
|
CAF_PDM_InitField( &m_useDepthRange, "UseDepthRange", false, "Filter by Depth Range" );
|
||||||
|
CAF_PDM_InitField( &m_depthRangeMin, "DepthRangeMin", 0.0, "Min Depth (MD)" );
|
||||||
|
CAF_PDM_InitField( &m_depthRangeMax, "DepthRangeMax", 5000.0, "Max Depth (MD)" );
|
||||||
|
|
||||||
|
CAF_PDM_InitField( &m_showAbsoluteValues, "ShowAbsoluteValues", false, "Show Absolute Values" );
|
||||||
|
CAF_PDM_InitField( &m_sortByAbsoluteValues, "SortByAbsoluteValues", true, "Sort by Absolute Values" );
|
||||||
|
CAF_PDM_InitField( &m_showOnlyTopNCorrelations, "ShowOnlyTopN", true, "Show Only Top Correlations" );
|
||||||
|
CAF_PDM_InitField( &m_topNFilterCount, "TopNFilterCount", 20, "Number of Rows" );
|
||||||
|
|
||||||
|
QColor qColor = QColor( "#3173b2" );
|
||||||
|
CAF_PDM_InitField( &m_barColor, "BarColor", RiaColorTools::fromQColorTo3f( qColor ), "Bar Color (Positive)" );
|
||||||
|
QColor highlightColor = QColor( "#f5a623" );
|
||||||
|
CAF_PDM_InitField( &m_highlightBarColor, "HighlightBarColor", RiaColorTools::fromQColorTo3f( highlightColor ), "Bar Color (Selected)" );
|
||||||
|
|
||||||
|
CAF_PDM_InitField( &m_useAutoPlotTitle, "UseAutoPlotTitle", true, "Auto Title" );
|
||||||
|
CAF_PDM_InitField( &m_description, "Description", QString( "RFT Tornado Plot" ), "Title" );
|
||||||
|
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_labelFontSize, "LabelFontSize", "Bar Label Font Size" );
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_axisTitleFontSize, "AxisTitleFontSize", "Axis Title Font Size" );
|
||||||
|
CAF_PDM_InitFieldNoDefault( &m_axisValueFontSize, "AxisValueFontSize", "Axis Value Font Size" );
|
||||||
|
|
||||||
|
m_labelFontSize = caf::FontTools::RelativeSize::Small;
|
||||||
|
m_axisTitleFontSize = caf::FontTools::RelativeSize::Small;
|
||||||
|
m_axisValueFontSize = caf::FontTools::RelativeSize::Small;
|
||||||
|
|
||||||
|
m_showPlotLegends = false;
|
||||||
|
setLegendsVisible( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimRftTornadoPlot::~RimRftTornadoPlot()
|
||||||
|
{
|
||||||
|
cleanupBeforeClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::setEnsemble( RimSummaryEnsemble* ensemble )
|
||||||
|
{
|
||||||
|
m_ensemble = ensemble;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::setWellName( const QString& wellName )
|
||||||
|
{
|
||||||
|
m_wellName = wellName;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::setTimeStep( const QDateTime& timeStep )
|
||||||
|
{
|
||||||
|
m_selectedTimeStep = timeStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::setEclipseCase( RimEclipseResultCase* eclipseCase )
|
||||||
|
{
|
||||||
|
m_eclipseCase = eclipseCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::setUseDepthRange( bool useDepthRange )
|
||||||
|
{
|
||||||
|
m_useDepthRange = useDepthRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::setDepthRange( double minMd, double maxMd )
|
||||||
|
{
|
||||||
|
m_depthRangeMin = minMd;
|
||||||
|
m_depthRangeMax = maxMd;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::setSelectedParameter( const QString& paramName )
|
||||||
|
{
|
||||||
|
m_selectedParameter = paramName;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::setParameterSelectedCallback( ParameterSelectedCallback callback )
|
||||||
|
{
|
||||||
|
m_parameterSelectedCallback = std::move( callback );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RiuQwtPlotWidget* RimRftTornadoPlot::viewer()
|
||||||
|
{
|
||||||
|
return m_plotWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RiuPlotWidget* RimRftTornadoPlot::plotWidget()
|
||||||
|
{
|
||||||
|
return m_plotWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::updateAxes()
|
||||||
|
{
|
||||||
|
if ( !m_plotWidget ) return;
|
||||||
|
|
||||||
|
const int titleSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisTitleFontSize() );
|
||||||
|
const int valueSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisValueFontSize() );
|
||||||
|
|
||||||
|
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultLeft(), "Parameter" );
|
||||||
|
m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultLeft(), true );
|
||||||
|
m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultLeft(), titleSize, valueSize, false, Qt::AlignCenter );
|
||||||
|
|
||||||
|
if ( m_showAbsoluteValues() )
|
||||||
|
{
|
||||||
|
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultBottom(), "Pearson Correlation Coefficient ABS" );
|
||||||
|
// Use a small negative margin so bar label text (anchored at x=0) remains visible
|
||||||
|
m_plotWidget->setAxisRange( RiuPlotAxis::defaultBottom(), -0.15, 1.0 );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultBottom(), "Pearson Correlation Coefficient" );
|
||||||
|
m_plotWidget->setAxisRange( RiuPlotAxis::defaultBottom(), -1.0, 1.0 );
|
||||||
|
}
|
||||||
|
m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultBottom(), true );
|
||||||
|
m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultBottom(), titleSize, valueSize, false, Qt::AlignCenter );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QString RimRftTornadoPlot::asciiDataForPlotExport() const
|
||||||
|
{
|
||||||
|
RiuGroupedBarChartBuilder chartBuilder;
|
||||||
|
addDataToChartBuilder( chartBuilder );
|
||||||
|
return chartBuilder.plotContentAsText();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::detachAllCurves()
|
||||||
|
{
|
||||||
|
if ( m_plotWidget )
|
||||||
|
{
|
||||||
|
m_plotWidget->qwtPlot()->detachItems( QwtPlotItem::Rtti_PlotBarChart );
|
||||||
|
m_plotWidget->qwtPlot()->detachItems( QwtPlotItem::Rtti_PlotScale );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QString RimRftTornadoPlot::description() const
|
||||||
|
{
|
||||||
|
return m_description();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
QWidget* RimRftTornadoPlot::viewWidget()
|
||||||
|
{
|
||||||
|
return m_plotWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::deleteViewWidget()
|
||||||
|
{
|
||||||
|
cleanupBeforeClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::doRenderWindowContent( QPaintDevice* paintDevice )
|
||||||
|
{
|
||||||
|
if ( m_plotWidget ) m_plotWidget->render( paintDevice );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RiuPlotWidget* RimRftTornadoPlot::doCreatePlotViewWidget( QWidget* parent )
|
||||||
|
{
|
||||||
|
if ( !m_plotWidget )
|
||||||
|
{
|
||||||
|
m_plotWidget = new RiuQwtPlotWidget( this, parent );
|
||||||
|
updatePlotTitle();
|
||||||
|
new RiuContextMenuLauncher( m_plotWidget, { "RicShowPlotDataFeature" } );
|
||||||
|
}
|
||||||
|
return m_plotWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::onLoadDataAndUpdate()
|
||||||
|
{
|
||||||
|
updateMdiWindowVisibility();
|
||||||
|
|
||||||
|
if ( m_plotWidget )
|
||||||
|
{
|
||||||
|
detachAllCurves();
|
||||||
|
|
||||||
|
RiuGroupedBarChartBuilder chartBuilder;
|
||||||
|
chartBuilder.setBarColor( RiaColorTools::toQColor( m_barColor() ) );
|
||||||
|
m_lastCorrelations = addDataToChartBuilder( chartBuilder );
|
||||||
|
const int labelSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_labelFontSize() );
|
||||||
|
chartBuilder.setLabelFontSize( labelSize );
|
||||||
|
chartBuilder.addBarChartToPlot( m_plotWidget->qwtPlot(), Qt::Horizontal, m_showOnlyTopNCorrelations() ? m_topNFilterCount() : -1 );
|
||||||
|
highlightSelectedParameterBar();
|
||||||
|
|
||||||
|
m_plotWidget->qwtPlot()->insertLegend( nullptr );
|
||||||
|
|
||||||
|
updateAxes();
|
||||||
|
updatePlotTitle();
|
||||||
|
m_plotWidget->scheduleReplot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
||||||
|
{
|
||||||
|
auto* settingsGroup = uiOrdering.addNewGroup( "Tornado Settings" );
|
||||||
|
settingsGroup->add( &m_showAbsoluteValues );
|
||||||
|
if ( !m_showAbsoluteValues() ) settingsGroup->add( &m_sortByAbsoluteValues );
|
||||||
|
settingsGroup->add( &m_showOnlyTopNCorrelations );
|
||||||
|
if ( m_showOnlyTopNCorrelations() ) settingsGroup->add( &m_topNFilterCount );
|
||||||
|
|
||||||
|
auto* colorGroup = uiOrdering.addNewGroup( "Colors" );
|
||||||
|
colorGroup->setCollapsedByDefault();
|
||||||
|
colorGroup->add( &m_barColor );
|
||||||
|
colorGroup->add( &m_highlightBarColor );
|
||||||
|
|
||||||
|
auto* plotGroup = uiOrdering.addNewGroup( "Plot Settings" );
|
||||||
|
plotGroup->setCollapsedByDefault();
|
||||||
|
plotGroup->add( &m_useAutoPlotTitle );
|
||||||
|
plotGroup->add( &m_description );
|
||||||
|
plotGroup->add( &m_labelFontSize );
|
||||||
|
plotGroup->add( &m_axisTitleFontSize );
|
||||||
|
plotGroup->add( &m_axisValueFontSize );
|
||||||
|
m_description.uiCapability()->setUiReadOnly( m_useAutoPlotTitle() );
|
||||||
|
|
||||||
|
uiOrdering.skipRemainingFields( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
|
||||||
|
{
|
||||||
|
RimPlot::fieldChangedByUi( changedField, oldValue, newValue );
|
||||||
|
loadDataAndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::onPlotItemSelected( std::shared_ptr<RiuPlotItem> plotItem, bool /*toggle*/, int /*sampleIndex*/ )
|
||||||
|
{
|
||||||
|
auto* qwtPlotItem = dynamic_cast<RiuQwtPlotItem*>( plotItem.get() );
|
||||||
|
if ( !qwtPlotItem ) return;
|
||||||
|
|
||||||
|
auto* barChart = dynamic_cast<QwtPlotBarChart*>( qwtPlotItem->qwtPlotItem() );
|
||||||
|
if ( barChart && m_parameterSelectedCallback ) m_parameterSelectedCallback( barChart->title().text() );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::map<QString, double> RimRftTornadoPlot::addDataToChartBuilder( RiuGroupedBarChartBuilder& chartBuilder ) const
|
||||||
|
{
|
||||||
|
std::map<QString, double> correlations;
|
||||||
|
|
||||||
|
if ( !m_ensemble() || m_wellName().isEmpty() || !m_selectedTimeStep().isValid() ) return correlations;
|
||||||
|
|
||||||
|
const auto& allCases = m_ensemble->allSummaryCases();
|
||||||
|
|
||||||
|
// Build pressure vector per case (indices match allCases order)
|
||||||
|
const std::vector<double> pressurePerCase = RimParameterRftCrossPlot::computeMeanPressurePerCase( m_ensemble(),
|
||||||
|
m_wellName(),
|
||||||
|
m_selectedTimeStep(),
|
||||||
|
m_eclipseCase(),
|
||||||
|
m_useDepthRange(),
|
||||||
|
m_depthRangeMin(),
|
||||||
|
m_depthRangeMax() );
|
||||||
|
|
||||||
|
// For each numeric parameter, compute Pearson correlation against pressurePerCase
|
||||||
|
for ( const auto& param : RimSummaryEnsembleTools::alphabeticEnsembleParameters( allCases ) )
|
||||||
|
{
|
||||||
|
if ( !param.isNumeric() ) continue;
|
||||||
|
if ( static_cast<size_t>( param.values.size() ) != allCases.size() ) continue;
|
||||||
|
|
||||||
|
std::vector<double> paramValues;
|
||||||
|
std::vector<double> pressureValues;
|
||||||
|
paramValues.reserve( allCases.size() );
|
||||||
|
pressureValues.reserve( allCases.size() );
|
||||||
|
|
||||||
|
for ( size_t i = 0; i < allCases.size(); ++i )
|
||||||
|
{
|
||||||
|
if ( std::isinf( pressurePerCase[i] ) ) continue;
|
||||||
|
paramValues.push_back( param.values[i].toDouble() );
|
||||||
|
pressureValues.push_back( pressurePerCase[i] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( paramValues.size() < 2 ) continue;
|
||||||
|
|
||||||
|
double pearson = RigStatisticsTools::pearsonCorrelation( paramValues, pressureValues );
|
||||||
|
if ( std::isinf( pearson ) || std::isnan( pearson ) ) continue;
|
||||||
|
|
||||||
|
correlations[param.name] = pearson;
|
||||||
|
|
||||||
|
double value = m_showAbsoluteValues() ? std::abs( pearson ) : pearson;
|
||||||
|
double sortValue = m_sortByAbsoluteValues() ? std::abs( value ) : value;
|
||||||
|
// legendText becomes barChart->title() and is used for click-to-select; must equal param.name.
|
||||||
|
// barText is shown on the axis label and can include the correlation value.
|
||||||
|
QString axisLabel = QString( "%1 (%2)" ).arg( param.name ).arg( pearson, 5, 'f', 2 );
|
||||||
|
chartBuilder.addBarEntry( "", "", "", sortValue, param.name, axisLabel, value );
|
||||||
|
}
|
||||||
|
|
||||||
|
return correlations;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::highlightSelectedParameterBar()
|
||||||
|
{
|
||||||
|
if ( !m_plotWidget ) return;
|
||||||
|
|
||||||
|
const QColor highlightColor = RiaColorTools::toQColor( m_highlightBarColor() );
|
||||||
|
const QColor barColor = RiaColorTools::toQColor( m_barColor() );
|
||||||
|
|
||||||
|
for ( QwtPlotItem* item : m_plotWidget->qwtPlot()->itemList( QwtPlotItem::Rtti_PlotBarChart ) )
|
||||||
|
{
|
||||||
|
auto* barChart = static_cast<QwtPlotBarChart*>( item );
|
||||||
|
auto* symbol = const_cast<QwtColumnSymbol*>( barChart->symbol() );
|
||||||
|
if ( !symbol ) continue;
|
||||||
|
|
||||||
|
const QString paramName = barChart->title().text();
|
||||||
|
|
||||||
|
QColor color;
|
||||||
|
if ( paramName == m_selectedParameter )
|
||||||
|
{
|
||||||
|
color = highlightColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
color = barColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPalette palette = symbol->palette();
|
||||||
|
palette.setColor( QPalette::Window, color );
|
||||||
|
palette.setColor( QPalette::Dark, color );
|
||||||
|
symbol->setPalette( palette );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::updatePlotTitle()
|
||||||
|
{
|
||||||
|
if ( !m_plotWidget ) return;
|
||||||
|
|
||||||
|
if ( m_useAutoPlotTitle() && m_ensemble() )
|
||||||
|
{
|
||||||
|
const QString rangeStr = m_useDepthRange() ? QString( " [MD %1 - %2 m]" ).arg( m_depthRangeMin() ).arg( m_depthRangeMax() )
|
||||||
|
: QString();
|
||||||
|
m_description = QString( "Parameter Correlation vs RFT Pressure%1, %2" ).arg( rangeStr ).arg( m_ensemble->name() );
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plotWidget->setPlotTitle( m_description() );
|
||||||
|
m_plotWidget->setPlotTitleEnabled( m_showPlotTitle() );
|
||||||
|
m_plotWidget->setPlotTitleFontSize( titleFontSize() );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimRftTornadoPlot::cleanupBeforeClose()
|
||||||
|
{
|
||||||
|
detachAllCurves();
|
||||||
|
|
||||||
|
if ( m_plotWidget )
|
||||||
|
{
|
||||||
|
m_plotWidget->setParent( nullptr );
|
||||||
|
delete m_plotWidget;
|
||||||
|
m_plotWidget = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "RimPlot.h"
|
||||||
|
|
||||||
|
#include "Appearance/RimFontSizeField.h"
|
||||||
|
|
||||||
|
#include "cafPdmField.h"
|
||||||
|
#include "cafPdmPtrField.h"
|
||||||
|
#include "cvfColor3.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
class RimEclipseResultCase;
|
||||||
|
class RimSummaryEnsemble;
|
||||||
|
class RiuQwtPlotWidget;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
///
|
||||||
|
/// Tornado plot showing Pearson correlation between ensemble parameters and mean RFT pressure
|
||||||
|
/// within a depth range. Used as a child of RimRftCorrelationReportPlot.
|
||||||
|
///
|
||||||
|
//==================================================================================================
|
||||||
|
class RimRftTornadoPlot : public RimPlot
|
||||||
|
{
|
||||||
|
Q_OBJECT;
|
||||||
|
CAF_PDM_HEADER_INIT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RimRftTornadoPlot();
|
||||||
|
~RimRftTornadoPlot() override;
|
||||||
|
|
||||||
|
using ParameterSelectedCallback = std::function<void( const QString& )>;
|
||||||
|
void setParameterSelectedCallback( ParameterSelectedCallback callback );
|
||||||
|
|
||||||
|
// Inputs set by the parent report plot
|
||||||
|
void setEnsemble( RimSummaryEnsemble* ensemble );
|
||||||
|
void setWellName( const QString& wellName );
|
||||||
|
void setTimeStep( const QDateTime& timeStep );
|
||||||
|
void setEclipseCase( RimEclipseResultCase* eclipseCase );
|
||||||
|
void setUseDepthRange( bool useDepthRange );
|
||||||
|
void setDepthRange( double minMd, double maxMd );
|
||||||
|
void setSelectedParameter( const QString& paramName );
|
||||||
|
|
||||||
|
RiuQwtPlotWidget* viewer();
|
||||||
|
|
||||||
|
// RimPlot pure virtual overrides
|
||||||
|
RiuPlotWidget* plotWidget() override;
|
||||||
|
void setAutoScaleXEnabled( bool ) override {}
|
||||||
|
void setAutoScaleYEnabled( bool ) override {}
|
||||||
|
void updateAxes() override;
|
||||||
|
void updateLegend() override {}
|
||||||
|
QString asciiDataForPlotExport() const override;
|
||||||
|
void reattachAllCurves() override {}
|
||||||
|
void detachAllCurves() override;
|
||||||
|
QString description() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWidget* viewWidget() override;
|
||||||
|
void zoomAll() override {}
|
||||||
|
void deleteViewWidget() override;
|
||||||
|
void doRenderWindowContent( QPaintDevice* paintDevice ) override;
|
||||||
|
RiuPlotWidget* doCreatePlotViewWidget( QWidget* parent = nullptr ) override;
|
||||||
|
|
||||||
|
void onLoadDataAndUpdate() override;
|
||||||
|
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
|
||||||
|
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
||||||
|
void onPlotItemSelected( std::shared_ptr<RiuPlotItem> plotItem, bool toggle, int sampleIndex ) override;
|
||||||
|
|
||||||
|
std::map<QString, double> addDataToChartBuilder( class RiuGroupedBarChartBuilder& chartBuilder ) const;
|
||||||
|
void highlightSelectedParameterBar();
|
||||||
|
void updatePlotTitle();
|
||||||
|
void cleanupBeforeClose();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Data source inputs
|
||||||
|
caf::PdmPtrField<RimSummaryEnsemble*> m_ensemble;
|
||||||
|
caf::PdmField<QString> m_wellName;
|
||||||
|
caf::PdmField<QDateTime> m_selectedTimeStep;
|
||||||
|
caf::PdmPtrField<RimEclipseResultCase*> m_eclipseCase;
|
||||||
|
caf::PdmField<bool> m_useDepthRange;
|
||||||
|
caf::PdmField<double> m_depthRangeMin;
|
||||||
|
caf::PdmField<double> m_depthRangeMax;
|
||||||
|
|
||||||
|
// Tornado settings
|
||||||
|
caf::PdmField<bool> m_showAbsoluteValues;
|
||||||
|
caf::PdmField<bool> m_sortByAbsoluteValues;
|
||||||
|
caf::PdmField<bool> m_showOnlyTopNCorrelations;
|
||||||
|
caf::PdmField<int> m_topNFilterCount;
|
||||||
|
caf::PdmField<cvf::Color3f> m_barColor;
|
||||||
|
caf::PdmField<cvf::Color3f> m_highlightBarColor;
|
||||||
|
|
||||||
|
caf::PdmField<bool> m_useAutoPlotTitle;
|
||||||
|
caf::PdmField<QString> m_description;
|
||||||
|
|
||||||
|
RimFontSizeField m_labelFontSize;
|
||||||
|
RimFontSizeField m_axisTitleFontSize;
|
||||||
|
RimFontSizeField m_axisValueFontSize;
|
||||||
|
|
||||||
|
ParameterSelectedCallback m_parameterSelectedCallback;
|
||||||
|
QString m_selectedParameter;
|
||||||
|
std::map<QString, double> m_lastCorrelations; // param.name -> pearson value, updated in onLoadDataAndUpdate
|
||||||
|
|
||||||
|
QPointer<RiuQwtPlotWidget> m_plotWidget;
|
||||||
|
};
|
||||||
@@ -76,8 +76,10 @@ RifDataSourceForRftPlt RimDataSourceForRftPlt::address() const
|
|||||||
case RifDataSourceForRftPlt::SourceType::OBSERVED_LAS_FILE:
|
case RifDataSourceForRftPlt::SourceType::OBSERVED_LAS_FILE:
|
||||||
return RifDataSourceForRftPlt( m_wellLogFile );
|
return RifDataSourceForRftPlt( m_wellLogFile );
|
||||||
case RifDataSourceForRftPlt::SourceType::RFT_SIM_WELL_DATA:
|
case RifDataSourceForRftPlt::SourceType::RFT_SIM_WELL_DATA:
|
||||||
|
if ( !m_eclCase ) return RifDataSourceForRftPlt();
|
||||||
return RifDataSourceForRftPlt( RifDataSourceForRftPlt::SourceType::RFT_SIM_WELL_DATA, m_eclCase );
|
return RifDataSourceForRftPlt( RifDataSourceForRftPlt::SourceType::RFT_SIM_WELL_DATA, m_eclCase );
|
||||||
case RifDataSourceForRftPlt::SourceType::GRID_MODEL_CELL_DATA:
|
case RifDataSourceForRftPlt::SourceType::GRID_MODEL_CELL_DATA:
|
||||||
|
if ( !m_eclCase ) return RifDataSourceForRftPlt();
|
||||||
return RifDataSourceForRftPlt( RifDataSourceForRftPlt::SourceType::GRID_MODEL_CELL_DATA, m_eclCase );
|
return RifDataSourceForRftPlt( RifDataSourceForRftPlt::SourceType::GRID_MODEL_CELL_DATA, m_eclCase );
|
||||||
case RifDataSourceForRftPlt::SourceType::SUMMARY_RFT:
|
case RifDataSourceForRftPlt::SourceType::SUMMARY_RFT:
|
||||||
return RifDataSourceForRftPlt( m_summaryCase, m_ensemble, m_eclCase );
|
return RifDataSourceForRftPlt( m_summaryCase, m_ensemble, m_eclCase );
|
||||||
|
|||||||
@@ -58,6 +58,8 @@
|
|||||||
|
|
||||||
#include "RiuAbstractLegendFrame.h"
|
#include "RiuAbstractLegendFrame.h"
|
||||||
#include "RiuDraggableOverlayFrame.h"
|
#include "RiuDraggableOverlayFrame.h"
|
||||||
|
#include "RiuQwtCurveSelectorFilter.h"
|
||||||
|
|
||||||
#include "RiuPlotCurve.h"
|
#include "RiuPlotCurve.h"
|
||||||
#include "RiuPlotItem.h"
|
#include "RiuPlotItem.h"
|
||||||
#include "RiuPlotMainWindowTools.h"
|
#include "RiuPlotMainWindowTools.h"
|
||||||
@@ -66,13 +68,17 @@
|
|||||||
#include "RiuQwtPlotItem.h"
|
#include "RiuQwtPlotItem.h"
|
||||||
#include "RiuQwtPlotWidget.h"
|
#include "RiuQwtPlotWidget.h"
|
||||||
|
|
||||||
|
#include "cafCmdFeatureMenuBuilder.h"
|
||||||
#include "cafPdmPointer.h"
|
#include "cafPdmPointer.h"
|
||||||
#include "cafPdmUiTreeOrdering.h"
|
#include "cafPdmUiTreeOrdering.h"
|
||||||
#include "cafPdmUiTreeSelectionEditor.h"
|
#include "cafPdmUiTreeSelectionEditor.h"
|
||||||
#include "cafSelectionManager.h"
|
#include "cafSelectionManager.h"
|
||||||
|
|
||||||
|
#include "qwt_plot.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
#include <limits>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
CAF_PDM_SOURCE_INIT( RimWellRftPlot, "WellRftPlot" );
|
CAF_PDM_SOURCE_INIT( RimWellRftPlot, "WellRftPlot" );
|
||||||
@@ -186,6 +192,29 @@ void RimWellRftPlot::applyCurveAppearance( RimWellLogCurve* curve )
|
|||||||
auto curveSet = findEnsembleCurveSet( curveDef.address().ensemble() );
|
auto curveSet = findEnsembleCurveSet( curveDef.address().ensemble() );
|
||||||
if ( curveSet && curveSet != m_highlightedCurveSet ) curve->setCurveColorOpacity( 0.1f );
|
if ( curveSet && curveSet != m_highlightedCurveSet ) curve->setCurveColorOpacity( 0.1f );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highlight the selected realization using a contrast color and raised z-order.
|
||||||
|
// Other ensemble member curves have their z-order restored to normal so this does not
|
||||||
|
// compete with the ensemble-level opacity-dim mechanism.
|
||||||
|
// Statistics curves (ENSEMBLE_RFT) and standalone case curves are never changed.
|
||||||
|
if ( curveDef.address().sourceType() == RifDataSourceForRftPlt::SourceType::SUMMARY_RFT && curveDef.address().ensemble() )
|
||||||
|
{
|
||||||
|
auto* rftCurve = dynamic_cast<RimWellLogRftCurve*>( curve );
|
||||||
|
if ( rftCurve )
|
||||||
|
{
|
||||||
|
if ( m_selectedRealization && rftCurve->summaryCase() == m_selectedRealization )
|
||||||
|
{
|
||||||
|
curve->setColor( RiaColorTools::fromQColorTo3f( QColor( "#e8572a" ) ) );
|
||||||
|
curve->setLineThickness( 3 );
|
||||||
|
curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_HIGHLIGHTED_CURVE ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
curve->setLineThickness( 1 );
|
||||||
|
curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_ENSEMBLE_CURVE ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -562,7 +591,7 @@ void RimWellRftPlot::updateCurvesInPlot( const std::set<RiaRftPltCurveDefinition
|
|||||||
curveDefToAdd.timeStep(),
|
curveDefToAdd.timeStep(),
|
||||||
RifEclipseRftAddress::RftWellLogChannelType::PRESSURE );
|
RifEclipseRftAddress::RftWellLogChannelType::PRESSURE );
|
||||||
curve->setRftAddress( address );
|
curve->setRftAddress( address );
|
||||||
curve->setZOrder( 1 );
|
curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_NON_OBSERVED ) );
|
||||||
curve->setSimWellBranchData( m_branchDetection, m_branchIndex );
|
curve->setSimWellBranchData( m_branchDetection, m_branchIndex );
|
||||||
|
|
||||||
applyCurveAppearance( curve );
|
applyCurveAppearance( curve );
|
||||||
@@ -617,7 +646,7 @@ void RimWellRftPlot::updateCurvesInPlot( const std::set<RiaRftPltCurveDefinition
|
|||||||
curve->setRftAddress( address );
|
curve->setRftAddress( address );
|
||||||
curve->setEclipseCase( eclipeCase );
|
curve->setEclipseCase( eclipeCase );
|
||||||
|
|
||||||
double zValue = 1.0;
|
double zValue = RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_ENSEMBLE_CURVE );
|
||||||
if ( !curveDefToAdd.address().ensemble() )
|
if ( !curveDefToAdd.address().ensemble() )
|
||||||
{
|
{
|
||||||
zValue = RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_NON_OBSERVED );
|
zValue = RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_NON_OBSERVED );
|
||||||
@@ -707,7 +736,7 @@ void RimWellRftPlot::updateCurvesInPlot( const std::set<RiaRftPltCurveDefinition
|
|||||||
}
|
}
|
||||||
|
|
||||||
curve->setCurrentTimeStep( currentTimeStepIndex );
|
curve->setCurrentTimeStep( currentTimeStepIndex );
|
||||||
curve->setZOrder( 0 );
|
curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_ENSEMBLE_CURVE ) );
|
||||||
|
|
||||||
applyCurveAppearance( curve );
|
applyCurveAppearance( curve );
|
||||||
}
|
}
|
||||||
@@ -725,7 +754,7 @@ void RimWellRftPlot::updateCurvesInPlot( const std::set<RiaRftPltCurveDefinition
|
|||||||
curve->setWellPath( wellPath );
|
curve->setWellPath( wellPath );
|
||||||
curve->setWellLog( wellLogFile );
|
curve->setWellLog( wellLogFile );
|
||||||
curve->setWellLogChannelName( pressureChannel->name() );
|
curve->setWellLogChannelName( pressureChannel->name() );
|
||||||
curve->setZOrder( 2 );
|
curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_NON_OBSERVED ) );
|
||||||
|
|
||||||
applyCurveAppearance( curve );
|
applyCurveAppearance( curve );
|
||||||
}
|
}
|
||||||
@@ -734,6 +763,18 @@ void RimWellRftPlot::updateCurvesInPlot( const std::set<RiaRftPltCurveDefinition
|
|||||||
|
|
||||||
if ( auto qwtWidget = dynamic_cast<RiuQwtPlotWidget*>( plotTrack->plotWidget() ) )
|
if ( auto qwtWidget = dynamic_cast<RiuQwtPlotWidget*>( plotTrack->plotWidget() ) )
|
||||||
{
|
{
|
||||||
|
// Install click-to-select filter once (guard against repeated calls to updateCurvesInPlot)
|
||||||
|
auto* canvas = qwtWidget->qwtPlot()->canvas();
|
||||||
|
const char* filterPropName = "rftCurveSelectorInstalled";
|
||||||
|
if ( !canvas->property( filterPropName ).toBool() )
|
||||||
|
{
|
||||||
|
caf::PdmPointer<RimWellRftPlot> self( this );
|
||||||
|
new RiuQwtCurveSelectorFilter( qwtWidget->qwtPlot(),
|
||||||
|
[self]( const QPoint& pos ) -> const caf::PdmUiItem*
|
||||||
|
{ return self ? self->findClosestRealization( pos ) : nullptr; } );
|
||||||
|
canvas->setProperty( filterPropName, true );
|
||||||
|
}
|
||||||
|
|
||||||
// Connect legend item clicks to select the corresponding ensemble curve set in the project tree.
|
// Connect legend item clicks to select the corresponding ensemble curve set in the project tree.
|
||||||
// Disconnect previous connection first to avoid duplicates (non-QObject lambda connections).
|
// Disconnect previous connection first to avoid duplicates (non-QObject lambda connections).
|
||||||
// Use a guarded PdmPointer to avoid use-after-free if the signal fires after this object is destroyed.
|
// Use a guarded PdmPointer to avoid use-after-free if the signal fires after this object is destroyed.
|
||||||
@@ -836,6 +877,7 @@ void RimWellRftPlot::setSimWellOrWellPathName( const QString& currWellName )
|
|||||||
{
|
{
|
||||||
m_wellPathNameOrSimWellName = "None";
|
m_wellPathNameOrSimWellName = "None";
|
||||||
}
|
}
|
||||||
|
m_nameConfig->setCustomName( QString( plotNameFormatString() ).arg( m_wellPathNameOrSimWellName ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -1668,6 +1710,22 @@ void RimWellRftPlot::detachAndDeleteLegendCurves()
|
|||||||
m_legendCurveToEnsembleCurveSet.clear();
|
m_legendCurveToEnsembleCurveSet.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimWellRftPlot::cleanupLegendCurves()
|
||||||
|
{
|
||||||
|
detachAndDeleteLegendCurves();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimWellRftPlot::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
|
||||||
|
{
|
||||||
|
menuBuilder << "RicCreateRftCorrelationReportFeature";
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
///
|
///
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -1688,12 +1746,68 @@ RimWellRftEnsembleCurveSet* RimWellRftPlot::selectedEnsembleCurveSet() const
|
|||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
void RimWellRftPlot::onSelectionManagerSelectionChanged( const std::set<int>& /*changedSelectionLevels*/ )
|
void RimWellRftPlot::onSelectionManagerSelectionChanged( const std::set<int>& /*changedSelectionLevels*/ )
|
||||||
{
|
{
|
||||||
auto newSelection = selectedEnsembleCurveSet();
|
auto newCurveSet = selectedEnsembleCurveSet();
|
||||||
if ( newSelection != m_highlightedCurveSet )
|
if ( newCurveSet != m_highlightedCurveSet )
|
||||||
{
|
{
|
||||||
m_highlightedCurveSet = newSelection;
|
m_highlightedCurveSet = newCurveSet;
|
||||||
loadDataAndUpdate();
|
loadDataAndUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto* newRealization = caf::SelectionManager::instance()->selectedItemOfType<RimSummaryCase>();
|
||||||
|
if ( newRealization != m_selectedRealization )
|
||||||
|
{
|
||||||
|
m_selectedRealization = newRealization;
|
||||||
|
highlightSelectedRealization();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RimSummaryCase* RimWellRftPlot::findClosestRealization( const QPoint& canvasPos )
|
||||||
|
{
|
||||||
|
auto* track = dynamic_cast<RimWellLogTrack*>( plotByIndex( 0 ) );
|
||||||
|
if ( !track ) return nullptr;
|
||||||
|
|
||||||
|
double minDist = 15.0; // pixel threshold
|
||||||
|
RimSummaryCase* closestCase = nullptr;
|
||||||
|
for ( RimWellLogCurve* curve : track->curves() )
|
||||||
|
{
|
||||||
|
auto* rftCurve = dynamic_cast<RimWellLogRftCurve*>( curve );
|
||||||
|
if ( !rftCurve || !rftCurve->summaryCase() || !rftCurve->ensemble() ) continue;
|
||||||
|
auto* qwtCurve = dynamic_cast<RiuQwtPlotCurve*>( rftCurve->plotCurve() );
|
||||||
|
if ( !qwtCurve ) continue;
|
||||||
|
double dist = std::numeric_limits<double>::max();
|
||||||
|
qwtCurve->closestPoint( canvasPos, &dist );
|
||||||
|
if ( dist < minDist )
|
||||||
|
{
|
||||||
|
minDist = dist;
|
||||||
|
closestCase = rftCurve->summaryCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle highlight: clicking the selected realization again clears it.
|
||||||
|
m_selectedRealization = ( closestCase != m_selectedRealization ) ? closestCase : nullptr;
|
||||||
|
highlightSelectedRealization();
|
||||||
|
|
||||||
|
return closestCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimWellRftPlot::highlightSelectedRealization()
|
||||||
|
{
|
||||||
|
auto* track = dynamic_cast<RimWellLogTrack*>( plotByIndex( 0 ) );
|
||||||
|
if ( !track ) return;
|
||||||
|
|
||||||
|
for ( RimWellLogCurve* curve : track->curves() )
|
||||||
|
{
|
||||||
|
applyCurveAppearance( curve );
|
||||||
|
curve->updateCurveAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( track->plotWidget() ) track->plotWidget()->scheduleReplot();
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -1741,6 +1855,22 @@ void RimWellRftPlot::rebuildCurves()
|
|||||||
syncCurvesFromUiSelection();
|
syncCurvesFromUiSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::vector<QDateTime> RimWellRftPlot::selectedTimeSteps() const
|
||||||
|
{
|
||||||
|
return m_selectedTimeSteps();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimWellRftPlot::setSelectedTimeSteps( const std::vector<QDateTime>& timeSteps )
|
||||||
|
{
|
||||||
|
m_selectedTimeSteps = timeSteps;
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
///
|
///
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "cafPdmField.h"
|
#include "cafPdmField.h"
|
||||||
#include "cafPdmObject.h"
|
#include "cafPdmObject.h"
|
||||||
|
#include "cafPdmPointer.h"
|
||||||
#include "cafPdmPtrField.h"
|
#include "cafPdmPtrField.h"
|
||||||
|
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
@@ -52,6 +53,7 @@ class RifDataSourceForRftPlt;
|
|||||||
class RifEclipseRftAddress;
|
class RifEclipseRftAddress;
|
||||||
class RiuDraggableOverlayFrame;
|
class RiuDraggableOverlayFrame;
|
||||||
class RimDataSourceForRftPlt;
|
class RimDataSourceForRftPlt;
|
||||||
|
class RimSummaryCase;
|
||||||
class RiuPlotCurve;
|
class RiuPlotCurve;
|
||||||
class RiuPlotItem;
|
class RiuPlotItem;
|
||||||
class RimWellRftEnsembleCurveSet;
|
class RimWellRftEnsembleCurveSet;
|
||||||
@@ -101,6 +103,17 @@ public:
|
|||||||
|
|
||||||
void initializeDataSources( RimWellRftPlot* source );
|
void initializeDataSources( RimWellRftPlot* source );
|
||||||
|
|
||||||
|
std::vector<QDateTime> selectedTimeSteps() const;
|
||||||
|
void setSelectedTimeSteps( const std::vector<QDateTime>& timeSteps );
|
||||||
|
|
||||||
|
// Called by owning composite plots before the underlying QwtPlot is destroyed
|
||||||
|
// so that legend curve raw pointers are cleaned up before QwtPlot auto-deletes them.
|
||||||
|
void cleanupLegendCurves();
|
||||||
|
|
||||||
|
std::vector<RimSummaryEnsemble*> selectedEnsembles() const;
|
||||||
|
|
||||||
|
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
||||||
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) override;
|
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) override;
|
||||||
@@ -143,7 +156,9 @@ private:
|
|||||||
void onSelectionManagerSelectionChanged( const std::set<int>& changedSelectionLevels ) override;
|
void onSelectionManagerSelectionChanged( const std::set<int>& changedSelectionLevels ) override;
|
||||||
RimWellRftEnsembleCurveSet* selectedEnsembleCurveSet() const;
|
RimWellRftEnsembleCurveSet* selectedEnsembleCurveSet() const;
|
||||||
|
|
||||||
void onLegendItemClicked( std::shared_ptr<RiuPlotItem> plotItem, bool toggle, int sampleIndex );
|
void onLegendItemClicked( std::shared_ptr<RiuPlotItem> plotItem, bool toggle, int sampleIndex );
|
||||||
|
RimSummaryCase* findClosestRealization( const QPoint& canvasPos );
|
||||||
|
void highlightSelectedRealization();
|
||||||
|
|
||||||
void updateFormationsOnPlot() const;
|
void updateFormationsOnPlot() const;
|
||||||
QString associatedSimWellName() const;
|
QString associatedSimWellName() const;
|
||||||
@@ -154,8 +169,7 @@ private:
|
|||||||
cvf::Color3f findCurveColor( RimWellLogCurve* curve );
|
cvf::Color3f findCurveColor( RimWellLogCurve* curve );
|
||||||
void defineCurveColorsAndSymbols( const std::set<RiaRftPltCurveDefinition>& allCurveDefs );
|
void defineCurveColorsAndSymbols( const std::set<RiaRftPltCurveDefinition>& allCurveDefs );
|
||||||
|
|
||||||
std::vector<RimSummaryEnsemble*> selectedEnsembles() const;
|
void createEnsembleCurveSets();
|
||||||
void createEnsembleCurveSets();
|
|
||||||
|
|
||||||
void detachAndDeleteLegendCurves();
|
void detachAndDeleteLegendCurves();
|
||||||
|
|
||||||
@@ -186,6 +200,7 @@ private:
|
|||||||
std::map<RiuPlotCurve*, RimWellRftEnsembleCurveSet*> m_legendCurveToEnsembleCurveSet;
|
std::map<RiuPlotCurve*, RimWellRftEnsembleCurveSet*> m_legendCurveToEnsembleCurveSet;
|
||||||
QMetaObject::Connection m_legendClickedConnection;
|
QMetaObject::Connection m_legendClickedConnection;
|
||||||
RimWellRftEnsembleCurveSet* m_highlightedCurveSet = nullptr;
|
RimWellRftEnsembleCurveSet* m_highlightedCurveSet = nullptr;
|
||||||
|
caf::PdmPointer<RimSummaryCase> m_selectedRealization;
|
||||||
|
|
||||||
caf::PdmChildField<RimWellLogPlot*> m_wellLogPlot_OBSOLETE;
|
caf::PdmChildField<RimWellLogPlot*> m_wellLogPlot_OBSOLETE;
|
||||||
bool m_isInitialized = false;
|
bool m_isInitialized = false;
|
||||||
|
|||||||
@@ -626,6 +626,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection()
|
|||||||
else if ( dynamic_cast<RimWellRftPlot*>( firstUiItem ) )
|
else if ( dynamic_cast<RimWellRftPlot*>( firstUiItem ) )
|
||||||
{
|
{
|
||||||
menuBuilder << "RicCreateRftPlotsFeature";
|
menuBuilder << "RicCreateRftPlotsFeature";
|
||||||
|
menuBuilder << "RicCreateRftCorrelationReportFeature";
|
||||||
}
|
}
|
||||||
else if ( dynamic_cast<RimWellLogPlot*>( firstUiItem ) && !dynamic_cast<RimWellPltPlot*>( firstUiItem ) )
|
else if ( dynamic_cast<RimWellLogPlot*>( firstUiItem ) && !dynamic_cast<RimWellPltPlot*>( firstUiItem ) )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "RimEnsembleCurveSet.h"
|
#include "RimEnsembleCurveSet.h"
|
||||||
#include "RimMainPlotCollection.h"
|
#include "RimMainPlotCollection.h"
|
||||||
#include "RimParameterResultCrossPlot.h"
|
#include "RimParameterResultCrossPlot.h"
|
||||||
|
#include "RimParameterRftCrossPlot.h"
|
||||||
#include "RimSummaryCase.h"
|
#include "RimSummaryCase.h"
|
||||||
#include "RimSummaryCurve.h"
|
#include "RimSummaryCurve.h"
|
||||||
#include "RimSummaryMultiPlot.h"
|
#include "RimSummaryMultiPlot.h"
|
||||||
@@ -451,6 +452,12 @@ void RimSummaryEnsembleTools::highlightCurvesForSummaryCases( const std::vector<
|
|||||||
{
|
{
|
||||||
c->loadDataAndUpdate();
|
c->loadDataAndUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto rftCrossPlots = correlationPlotColl->descendantsIncludingThisOfType<RimParameterRftCrossPlot>();
|
||||||
|
for ( auto c : rftCrossPlots )
|
||||||
|
{
|
||||||
|
c->loadDataAndUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -385,11 +385,24 @@ std::vector<double> interpolateMdFromTvd( const std::vector<double>& originalMdV
|
|||||||
auto splinePoints = Internal::createSplinePoints( originalMdValues, originalTvdValues );
|
auto splinePoints = Internal::createSplinePoints( originalMdValues, originalTvdValues );
|
||||||
std::vector<int> segmentStartIndices = Internal::findSplineSegmentsContainingRoots( splinePoints, tvdValuesToInterpolateFrom );
|
std::vector<int> segmentStartIndices = Internal::findSplineSegmentsContainingRoots( splinePoints, tvdValuesToInterpolateFrom );
|
||||||
|
|
||||||
|
// Track the last two valid (non-infinity) interpolated MD values so that infinity
|
||||||
|
// entries in the input do not corrupt the startMD search interval for later valid entries.
|
||||||
|
double lastValidMd = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
double prevLastValidMd = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
|
||||||
for ( size_t i = 0; i < segmentStartIndices.size(); ++i )
|
for ( size_t i = 0; i < segmentStartIndices.size(); ++i )
|
||||||
{
|
{
|
||||||
double currentTVDValue = tvdValuesToInterpolateFrom[i];
|
double currentTVDValue = tvdValuesToInterpolateFrom[i];
|
||||||
double startMD = splinePoints.front().x();
|
|
||||||
double endMD = splinePoints.back().y();
|
// Infinity TVD entries are handled in the second pass below; push a placeholder here.
|
||||||
|
if ( std::isinf( currentTVDValue ) )
|
||||||
|
{
|
||||||
|
interpolatedMdValues.push_back( 0.0 );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double startMD = splinePoints.front().x();
|
||||||
|
double endMD = splinePoints.back().y();
|
||||||
if ( segmentStartIndices[i] != -1 )
|
if ( segmentStartIndices[i] != -1 )
|
||||||
{
|
{
|
||||||
int startIndex = segmentStartIndices[i];
|
int startIndex = segmentStartIndices[i];
|
||||||
@@ -399,23 +412,62 @@ std::vector<double> interpolateMdFromTvd( const std::vector<double>& originalMdV
|
|||||||
startMD = splinePoints[startIndex].x();
|
startMD = splinePoints[startIndex].x();
|
||||||
endMD = splinePoints.back().y();
|
endMD = splinePoints.back().y();
|
||||||
|
|
||||||
if ( endIndex < splinePoints.size() )
|
if ( endIndex < (int)splinePoints.size() )
|
||||||
{
|
{
|
||||||
if ( !interpolatedMdValues.empty() )
|
if ( !std::isnan( lastValidMd ) )
|
||||||
{
|
{
|
||||||
double mdDiff = 0.0;
|
double mdDiff = std::isnan( prevLastValidMd ) ? 0.0 : ( lastValidMd - prevLastValidMd );
|
||||||
if ( interpolatedMdValues.size() > 1 )
|
startMD = std::max( startMD, lastValidMd + 0.1 * mdDiff );
|
||||||
{
|
|
||||||
mdDiff = interpolatedMdValues[i - 1] - interpolatedMdValues[i - 2];
|
|
||||||
}
|
|
||||||
startMD = std::max( startMD, interpolatedMdValues.back() + 0.1 * mdDiff );
|
|
||||||
}
|
}
|
||||||
endMD = splinePoints[endIndex].x();
|
endMD = splinePoints[endIndex].x();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
double mdValue = Internal::solveForX( splinePoints, startMD, endMD, currentTVDValue );
|
double mdValue = Internal::solveForX( splinePoints, startMD, endMD, currentTVDValue );
|
||||||
interpolatedMdValues.push_back( mdValue );
|
interpolatedMdValues.push_back( mdValue );
|
||||||
|
|
||||||
|
prevLastValidMd = lastValidMd;
|
||||||
|
lastValidMd = mdValue;
|
||||||
}
|
}
|
||||||
|
// Second pass: linearly interpolate MD for any infinity-TVD entries so the result is monotonically rising.
|
||||||
|
for ( size_t i = 0; i < tvdValuesToInterpolateFrom.size(); ++i )
|
||||||
|
{
|
||||||
|
if ( !std::isinf( tvdValuesToInterpolateFrom[i] ) ) continue;
|
||||||
|
|
||||||
|
int prevValid = -1;
|
||||||
|
int nextValid = -1;
|
||||||
|
for ( int j = (int)i - 1; j >= 0; --j )
|
||||||
|
{
|
||||||
|
if ( !std::isinf( tvdValuesToInterpolateFrom[j] ) )
|
||||||
|
{
|
||||||
|
prevValid = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ( int j = (int)i + 1; j < (int)tvdValuesToInterpolateFrom.size(); ++j )
|
||||||
|
{
|
||||||
|
if ( !std::isinf( tvdValuesToInterpolateFrom[j] ) )
|
||||||
|
{
|
||||||
|
nextValid = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( prevValid != -1 && nextValid != -1 )
|
||||||
|
{
|
||||||
|
double t = (double)( i - prevValid ) / (double)( nextValid - prevValid );
|
||||||
|
interpolatedMdValues[i] = interpolatedMdValues[prevValid] +
|
||||||
|
t * ( interpolatedMdValues[nextValid] - interpolatedMdValues[prevValid] );
|
||||||
|
}
|
||||||
|
else if ( prevValid != -1 )
|
||||||
|
{
|
||||||
|
interpolatedMdValues[i] = interpolatedMdValues[prevValid];
|
||||||
|
}
|
||||||
|
else if ( nextValid != -1 )
|
||||||
|
{
|
||||||
|
interpolatedMdValues[i] = interpolatedMdValues[nextValid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return interpolatedMdValues;
|
return interpolatedMdValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <complex>
|
#include <complex>
|
||||||
|
#include <limits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define TOLERANCE 1.0e-7
|
#define TOLERANCE 1.0e-7
|
||||||
@@ -177,3 +178,89 @@ TEST( RigWellPathGeometryTools, CubicPathPoorSampling )
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/// When infinity is passed as a TVD value (e.g. for invalid grid cells), the function must not
|
||||||
|
/// crash and must return a result vector of the same size as the input.
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigWellPathGeometryTools, InfinityTvdValues_OutputSizeMatchesInput )
|
||||||
|
{
|
||||||
|
const double inf = std::numeric_limits<double>::infinity();
|
||||||
|
|
||||||
|
std::vector<double> mdValues = { 100, 500, 1000 };
|
||||||
|
std::vector<double> tvdValues = { 100, 500, 1000 };
|
||||||
|
|
||||||
|
// All entries are infinity
|
||||||
|
std::vector<double> allInfTvd = { inf, inf, inf };
|
||||||
|
std::vector<double> result = RigWellPathGeometryTools::interpolateMdFromTvd( mdValues, tvdValues, allInfTvd );
|
||||||
|
EXPECT_EQ( result.size(), allInfTvd.size() );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/// Valid TVD entries that are mixed with infinity entries must still yield correct MD values.
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigWellPathGeometryTools, InfinityTvdValues_ValidEntriesStillInterpolatedCorrectly )
|
||||||
|
{
|
||||||
|
const double inf = std::numeric_limits<double>::infinity();
|
||||||
|
|
||||||
|
// Vertical well: MD == TVD
|
||||||
|
std::vector<double> mdValues = { 100, 500, 1000 };
|
||||||
|
std::vector<double> tvdValues = { 100, 500, 1000 };
|
||||||
|
|
||||||
|
// Mix of infinity and valid TVD values
|
||||||
|
std::vector<double> mixedTvd = { 100.0, inf, 500.0, inf, 1000.0 };
|
||||||
|
std::vector<double> result = RigWellPathGeometryTools::interpolateMdFromTvd( mdValues, tvdValues, mixedTvd );
|
||||||
|
|
||||||
|
EXPECT_EQ( result.size(), mixedTvd.size() );
|
||||||
|
|
||||||
|
// Valid entries at indices 0, 2, 4 should interpolate correctly (MD == TVD for a vertical well)
|
||||||
|
EXPECT_NEAR( result[0], 100.0, TOLERANCE );
|
||||||
|
EXPECT_NEAR( result[2], 500.0, TOLERANCE );
|
||||||
|
EXPECT_NEAR( result[4], 1000.0, TOLERANCE );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/// A single infinity entry in an otherwise valid list must not corrupt subsequent results.
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigWellPathGeometryTools, InfinityTvdValues_SingleInfinityDoesNotCorruptOtherResults )
|
||||||
|
{
|
||||||
|
const double inf = std::numeric_limits<double>::infinity();
|
||||||
|
|
||||||
|
// Vertical well: MD == TVD
|
||||||
|
std::vector<double> mdValues = { 100, 500, 1000 };
|
||||||
|
std::vector<double> tvdValues = { 100, 500, 1000 };
|
||||||
|
|
||||||
|
std::vector<double> tvdWithInf = { 200.0, inf, 800.0 };
|
||||||
|
std::vector<double> result = RigWellPathGeometryTools::interpolateMdFromTvd( mdValues, tvdValues, tvdWithInf );
|
||||||
|
|
||||||
|
EXPECT_EQ( result.size(), tvdWithInf.size() );
|
||||||
|
EXPECT_NEAR( result[0], 200.0, TOLERANCE );
|
||||||
|
// inf TVD at index 1 is linearly interpolated between neighbors: (200 + 800) / 2 = 500
|
||||||
|
EXPECT_NEAR( result[1], 500.0, TOLERANCE );
|
||||||
|
EXPECT_NEAR( result[2], 800.0, TOLERANCE );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/// N consecutive infinity TVD entries must be distributed evenly across the MD interval defined
|
||||||
|
/// by the nearest valid neighbors.
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigWellPathGeometryTools, InfinityTvdValues_ConsecutiveInfinityDistributedEvenly )
|
||||||
|
{
|
||||||
|
const double inf = std::numeric_limits<double>::infinity();
|
||||||
|
|
||||||
|
// Vertical well: MD == TVD
|
||||||
|
std::vector<double> mdValues = { 100, 500, 1000 };
|
||||||
|
std::vector<double> tvdValues = { 100, 500, 1000 };
|
||||||
|
|
||||||
|
// Three consecutive inf entries between TVD 200 and TVD 800
|
||||||
|
std::vector<double> tvdWithInf = { 200.0, inf, inf, inf, 800.0 };
|
||||||
|
std::vector<double> result = RigWellPathGeometryTools::interpolateMdFromTvd( mdValues, tvdValues, tvdWithInf );
|
||||||
|
|
||||||
|
EXPECT_EQ( result.size(), tvdWithInf.size() );
|
||||||
|
EXPECT_NEAR( result[0], 200.0, TOLERANCE );
|
||||||
|
// Three inf entries divide [200, 800] into four equal steps of 150 each
|
||||||
|
EXPECT_NEAR( result[1], 350.0, TOLERANCE );
|
||||||
|
EXPECT_NEAR( result[2], 500.0, TOLERANCE );
|
||||||
|
EXPECT_NEAR( result[3], 650.0, TOLERANCE );
|
||||||
|
EXPECT_NEAR( result[4], 800.0, TOLERANCE );
|
||||||
|
}
|
||||||
@@ -90,6 +90,7 @@ set(SOURCE_GROUP_HEADER_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RiuPlotMainWindowTools.h
|
${CMAKE_CURRENT_LIST_DIR}/RiuPlotMainWindowTools.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/Riu3DMainWindowTools.h
|
${CMAKE_CURRENT_LIST_DIR}/Riu3DMainWindowTools.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RiuDockWidgetTools.h
|
${CMAKE_CURRENT_LIST_DIR}/RiuDockWidgetTools.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RiuQwtCurveSelectorFilter.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotItemGroup.h
|
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotItemGroup.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotTools.h
|
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotTools.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RiuDepthQwtPlot.h
|
${CMAKE_CURRENT_LIST_DIR}/RiuDepthQwtPlot.h
|
||||||
@@ -204,6 +205,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RiuPlotMainWindowTools.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RiuPlotMainWindowTools.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/Riu3DMainWindowTools.cpp
|
${CMAKE_CURRENT_LIST_DIR}/Riu3DMainWindowTools.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RiuDockWidgetTools.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RiuDockWidgetTools.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RiuQwtCurveSelectorFilter.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotItemGroup.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotItemGroup.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotTools.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotTools.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RiuDepthQwtPlot.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RiuDepthQwtPlot.cpp
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ void RiuMultiPlotPage::updateSubTitles()
|
|||||||
{
|
{
|
||||||
for ( int i = 0; i < m_plotWidgets.size(); ++i )
|
for ( int i = 0; i < m_plotWidgets.size(); ++i )
|
||||||
{
|
{
|
||||||
if ( m_plotWidgets[i]->isChecked() )
|
if ( m_plotWidgets[i] && m_plotWidgets[i]->isChecked() )
|
||||||
{
|
{
|
||||||
m_subTitles[i]->setText( m_plotWidgets[i]->plotTitle() );
|
m_subTitles[i]->setText( m_plotWidgets[i]->plotTitle() );
|
||||||
}
|
}
|
||||||
@@ -436,6 +436,11 @@ void RiuMultiPlotPage::contextMenuEvent( QContextMenuEvent* event )
|
|||||||
menuBuilder << "RicShowContributingWellsFromPlotFeature";
|
menuBuilder << "RicShowContributingWellsFromPlotFeature";
|
||||||
menuBuilder << "RicNewDefaultSummaryPlotFeature";
|
menuBuilder << "RicNewDefaultSummaryPlotFeature";
|
||||||
|
|
||||||
|
if ( auto* pdmObject = dynamic_cast<caf::PdmUiObjectHandle*>( ownerPlotDefinition() ) )
|
||||||
|
{
|
||||||
|
pdmObject->appendMenuItems( menuBuilder );
|
||||||
|
}
|
||||||
|
|
||||||
menuBuilder.appendToMenu( &menu );
|
menuBuilder.appendToMenu( &menu );
|
||||||
|
|
||||||
if ( !menu.actions().empty() )
|
if ( !menu.actions().empty() )
|
||||||
@@ -980,7 +985,7 @@ QList<QPointer<RiuQwtPlotLegend>> RiuMultiPlotPage::legendsForVisiblePlots() con
|
|||||||
QList<QPointer<RiuQwtPlotLegend>> legends;
|
QList<QPointer<RiuQwtPlotLegend>> legends;
|
||||||
for ( int i = 0; i < m_plotWidgets.size(); ++i )
|
for ( int i = 0; i < m_plotWidgets.size(); ++i )
|
||||||
{
|
{
|
||||||
if ( m_plotWidgets[i]->isChecked() )
|
if ( m_plotWidgets[i] && m_plotWidgets[i]->isChecked() )
|
||||||
{
|
{
|
||||||
legends.push_back( m_legends[i] );
|
legends.push_back( m_legends[i] );
|
||||||
}
|
}
|
||||||
@@ -996,7 +1001,7 @@ QList<QPointer<RiuDraggableOverlayFrame>> RiuMultiPlotPage::legendFramesForVisib
|
|||||||
QList<QPointer<RiuDraggableOverlayFrame>> legendFrames;
|
QList<QPointer<RiuDraggableOverlayFrame>> legendFrames;
|
||||||
for ( int i = 0; i < m_plotWidgets.size(); ++i )
|
for ( int i = 0; i < m_plotWidgets.size(); ++i )
|
||||||
{
|
{
|
||||||
if ( m_plotWidgets[i]->isChecked() )
|
if ( m_plotWidgets[i] && m_plotWidgets[i]->isChecked() )
|
||||||
{
|
{
|
||||||
legendFrames.push_back( m_legendFrames[i] );
|
legendFrames.push_back( m_legendFrames[i] );
|
||||||
}
|
}
|
||||||
@@ -1012,7 +1017,7 @@ QList<QPointer<QLabel>> RiuMultiPlotPage::subTitlesForVisiblePlots() const
|
|||||||
QList<QPointer<QLabel>> subTitles;
|
QList<QPointer<QLabel>> subTitles;
|
||||||
for ( int i = 0; i < m_plotWidgets.size(); ++i )
|
for ( int i = 0; i < m_plotWidgets.size(); ++i )
|
||||||
{
|
{
|
||||||
if ( m_plotWidgets[i]->isChecked() )
|
if ( m_plotWidgets[i] && m_plotWidgets[i]->isChecked() )
|
||||||
{
|
{
|
||||||
m_subTitles[i]->setText( m_plotWidgets[i]->plotTitle() );
|
m_subTitles[i]->setText( m_plotWidgets[i]->plotTitle() );
|
||||||
subTitles.push_back( m_subTitles[i] );
|
subTitles.push_back( m_subTitles[i] );
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 "RiuQwtCurveSelectorFilter.h"
|
||||||
|
|
||||||
|
#include "RiuDockWidgetTools.h"
|
||||||
|
|
||||||
|
#include "qwt_plot.h"
|
||||||
|
#include "qwt_scale_map.h"
|
||||||
|
|
||||||
|
#include <QMouseEvent>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RiuQwtCurveSelectorFilter::RiuQwtCurveSelectorFilter( QwtPlot* plot, ItemFinder finder )
|
||||||
|
: QObject( plot->canvas() )
|
||||||
|
, m_finder( std::move( finder ) )
|
||||||
|
{
|
||||||
|
plot->canvas()->installEventFilter( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
int RiuQwtCurveSelectorFilter::closestPointIndex( QwtPlot* plot, const QPoint& canvasPos, const std::vector<std::pair<double, double>>& points )
|
||||||
|
{
|
||||||
|
auto xMap = plot->canvasMap( QwtAxis::XBottom );
|
||||||
|
auto yMap = plot->canvasMap( QwtAxis::YLeft );
|
||||||
|
|
||||||
|
const double xRange = std::abs( xMap.s2() - xMap.s1() );
|
||||||
|
const double yRange = std::abs( yMap.s2() - yMap.s1() );
|
||||||
|
const double minRangeEpsilon = 1e-14;
|
||||||
|
if ( xRange < minRangeEpsilon || yRange < minRangeEpsilon ) return -1;
|
||||||
|
|
||||||
|
const double clickX = xMap.invTransform( canvasPos.x() );
|
||||||
|
const double clickY = yMap.invTransform( canvasPos.y() );
|
||||||
|
|
||||||
|
double minDist = std::numeric_limits<double>::max();
|
||||||
|
int closestIdx = -1;
|
||||||
|
for ( int i = 0; i < static_cast<int>( points.size() ); ++i )
|
||||||
|
{
|
||||||
|
const double dx = ( points[i].first - clickX ) / xRange;
|
||||||
|
const double dy = ( points[i].second - clickY ) / yRange;
|
||||||
|
const double dist = dx * dx + dy * dy;
|
||||||
|
if ( dist < minDist )
|
||||||
|
{
|
||||||
|
minDist = dist;
|
||||||
|
closestIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ( closestIdx >= 0 && minDist < 0.03 * 0.03 ) ? closestIdx : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
bool RiuQwtCurveSelectorFilter::eventFilter( QObject* /*watched*/, QEvent* event )
|
||||||
|
{
|
||||||
|
if ( event->type() == QEvent::MouseButtonPress )
|
||||||
|
{
|
||||||
|
auto* mouseEvent = static_cast<QMouseEvent*>( event );
|
||||||
|
if ( mouseEvent->button() == Qt::LeftButton )
|
||||||
|
{
|
||||||
|
if ( const caf::PdmUiItem* item = m_finder( mouseEvent->pos() ) )
|
||||||
|
{
|
||||||
|
RiuDockWidgetTools::selectItemsInTreeView( RiuDockWidgetTools::plotMainWindowDataSourceTreeName(), { item } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class QPoint;
|
||||||
|
class QwtPlot;
|
||||||
|
|
||||||
|
namespace caf
|
||||||
|
{
|
||||||
|
class PdmUiItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
/// Event filter that selects a realization in the plot main window tree when the user left-clicks
|
||||||
|
/// near a curve on a Qwt plot canvas.
|
||||||
|
///
|
||||||
|
/// The caller supplies a finder callback: given a canvas pixel position, return the PdmUiItem
|
||||||
|
/// (typically a RimSummaryCase*) closest to that position, or nullptr if nothing is close enough.
|
||||||
|
/// When the callback returns a non-null item it is selected via RiuDockWidgetTools.
|
||||||
|
//==================================================================================================
|
||||||
|
class RiuQwtCurveSelectorFilter : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using ItemFinder = std::function<const caf::PdmUiItem*( const QPoint& )>;
|
||||||
|
|
||||||
|
RiuQwtCurveSelectorFilter( QwtPlot* plot, ItemFinder finder );
|
||||||
|
|
||||||
|
// Returns the index of the point in `points` closest to `canvasPos` using axis-normalised
|
||||||
|
// squared distance, or -1 if none falls within the 3% threshold.
|
||||||
|
static int closestPointIndex( QwtPlot* plot, const QPoint& canvasPos, const std::vector<std::pair<double, double>>& points );
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter( QObject* watched, QEvent* event ) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ItemFinder m_finder;
|
||||||
|
};
|
||||||
@@ -67,6 +67,9 @@ int RiuQwtPlotCurveDefines::zDepthForIndex( ZIndex index )
|
|||||||
case RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_OBSERVED:
|
case RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_OBSERVED:
|
||||||
return 500;
|
return 500;
|
||||||
break;
|
break;
|
||||||
|
case RiuQwtPlotCurveDefines::ZIndex::Z_HIGHLIGHTED_CURVE:
|
||||||
|
return 550;
|
||||||
|
break;
|
||||||
case RiuQwtPlotCurveDefines::ZIndex::Z_PLOT_RECT_ANNOTATION:
|
case RiuQwtPlotCurveDefines::ZIndex::Z_PLOT_RECT_ANNOTATION:
|
||||||
return 600;
|
return 600;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ enum class ZIndex
|
|||||||
Z_SINGLE_CURVE_NON_OBSERVED,
|
Z_SINGLE_CURVE_NON_OBSERVED,
|
||||||
Z_ERROR_BARS,
|
Z_ERROR_BARS,
|
||||||
Z_SINGLE_CURVE_OBSERVED,
|
Z_SINGLE_CURVE_OBSERVED,
|
||||||
|
Z_HIGHLIGHTED_CURVE,
|
||||||
Z_PLOT_RECT_ANNOTATION,
|
Z_PLOT_RECT_ANNOTATION,
|
||||||
Z_REGRESSION_CURVE,
|
Z_REGRESSION_CURVE,
|
||||||
Z_ANNOTATION
|
Z_ANNOTATION
|
||||||
|
|||||||
Reference in new issue
Block a user