///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2017 Statoil 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 // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RimWellRftPlot.h" #include "RiaColorTables.h" #include "RiaColorTools.h" #include "RiaPlotDefines.h" #include "RiaQDateTimeTools.h" #include "RiaSimWellBranchTools.h" #include "Summary/RiaSummaryTools.h" #include "RifReaderRftInterface.h" #include "RigCaseCellResultsData.h" #include "RigEclipseCaseData.h" #include "RimDataSourceForRftPlt.h" #include "RimDockWindowController.h" #include "RimEclipseCase.h" #include "RimEclipseResultCase.h" #include "RimEnsembleCurveSetColorManager.h" #include "RimObservedFmuRftData.h" #include "RimOilField.h" #include "RimPressureDepthData.h" #include "RimProject.h" #include "RimRegularLegendConfig.h" #include "RimReloadCaseTools.h" #include "RimSummaryCase.h" #include "RimSummaryEnsemble.h" #include "RimSummaryEnsembleTools.h" #include "RimTools.h" #include "RimWellLogChannel.h" #include "RimWellLogExtractionCurve.h" #include "RimWellLogLasFile.h" #include "RimWellLogLasFileCurve.h" #include "RimWellLogPlot.h" #include "RimWellLogPlotNameConfig.h" #include "RimWellLogRftCurve.h" #include "RimWellLogTrack.h" #include "RimWellPath.h" #include "RimWellPathCollection.h" #include "RimWellPlotTools.h" #include "RimWellRftEnsembleCurveSet.h" #include "Formations/RimWellFormationsCollection.h" #include "Formations/RimWellFormationsFile.h" #include "RiuAbstractLegendFrame.h" #include "RiuDraggableOverlayFrame.h" #include "RiuQwtCurveSelectorFilter.h" #include "RiuPlotCurve.h" #include "RiuPlotItem.h" #include "RiuPlotMainWindowTools.h" #include "RiuQwtPlotCurve.h" #include "RiuQwtPlotCurveDefines.h" #include "RiuQwtPlotItem.h" #include "RiuQwtPlotWidget.h" #include "cafCmdFeatureMenuBuilder.h" #include "cafPdmPointer.h" #include "cafPdmUiTreeOrdering.h" #include "cafPdmUiTreeSelectionEditor.h" #include "cafSelectionManager.h" #include "qwt_plot.h" #include #include #include #include CAF_PDM_SOURCE_INIT( RimWellRftPlot, "WellRftPlot" ); using ColorMode = RimEnsembleCurveSetColorManager::ColorMode; //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- const char RimWellRftPlot::PLOT_NAME_QFORMAT_STRING[] = "RFT: %1"; //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimWellRftPlot::RimWellRftPlot() : RimWellLogPlot() { CAF_PDM_InitObject( "RFT Plot", ":/RFTPlot16x16.png" ); CAF_PDM_InitField( &m_showStatisticsCurves, "ShowStatisticsCurves", true, "Show Statistics Curves" ); CAF_PDM_InitField( &m_showEnsembleCurves, "ShowEnsembleCurves", true, "Show Ensemble Curves" ); CAF_PDM_InitField( &m_showErrorInObservedData, "ShowErrorObserved", true, "Show Observed Data Error" ); CAF_PDM_InitFieldNoDefault( &m_wellLogPlot_OBSOLETE, "WellLog", "Well Log" ); m_wellLogPlot_OBSOLETE.xmlCapability()->setIOWritable( false ); m_depthType = RiaDefines::DepthType::TRUE_VERTICAL_DEPTH; CAF_PDM_InitFieldNoDefault( &m_wellPathNameOrSimWellName, "WellName", "Well Name" ); CAF_PDM_InitField( &m_branchIndex, "BranchIndex", 0, "Branch Index" ); CAF_PDM_InitField( &m_branchDetection, "BranchDetection", true, "Branch Detection", "", "Compute branches based on how simulation well cells are organized", "" ); CAF_PDM_InitFieldNoDefault( &m_selectedSources, "Sources", "Sources" ); m_selectedSources.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); m_selectedSources.xmlCapability()->disableIO(); m_selectedSources.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::HIDDEN ); m_selectedSources.uiCapability()->setAutoAddingOptionFromValue( false ); CAF_PDM_InitFieldNoDefault( &m_selectedSourcesForIo, "SourcesForIo", "SourcesForIo" ); CAF_PDM_InitFieldNoDefault( &m_selectedTimeSteps, "TimeSteps", "Time Steps" ); m_selectedTimeSteps.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); m_selectedTimeSteps.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::HIDDEN ); m_selectedTimeSteps.uiCapability()->setAutoAddingOptionFromValue( false ); CAF_PDM_InitFieldNoDefault( &m_ensembleCurveSets, "EnsembleCurveSets", "Ensemble Curve Sets" ); CAF_PDM_InitFieldNoDefault( &m_ensembleCurveSetEclipseCase, "EclipseResultCase", "Grid Model For MD", "Grid model used to compute measured depth using well path geometry" ); // TODO: may want to support TRUE_VERTICAL_DEPTH_RKB in the future // It was developed for regular well log plots and requires some more work for RFT plots. setAvailableDepthTypes( { RiaDefines::DepthType::MEASURED_DEPTH, RiaDefines::DepthType::TRUE_VERTICAL_DEPTH } ); m_nameConfig->setCustomName( "RFT Plot" ); setNamingMethod( RiaDefines::ObjectNamingMethod::CUSTOM ); m_plotLegendsHorizontal = false; setPlotTitleVisible( true ); // Not docked by default: can be embedded elsewhere. Standalone creators call dockAsPlotWindow(). m_isOnLoad = true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimWellRftPlot::~RimWellRftPlot() { removeWindowFromDock(); deleteViewWidget(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::detachFromDockPermanently() { if ( m_windowController != nullptr ) { m_windowController->removeWindowFromDock(); delete m_windowController(); m_windowController = nullptr; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::applyCurveAppearance( RimWellLogCurve* curve ) { applyCurveColor( curve ); RiaRftPltCurveDefinition curveDef = RimWellPlotTools::curveDefFromCurve( curve ); RiuQwtPlotCurveDefines::LineStyleEnum lineStyle = RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_SOLID; RiuPlotCurveSymbol::PointSymbolEnum currentSymbol = RiuPlotCurveSymbol::SYMBOL_NONE; if ( curveDef.address().sourceType() != RifDataSourceForRftPlt::SourceType::ENSEMBLE_RFT ) { currentSymbol = m_timeStepSymbols[curveDef.timeStep()]; } bool isObservedData = curveDef.address().sourceType() == RifDataSourceForRftPlt::SourceType::OBSERVED_LAS_FILE || curveDef.address().sourceType() == RifDataSourceForRftPlt::SourceType::OBSERVED_FMU_RFT; // Observed data lineStyle = isObservedData ? RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_NONE : RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_SOLID; curve->setSymbol( currentSymbol ); curve->setLineStyle( lineStyle ); // Dim ensemble member curves that do not belong to the highlighted curve set. // Statistics curves (ENSEMBLE_RFT) are intentionally excluded. if ( m_highlightedCurveSet && curveDef.address().sourceType() != RifDataSourceForRftPlt::SourceType::ENSEMBLE_RFT ) { auto curveSet = findEnsembleCurveSet( curveDef.address().ensemble() ); 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( 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 ) ); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::applyCurveColor( RimWellLogCurve* curve ) { cvf::Color3f color = findCurveColor( curve ); curve->setColor( color ); curve->setSymbolEdgeColor( color ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::updateFormationsOnPlot() const { if ( plotCount() > 0 ) { RimProject* proj = RimProject::current(); RimWellPath* wellPath = proj->wellPathByName( m_wellPathNameOrSimWellName ); RimCase* formationNamesCase = nullptr; RimWellLogTrack* track = dynamic_cast( plotByIndex( 0 ) ); if ( track ) { formationNamesCase = track->formationCase(); if ( !formationNamesCase ) { /// Set default case. Todo : Use the first of the selected cases in the plot std::vector cases = proj->allGridCases(); if ( !cases.empty() ) { formationNamesCase = cases[0]; } } // Prefer well-pick (formations.csv) zone bands over case-derived formation names when // they are available for the selected well, so the RFT profile shows the same zone // bands used by other well log tracks and the RFT correlation/cross plots. RimWellFormationsFile* wellFormationsFile = nullptr; QString wellNameInFile = associatedSimWellName(); if ( wellPath && wellPath->formationsGeometry() ) { wellFormationsFile = wellPath->wellFormationsFile(); } if ( wellFormationsFile ) { track->setAndUpdateWellPickFormationsData( wellPath ); } else if ( wellPath ) { track->setAndUpdateWellPathFormationNamesData( formationNamesCase, wellPath ); } else { // No well path trajectory is available (e.g. a purely simulation-well based RFT // entry). Prefer a formations file with explicit, depth-based zone picks for this // well over the case/K-layer based formation names, since there is no trajectory to // extract K-layers along. RimWellFormationsFile* formationsFileForWell = nullptr; if ( proj->activeOilField() && proj->activeOilField()->wellFormationsCollection() ) { formationsFileForWell = proj->activeOilField()->wellFormationsCollection()->findFileForWell( wellNameInFile ); } if ( formationsFileForWell ) { track->setAndUpdateFormationFileData( formationsFileForWell, wellNameInFile ); } else { track->setAndUpdateSimWellFormationNamesAndBranchData( formationNamesCase, associatedSimWellName(), m_branchIndex, m_branchDetection ); } } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimWellRftPlot::associatedSimWellName() const { return RimWellPlotTools::simWellName( m_wellPathNameOrSimWellName ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::setOrInitializeDataSources( const std::vector& sourcesToSelect ) { std::map wellSources = findWellSources(); if ( m_wellPathNameOrSimWellName() == "None" && !wellSources.empty() ) { m_wellPathNameOrSimWellName = wellSources.begin()->first; } std::vector dataSources; const QString simWellName = associatedSimWellName(); if ( !sourcesToSelect.empty() ) { // If the selection is provided, use it directly dataSources = sourcesToSelect; } else { // If no selection is provided, build the selection based on available data sources for ( RimEclipseResultCase* const rftCase : RimWellPlotTools::rftCasesForWell( simWellName ) ) { dataSources.push_back( RifDataSourceForRftPlt( RifDataSourceForRftPlt::SourceType::RFT_SIM_WELL_DATA, rftCase ) ); } for ( RimEclipseResultCase* const gridCase : RimWellPlotTools::gridCasesForWell( simWellName ) ) { dataSources.push_back( RifDataSourceForRftPlt( RifDataSourceForRftPlt::SourceType::GRID_MODEL_CELL_DATA, gridCase ) ); } for ( RimSummaryEnsemble* const ensemble : RimWellPlotTools::rftEnsemblesForWell( simWellName ) ) { dataSources.push_back( RifDataSourceForRftPlt( ensemble ) ); } for ( auto singleCase : RiaSummaryTools::singleTopLevelSummaryCases() ) { dataSources.push_back( RifDataSourceForRftPlt( singleCase, nullptr, nullptr ) ); } } std::vector wellLogFiles = RimWellPlotTools::wellLogFilesContainingPressure( m_wellPathNameOrSimWellName ); if ( !wellLogFiles.empty() ) { for ( RimWellLogFile* const wellLogFile : wellLogFiles ) { if ( auto wellLogLasFile = dynamic_cast( wellLogFile ) ) { dataSources.push_back( RifDataSourceForRftPlt( wellLogLasFile ) ); } } } for ( RimObservedFmuRftData* const observedFmuRftData : RimWellPlotTools::observedFmuRftDataForWell( m_wellPathNameOrSimWellName ) ) { dataSources.push_back( RifDataSourceForRftPlt( observedFmuRftData ) ); } m_selectedSources = dataSources; { std::set channelTypesToUse = RifEclipseRftAddress::rftPlotChannelTypes(); auto relevantTimeSteps = RimWellPlotTools::calculateRelevantTimeStepsFromCases( m_wellPathNameOrSimWellName, m_selectedSources, channelTypesToUse ); if ( !relevantTimeSteps.empty() ) { std::vector timeStepVector; // If we have RFT data from multiple sources, relevant time steps end up with a small number of time steps // If this is the case, preselect all time steps const size_t maxCountToPreSelect = 3; if ( relevantTimeSteps.size() <= maxCountToPreSelect ) { for ( const auto& item : relevantTimeSteps ) timeStepVector.push_back( item.first ); } else { // If only one RFT source is available, we might get a large number of time steps causing performance // issues Only select the first available time step timeStepVector.push_back( relevantTimeSteps.begin()->first ); } m_selectedTimeSteps = timeStepVector; } } createEnsembleCurveSets(); syncCurvesFromUiSelection(); m_isInitialized = true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::updateEditorsFromPreviousSelection() { std::set previousSources( m_selectedSources().begin(), m_selectedSources().end() ); std::set previousTimeSteps( m_selectedTimeSteps().begin(), m_selectedTimeSteps().end() ); m_selectedSources.v().clear(); m_selectedTimeSteps.v().clear(); auto dataSourceOptions = calculateValueOptions( &m_selectedSources ); for ( const auto& dataSourceOption : dataSourceOptions ) { if ( dataSourceOption.level() == 1 ) { RifDataSourceForRftPlt dataSource = dataSourceOption.value().value(); if ( previousSources.count( dataSource ) ) { m_selectedSources.v().push_back( dataSource ); } } } // This has to happen after the m_selectedSources is filled // because the available time steps is dependent on the selected sources. auto timeStepOptions = calculateValueOptions( &m_selectedTimeSteps ); for ( const auto& timeStepOption : timeStepOptions ) { QDateTime timeStep = timeStepOption.value().toDateTime(); if ( previousTimeSteps.count( timeStep ) ) { m_selectedTimeSteps.v().push_back( timeStep ); } } if ( m_selectedTimeSteps.v().empty() && !timeStepOptions.empty() ) { QDateTime timeStep = timeStepOptions.first().value().toDateTime(); m_selectedTimeSteps.v().push_back( timeStep ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::setSelectedSourcesFromCurves() { if ( RimProject::current()->isProjectFileVersionEqualOrOlderThan( "2025.04.3" ) ) { std::set selectedSources; std::set selectedTimeSteps; std::map> selectedTimeStepsMap; for ( const RiaRftPltCurveDefinition& curveDef : curveDefsFromCurves() ) { if ( curveDef.address().sourceType() == RifDataSourceForRftPlt::SourceType::OBSERVED_LAS_FILE ) { selectedSources.insert( RifDataSourceForRftPlt( curveDef.address().wellLogFile() ) ); } else if ( ( curveDef.address().sourceType() == RifDataSourceForRftPlt::SourceType::SUMMARY_RFT ) && curveDef.address().ensemble() ) { selectedSources.insert( RifDataSourceForRftPlt( curveDef.address().ensemble() ) ); } else selectedSources.insert( curveDef.address() ); auto newTimeStepMap = std::map>{ { curveDef.timeStep(), std::set{ curveDef.address() } } }; RimWellPlotTools::addTimeStepsToMap( selectedTimeStepsMap, newTimeStepMap ); selectedTimeSteps.insert( curveDef.timeStep() ); } // Storage of time steps to the project file was changed in 2025.04.3 m_selectedSources = std::vector( selectedSources.begin(), selectedSources.end() ); m_selectedTimeSteps = std::vector( selectedTimeSteps.begin(), selectedTimeSteps.end() ); return; } std::vector selectedSources; for ( RimDataSourceForRftPlt* addr : m_selectedSourcesForIo ) { selectedSources.push_back( addr->address() ); } m_selectedSources = selectedSources; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::syncCurvesFromUiSelection() { RimWellLogTrack* plotTrack = dynamic_cast( plotByIndex( 0 ) ); if ( !plotTrack ) return; std::set allCurveDefs = selectedCurveDefs(); std::set curveDefsInPlot = curveDefsFromCurves(); std::set curvesToDelete; std::set newCurveDefs; if ( allCurveDefs.size() < curveDefsInPlot.size() ) { // Determine which curves to delete from plot std::set deleteCurveDefs; std::set_difference( curveDefsInPlot.begin(), curveDefsInPlot.end(), allCurveDefs.begin(), allCurveDefs.end(), std::inserter( deleteCurveDefs, deleteCurveDefs.end() ) ); for ( RimWellLogCurve* const curve : plotTrack->curves() ) { RiaRftPltCurveDefinition curveDef = RimWellPlotTools::curveDefFromCurve( curve ); if ( deleteCurveDefs.count( curveDef ) > 0 ) { curvesToDelete.insert( curve ); } } } else { // Determine which curves are new since last time std::set_difference( allCurveDefs.begin(), allCurveDefs.end(), curveDefsInPlot.begin(), curveDefsInPlot.end(), std::inserter( newCurveDefs, newCurveDefs.end() ) ); } updateCurvesInPlot( allCurveDefs, newCurveDefs, curvesToDelete ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::set RimWellRftPlot::selectedCurveDefs() const { std::set channelTypesToUse = RifEclipseRftAddress::rftPlotChannelTypes(); return RimWellPlotTools::curveDefsFromTimesteps( m_wellPathNameOrSimWellName, m_selectedTimeSteps.v(), true, selectedSourcesExpanded(), channelTypesToUse ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::set RimWellRftPlot::curveDefsFromCurves() const { std::set curveDefs; RimWellLogTrack* const plotTrack = dynamic_cast( plotByIndex( 0 ) ); if ( plotTrack ) { for ( RimWellLogCurve* const curve : plotTrack->curves() ) { curveDefs.insert( RimWellPlotTools::curveDefFromCurve( curve ) ); } } return curveDefs; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::updateCurvesInPlot( const std::set& allCurveDefs, const std::set& curveDefsToAdd, const std::set& curvesToDelete ) { const QString simWellName = associatedSimWellName(); RimWellLogTrack* const plotTrack = dynamic_cast( plotByIndex( 0 ) ); if ( !plotTrack ) return; if ( plotTrack->plotWidget() ) { detachAndDeleteLegendCurves(); } // Delete curves plotTrack->deleteAllCurves(); defineCurveColorsAndSymbols( allCurveDefs ); // Sort legend items by project tree order (index in m_ensembleCurveSets), then by date/time auto curveSetsForLegendCmp = [this]( const std::pair& a, const std::pair& b ) -> bool { auto ia = m_ensembleCurveSets.indexOf( a.first ); auto ib = m_ensembleCurveSets.indexOf( b.first ); if ( ia != ib ) return ia < ib; return a.second < b.second; }; std::set, decltype( curveSetsForLegendCmp )> curveSetsForLegend( curveSetsForLegendCmp ); // Add new curves for ( const RiaRftPltCurveDefinition& curveDefToAdd : allCurveDefs ) { if ( curveDefToAdd.address().sourceType() == RifDataSourceForRftPlt::SourceType::RFT_SIM_WELL_DATA ) { auto curve = new RimWellLogRftCurve(); plotTrack->addCurve( curve ); auto rftCase = curveDefToAdd.address().eclCase(); curve->setEclipseCase( rftCase ); RifEclipseRftAddress address = RifEclipseRftAddress::createAddress( simWellName, curveDefToAdd.timeStep(), RifEclipseRftAddress::RftWellLogChannelType::PRESSURE ); curve->setRftAddress( address ); curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_NON_OBSERVED ) ); curve->setSimWellBranchData( m_branchDetection, m_branchIndex ); applyCurveAppearance( curve ); } else if ( curveDefToAdd.address().sourceType() == RifDataSourceForRftPlt::SourceType::OBSERVED_FMU_RFT ) { auto curve = new RimWellLogRftCurve(); plotTrack->addCurve( curve ); if ( auto observedFmuRftData = curveDefToAdd.address().observedFmuRftData() ) { curve->setErrorBarsVisible( m_showErrorInObservedData ); curve->setObservedFmuRftData( observedFmuRftData ); } else if ( auto pressureDepthData = curveDefToAdd.address().pressureDepthData() ) { curve->setPressureDepthData( pressureDepthData ); } RifEclipseRftAddress address = RifEclipseRftAddress::createAddress( m_wellPathNameOrSimWellName, curveDefToAdd.timeStep(), RifEclipseRftAddress::RftWellLogChannelType::PRESSURE ); curve->setRftAddress( address ); curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_OBSERVED ) ); applyCurveAppearance( curve ); } else if ( ( !curveDefToAdd.address().ensemble() || m_showEnsembleCurves ) && curveDefToAdd.address().sourceType() == RifDataSourceForRftPlt::SourceType::SUMMARY_RFT ) { // A summary case address can optionally contain an Eclipse case used to compute the TVD/MD for a well path // https://github.com/OPM/ResInsight/issues/10501 auto eclipeCase = curveDefToAdd.address().eclCase(); if ( curveDefToAdd.address().ensemble() ) { auto curveSet = findEnsembleCurveSet( curveDefToAdd.address().ensemble() ); if ( curveSet ) { eclipeCase = curveSet->eclipseCase(); curveSetsForLegend.insert( { curveSet, curveDefToAdd.timeStep() } ); } } auto curve = new RimWellLogRftCurve(); plotTrack->addCurve( curve ); auto summaryCase = curveDefToAdd.address().summaryCase(); curve->setSummaryCase( summaryCase ); curve->setEnsemble( curveDefToAdd.address().ensemble() ); curve->setObservedFmuRftData( findObservedFmuData( m_wellPathNameOrSimWellName, curveDefToAdd.timeStep() ) ); RifEclipseRftAddress address = RifEclipseRftAddress::createAddress( m_wellPathNameOrSimWellName, curveDefToAdd.timeStep(), RifEclipseRftAddress::RftWellLogChannelType::PRESSURE ); curve->setRftAddress( address ); curve->setEclipseCase( eclipeCase ); double zValue = RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_ENSEMBLE_CURVE ); if ( !curveDefToAdd.address().ensemble() ) { zValue = RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_NON_OBSERVED ); } curve->setZOrder( zValue ); applyCurveAppearance( curve ); if ( curveDefToAdd.address().ensemble() ) { curve->setShowInLegend( false ); } } else if ( m_showStatisticsCurves && curveDefToAdd.address().sourceType() == RifDataSourceForRftPlt::SourceType::ENSEMBLE_RFT ) { RimSummaryEnsemble* ensemble = curveDefToAdd.address().ensemble(); if ( auto curveSet = findEnsembleCurveSet( ensemble ) ) { std::set rftAddresses = curveSet->statisticsEclipseRftReader()->eclipseRftAddresses( m_wellPathNameOrSimWellName, curveDefToAdd.timeStep() ); for ( const auto& rftAddress : rftAddresses ) { if ( rftAddress.wellLogChannel() == RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_P50 ) { // Default statistics curves are P10, P50, P90 and mean // It is not common to use P50 for ensemble RFT, so skip display of P50 to avoid confusion with mean // https://github.com/OPM/ResInsight/issues/5238 continue; } if ( rftAddress.wellLogChannel() != RifEclipseRftAddress::RftWellLogChannelType::TVD ) { auto curve = new RimWellLogRftCurve(); plotTrack->addCurve( curve ); curve->setEnsemble( ensemble ); curve->setEclipseCase( curveSet->eclipseCase() ); curve->setRftAddress( rftAddress ); curve->setObservedFmuRftData( findObservedFmuData( m_wellPathNameOrSimWellName, curveDefToAdd.timeStep() ) ); curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_ENSEMBLE_STAT_CURVE ) ); applyCurveAppearance( curve ); auto symbol = m_timeStepSymbols[curveDefToAdd.timeStep()]; curve->setSymbol( symbol ); auto size = curve->symbolSize(); curve->setSymbolSize( size + 2 ); curve->setLineStyle( RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_SOLID ); curve->setLineThickness( 3 ); curve->setShowInLegend( false ); curveSetsForLegend.insert( { curveSet, curveDefToAdd.timeStep() } ); } } } } else if ( curveDefToAdd.address().sourceType() == RifDataSourceForRftPlt::SourceType::GRID_MODEL_CELL_DATA ) { auto curve = new RimWellLogExtractionCurve(); plotTrack->addCurve( curve ); cvf::Color3f curveColor = RiaColorTables::wellLogPlotPaletteColors().cycledColor3f( plotTrack->curveCount() ); curve->setColor( curveColor ); curve->setFromSimulationWellName( simWellName, m_branchIndex, m_branchDetection ); // Fetch cases and time steps auto gridCase = curveDefToAdd.address().eclCase(); if ( gridCase != nullptr ) { // Case curve->setCase( gridCase ); curve->setEclipseResultVariable( "PRESSURE" ); // Time step std::vector timeSteps = gridCase->eclipseCaseData()->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->timeStepDates(); int currentTimeStepIndex = -1; for ( size_t tsIdx = 0; tsIdx < timeSteps.size(); ++tsIdx ) { if ( timeSteps[tsIdx] == curveDefToAdd.timeStep() ) { currentTimeStepIndex = static_cast( tsIdx ); break; } } curve->setCurrentTimeStep( currentTimeStepIndex ); curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_ENSEMBLE_CURVE ) ); applyCurveAppearance( curve ); } } else if ( curveDefToAdd.address().sourceType() == RifDataSourceForRftPlt::SourceType::OBSERVED_LAS_FILE ) { RimWellLogLasFile* const wellLogFile = curveDefToAdd.address().wellLogFile(); RimWellPath* const wellPath = RimWellPlotTools::wellPathFromWellLogFile( wellLogFile ); if ( wellLogFile != nullptr ) { RimWellLogChannel* pressureChannel = RimWellPlotTools::getPressureChannelFromWellFile( wellLogFile ); auto curve = new RimWellLogLasFileCurve(); plotTrack->addCurve( curve ); curve->setWellPath( wellPath ); curve->setWellLog( wellLogFile ); curve->setWellLogChannelName( pressureChannel->name() ); curve->setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_NON_OBSERVED ) ); applyCurveAppearance( curve ); } } } if ( auto qwtWidget = dynamic_cast( 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 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. // 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. QObject::disconnect( m_legendClickedConnection ); caf::PdmPointer self( this ); m_legendClickedConnection = QObject::connect( qwtWidget, &RiuQwtPlotWidget::plotItemSelected, [self]( std::shared_ptr item, bool toggle, int idx ) { if ( self ) self->onLegendItemClicked( item, toggle, idx ); } ); // Create curves with no content to display in the curve legend section. Ensures a consistent legend for both ensemble and // statistics curves. auto formatString = RiaQDateTimeTools::createTimeFormatStringFromDates( m_selectedTimeSteps() ); for ( const auto& [curveSet, dateTime] : curveSetsForLegend ) { auto riuCurve = qwtWidget->createPlotCurve( nullptr, "" ); riuCurve->attachToPlot( qwtWidget ); riuCurve->setVisibleInLegend( true ); QStringList titleItems; titleItems.push_back( curveSet->ensembleName() ); titleItems.push_back( dateTime.toString( formatString ) ); titleItems.push_back( m_wellPathNameOrSimWellName ); QString title = titleItems.join( ", " ); riuCurve->setTitle( title ); auto color = RiaColorTools::toQColor( curveSet->curveColor( nullptr, nullptr ) ); int curveThickness = 3; riuCurve->setAppearance( RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_SOLID, RiuQwtPlotCurveDefines::CurveInterpolationEnum::INTERPOLATION_POINT_TO_POINT, curveThickness, color ); auto symbolType = m_timeStepSymbols[dateTime]; auto symbol = riuCurve->createSymbol( symbolType ); symbol->setSize( 8, 8 ); symbol->setColor( color ); riuCurve->setSymbol( symbol ); m_legendPlotCurves.push_back( riuCurve ); m_legendCurveToEnsembleCurveSet[riuCurve] = curveSet; } } if ( depthType() == RiaDefines::DepthType::MEASURED_DEPTH ) { assignWellPathToExtractionCurves(); } RimWellLogPlot::onLoadDataAndUpdate(); if ( plotTrack->curveCount() ) { zoomAll(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimWellRftPlot::selectedSourcesExpanded() const { std::vector sources; for ( const RifDataSourceForRftPlt& addr : m_selectedSources() ) { if ( addr.sourceType() == RifDataSourceForRftPlt::SourceType::OBSERVED_LAS_FILE ) { for ( RimWellLogFile* const wellLogFile : RimWellPlotTools::wellLogFilesContainingPressure( m_wellPathNameOrSimWellName ) ) { if ( auto wellLogLasFile = dynamic_cast( wellLogFile ) ) { sources.push_back( RifDataSourceForRftPlt( wellLogLasFile ) ); } } } else sources.push_back( addr ); } return sources; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- const QString& RimWellRftPlot::simWellOrWellPathName() const { return m_wellPathNameOrSimWellName.v(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::setSimWellOrWellPathName( const QString& currWellName ) { m_wellPathNameOrSimWellName = currWellName; if ( m_wellPathNameOrSimWellName().isEmpty() ) { m_wellPathNameOrSimWellName = "None"; } m_nameConfig->setCustomName( QString( plotNameFormatString() ).arg( m_wellPathNameOrSimWellName ) ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RimWellRftPlot::branchIndex() const { return m_branchIndex; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- const char* RimWellRftPlot::plotNameFormatString() { return PLOT_NAME_QFORMAT_STRING; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimWellRftPlot::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { QList options = RimWellLogPlot::calculateValueOptions( fieldNeedingOptions ); const QString simWellName = associatedSimWellName(); if ( fieldNeedingOptions == &m_wellPathNameOrSimWellName ) { options.push_back( caf::PdmOptionItemInfo( "None", "None" ) ); std::map wellSources = findWellSources(); for ( const auto& wellName : wellSources ) { const QStringList& tags = wellName.second; QString uiText = wellName.first; if ( !tags.empty() ) { uiText += QString( " (%1)" ).arg( wellName.second.join( ", " ) ); } options.push_back( caf::PdmOptionItemInfo( uiText, wellName.first ) ); } } else if ( fieldNeedingOptions == &m_selectedSources ) { options = calculateValueOptionsForSources(); } else if ( fieldNeedingOptions == &m_selectedTimeSteps ) { std::set channelTypesToUse = RifEclipseRftAddress::rftPlotChannelTypes(); RimWellPlotTools::calculateValueOptionsForTimeSteps( m_wellPathNameOrSimWellName, selectedSourcesExpanded(), channelTypesToUse, options ); } else if ( fieldNeedingOptions == &m_branchIndex ) { auto simulationWellBranches = RiaSimWellBranchTools::simulationWellBranches( simWellName, m_branchDetection ); options = RiaSimWellBranchTools::valueOptionsForBranchIndexField( simulationWellBranches ); } else if ( fieldNeedingOptions == &m_ensembleCurveSetEclipseCase ) { RimTools::caseOptionItems( &options ); options.push_front( caf::PdmOptionItemInfo( "None", nullptr ) ); } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimWellRftPlot::calculateValueOptionsForSources() const { QList options; const QString simWellName = associatedSimWellName(); const std::vector rftCases = RimWellPlotTools::rftCasesForWell( simWellName ); if ( !rftCases.empty() ) { options.push_back( caf::PdmOptionItemInfo::createHeader( RifDataSourceForRftPlt::sourceTypeUiText( RifDataSourceForRftPlt::SourceType::RFT_SIM_WELL_DATA ), true ) ); for ( const auto& rftCase : rftCases ) { auto addr = RifDataSourceForRftPlt( RifDataSourceForRftPlt::SourceType::RFT_SIM_WELL_DATA, rftCase ); auto item = caf::PdmOptionItemInfo( rftCase->caseUserDescription(), QVariant::fromValue( addr ) ); item.setLevel( 1 ); options.push_back( item ); } } const std::vector rftEnsembles = RimWellPlotTools::rftEnsemblesForWell( m_wellPathNameOrSimWellName ); if ( !rftEnsembles.empty() ) { options.push_back( caf::PdmOptionItemInfo::createHeader( RifDataSourceForRftPlt::sourceTypeUiText( RifDataSourceForRftPlt::SourceType::ENSEMBLE_RFT ), true ) ); for ( RimSummaryEnsemble* rftEnsemble : rftEnsembles ) { auto addr = RifDataSourceForRftPlt( rftEnsemble ); auto item = caf::PdmOptionItemInfo( rftEnsemble->name(), QVariant::fromValue( addr ) ); item.setLevel( 1 ); options.push_back( item ); } } auto singleCases = RiaSummaryTools::singleTopLevelSummaryCases(); if ( !singleCases.empty() ) { options.push_back( caf::PdmOptionItemInfo::createHeader( RifDataSourceForRftPlt::sourceTypeUiText( RifDataSourceForRftPlt::SourceType::SUMMARY_RFT ), true ) ); for ( auto summaryCase : singleCases ) { if ( summaryCase->rftReader() && summaryCase->rftReader()->wellNames().contains( m_wellPathNameOrSimWellName ) ) { auto eclipeGridModel = RimReloadCaseTools::gridModelFromSummaryCase( summaryCase ); auto parentEnsemble = summaryCase->firstAncestorOrThisOfType(); auto addr = RifDataSourceForRftPlt( summaryCase, parentEnsemble, eclipeGridModel ); auto item = caf::PdmOptionItemInfo( summaryCase->displayCaseName(), QVariant::fromValue( addr ) ); item.setLevel( 1 ); options.push_back( item ); } } } const std::vector gridCases = RimWellPlotTools::gridCasesForWell( simWellName ); if ( !gridCases.empty() ) { options.push_back( caf::PdmOptionItemInfo::createHeader( RifDataSourceForRftPlt::sourceTypeUiText( RifDataSourceForRftPlt::SourceType::GRID_MODEL_CELL_DATA ), true ) ); for ( const auto& gridCase : gridCases ) { auto addr = RifDataSourceForRftPlt( RifDataSourceForRftPlt::SourceType::GRID_MODEL_CELL_DATA, gridCase ); auto item = caf::PdmOptionItemInfo( gridCase->caseUserDescription(), QVariant::fromValue( addr ) ); item.setLevel( 1 ); options.push_back( item ); } } auto wellLogFiles = RimWellPlotTools::wellLogFilesContainingPressure( m_wellPathNameOrSimWellName ); if ( !wellLogFiles.empty() ) { options.push_back( caf::PdmOptionItemInfo::createHeader( RifDataSourceForRftPlt::sourceTypeUiText( RifDataSourceForRftPlt::SourceType::OBSERVED_LAS_FILE ), true ) ); for ( const auto& wellLogFile : wellLogFiles ) { if ( auto wellLogLasFile = dynamic_cast( wellLogFile ) ) { auto addr = RifDataSourceForRftPlt( wellLogLasFile ); auto item = caf::PdmOptionItemInfo( "Observed Data", QVariant::fromValue( addr ) ); item.setLevel( 1 ); options.push_back( item ); } } } const std::vector observedFmuRftCases = RimWellPlotTools::observedFmuRftDataForWell( m_wellPathNameOrSimWellName ); if ( !observedFmuRftCases.empty() ) { options.push_back( caf::PdmOptionItemInfo::createHeader( RifDataSourceForRftPlt::sourceTypeUiText( RifDataSourceForRftPlt::SourceType::OBSERVED_FMU_RFT ), true ) ); for ( const auto& observedFmuRftCase : observedFmuRftCases ) { auto addr = RifDataSourceForRftPlt( observedFmuRftCase ); auto item = caf::PdmOptionItemInfo( observedFmuRftCase->name(), QVariant::fromValue( addr ) ); item.setLevel( 1 ); options.push_back( item ); } } const std::vector pressureDepthData = RimWellPlotTools::pressureDepthDataForWell( m_wellPathNameOrSimWellName ); if ( !pressureDepthData.empty() ) { options.push_back( caf::PdmOptionItemInfo::createHeader( RifDataSourceForRftPlt::sourceTypeUiText( RifDataSourceForRftPlt::SourceType::OBSERVED_FMU_RFT ), true ) ); for ( const auto& pd : pressureDepthData ) { auto addr = RifDataSourceForRftPlt( pd ); auto item = caf::PdmOptionItemInfo( pd->name(), QVariant::fromValue( addr ) ); item.setLevel( 1 ); options.push_back( item ); } } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { RimWellLogPlot::fieldChangedByUi( changedField, oldValue, newValue ); bool rebuildEnsembleCurveSets = false; bool updateEditors = false; if ( changedField == &m_wellPathNameOrSimWellName ) { m_nameConfig->setCustomName( QString( plotNameFormatString() ).arg( m_wellPathNameOrSimWellName ) ); m_branchIndex = 0; RimWellLogTrack* const plotTrack = dynamic_cast( plotByIndex( 0 ) ); if ( plotTrack ) { plotTrack->deleteAllCurves(); } updateEditorsFromPreviousSelection(); rebuildEnsembleCurveSets = true; } else if ( changedField == &m_branchIndex || changedField == &m_branchDetection ) { const QString simWellName = associatedSimWellName(); m_branchIndex = RiaSimWellBranchTools::clampBranchIndex( simWellName, m_branchIndex, m_branchDetection ); rebuildEnsembleCurveSets = true; } else if ( changedField == &m_selectedSources || changedField == &m_selectedTimeSteps ) { rebuildEnsembleCurveSets = true; updateEditors = true; } else if ( changedField == &m_showStatisticsCurves || changedField == &m_showEnsembleCurves || changedField == &m_showErrorInObservedData ) { // No extra action needed — handled by the common tail below. } else if ( changedField == &m_ensembleCurveSetEclipseCase ) { for ( RimWellRftEnsembleCurveSet* curveSet : m_ensembleCurveSets() ) { curveSet->setEclipseCase( m_ensembleCurveSetEclipseCase ); } rebuildEnsembleCurveSets = true; } else { return; } if ( rebuildEnsembleCurveSets ) { createEnsembleCurveSets(); } updateFormationsOnPlot(); syncCurvesFromUiSelection(); if ( updateEditors ) { updateConnectedEditors(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) { for ( RimWellRftEnsembleCurveSet* curveSet : m_ensembleCurveSets() ) { bool isSelected = false; for ( RimSummaryEnsemble* selectedCurveSet : selectedEnsembles() ) { if ( curveSet->ensemble() == selectedCurveSet ) { isSelected = true; break; } } if ( isSelected ) { uiTreeOrdering.add( curveSet ); } } uiTreeOrdering.skipRemainingChildren( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { uiOrdering.add( &m_wellPathNameOrSimWellName ); uiOrdering.add( &m_showStatisticsCurves ); uiOrdering.add( &m_showEnsembleCurves ); uiOrdering.add( &m_showErrorInObservedData ); bool ensembleDataSelected = !selectedEnsembles().empty(); m_showStatisticsCurves.uiCapability()->setUiReadOnly( !ensembleDataSelected ); m_showEnsembleCurves.uiCapability()->setUiReadOnly( !ensembleDataSelected ); RiaSimWellBranchTools::appendSimWellBranchFieldsIfRequiredFromWellName( &uiOrdering, m_wellPathNameOrSimWellName, m_branchDetection, m_branchIndex ); caf::PdmUiGroup* sourcesGroup = uiOrdering.addNewGroupWithKeyword( "Sources", "Sources" ); sourcesGroup->add( &m_selectedSources ); if ( !m_ensembleCurveSets.empty() ) { uiOrdering.add( &m_ensembleCurveSetEclipseCase ); } caf::PdmUiGroup* timeStepsGroup = uiOrdering.addNewGroupWithKeyword( "Time Steps", "TimeSteps" ); timeStepsGroup->add( &m_selectedTimeSteps ); if ( plotCount() > 0 ) { RimWellLogTrack* const track = dynamic_cast( plotByIndex( 0 ) ); if ( track ) { track->uiOrderingForRftPltFormations( uiOrdering ); track->uiOrderingForPropertyAxisSettings( uiOrdering ); caf::PdmUiGroup* depthGroup = uiOrdering.addNewGroup( "Depth Axis Settings" ); uiOrderingForDepthAxis( uiConfigName, *depthGroup ); caf::PdmUiGroup* plotLayoutGroup = uiOrdering.addNewGroup( "Plot Layout" ); plotLayoutGroup->setCollapsedByDefault(); RimWellLogPlot::uiOrderingForAutoName( uiConfigName, *plotLayoutGroup ); RimPlotWindow::uiOrderingForLegendsAndFonts( uiConfigName, uiOrdering ); plotLayoutGroup->add( &m_depthOrientation ); } } uiOrdering.skipRemainingFields( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::map RimWellRftPlot::findWellSources() { std::map wellNames; RimProject* proj = RimProject::current(); if ( proj != nullptr ) { const std::vector simWellNames = proj->simulationWellNames(); std::set simWellsAssociatedWithWellPath; // Observed wells for ( RimWellPath* const wellPath : proj->allWellPaths() ) { wellNames[wellPath->name()].push_back( "Well Path" ); if ( !wellPath->associatedSimulationWellName().isEmpty() ) { simWellsAssociatedWithWellPath.insert( wellPath->associatedSimulationWellName() ); } } // Sim wells not associated with well path for ( const QString& simWellName : simWellNames ) { if ( simWellsAssociatedWithWellPath.count( simWellName ) == 0 ) { wellNames[simWellName].push_back( "Sim.Well" ); } } auto singleCases = RiaSummaryTools::singleTopLevelSummaryCases(); for ( auto summaryCase : singleCases ) { if ( auto rftReader = summaryCase->rftReader() ) { for ( const QString& wellName : rftReader->wellNames() ) { wellNames[wellName].push_back( "Summary" ); } } } const std::vector rftEnsembles = RimWellPlotTools::rftEnsembles(); // Ensemble RFT wells { for ( RimSummaryEnsemble* summaryCaseColl : rftEnsembles ) { std::set wellsWithRftData = RimSummaryEnsembleTools::wellsWithRftData( summaryCaseColl->allSummaryCases() ); for ( const QString& wellName : wellsWithRftData ) { wellNames[wellName].push_back( "Ensemble" ); } } } // Observed FMU RFT wells const std::vector allRftFmuData = RimWellPlotTools::observedFmuRftData(); if ( !allRftFmuData.empty() ) { for ( RimObservedFmuRftData* rftFmuData : allRftFmuData ) { for ( const QString& wellName : rftFmuData->wells() ) { wellNames[wellName].push_back( "Observed" ); } } } const std::vector pressureDepthData = RimWellPlotTools::pressureDepthData(); for ( const auto& pd : pressureDepthData ) { for ( const auto& wellName : pd->wellNames() ) { wellNames[wellName].push_back( "Observed" ); } } } return wellNames; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::onLoadDataAndUpdate() { if ( !m_isInitialized ) { // TODO: m_selectedSources and m_selectedTimeSteps are not stored in the project file, so we need to set them here // to ensure that the plot is initialized with the correct sources and time steps. Refactor m_selectedSources and // m_selectedTimeSteps to be stored in the project file, and disable storing of curves to the project file. This is a temporary // solution to ensure that the plot is initialized correctly setSelectedSourcesFromCurves(); // // This function call was previously in initAfterRead, and triggered opening of the grid model. Moved here to allow 3D views to // trigger open of grid model setSelectedSourcesFromCurves(); m_isInitialized = true; } if ( m_isOnLoad ) { if ( plotCount() > 0 ) { RimWellLogTrack* const plotTrack = dynamic_cast( plotByIndex( 0 ) ); if ( plotTrack ) { plotTrack->setAnnotationType( RiaDefines::RegionAnnotationType::FORMATION_ANNOTATIONS ); } } m_isOnLoad = false; } updateDockWindowVisibility(); updateFormationsOnPlot(); if ( depthType() == RiaDefines::DepthType::MEASURED_DEPTH ) { assignWellPathToExtractionCurves(); } RimWellLogPlot::onLoadDataAndUpdate(); createEnsembleCurveSets(); // Update of curve color must happen here when loading data from project file, as the curve color is blended by // the background color. The background color is taken from the viewer. RimWellLogTrack* const plotTrack = dynamic_cast( plotByIndex( 0 ) ); if ( plotTrack && plotTrack->viewer() ) { syncCurvesFromUiSelection(); for ( auto c : plotTrack->curves() ) { applyCurveColor( c ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::setupBeforeSave() { syncSourcesIoFieldFromGuiField(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::initAfterRead() { if ( m_wellLogPlot_OBSOLETE ) { RimWellLogPlot& wellLogPlot = dynamic_cast( *this ); wellLogPlot = std::move( *m_wellLogPlot_OBSOLETE.value() ); delete m_wellLogPlot_OBSOLETE; m_wellLogPlot_OBSOLETE = nullptr; } if ( RimProject::current()->isProjectFileVersionEqualOrOlderThan( "2025.04.3" ) ) { std::vector colorTable; RiaColorTables::summaryCurveDefaultPaletteColors().color3fArray().toStdVector( &colorTable ); size_t colorIndex = 0; for ( auto e : m_ensembleCurveSets ) { e->setCurveColor( colorTable[colorIndex % colorTable.size()] ); } } RimWellLogPlot::initAfterRead(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimWellRftPlot::showErrorBarsForObservedData() const { return m_showErrorInObservedData(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::onLegendDefinitionChanged() { syncCurvesFromUiSelection(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimWellRftPlot::useUndoRedoForFieldChanged() { // m_selectedSources use data types that are not compatible with caf // consider rewrite to caf object using ptrfield instead of pdmpointer return false; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::deleteViewWidget() { // Required to detach curves before view widget is deleted. The Qwt plot curves are implicitly deleted when the view widget is deleted. detachAndDeleteLegendCurves(); // Disconnect before the widget is deleted to prevent the lambda from firing after this object is destroyed. QObject::disconnect( m_legendClickedConnection ); m_legendClickedConnection = {}; RimDepthTrackPlot::deleteViewWidget(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::assignWellPathToExtractionCurves() { RimProject* proj = RimProject::current(); RimWellPath* wellPath = proj->wellPathByName( m_wellPathNameOrSimWellName ); if ( wellPath ) { RimWellLogTrack* const plotTrack = dynamic_cast( plotByIndex( 0 ) ); if ( plotTrack ) { for ( RimWellLogCurve* curve : plotTrack->curves() ) { auto extractionCurve = dynamic_cast( curve ); if ( extractionCurve ) { extractionCurve->setTrajectoryType( RimWellLogExtractionCurve::WELL_PATH ); extractionCurve->setWellPath( wellPath ); } } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::syncSourcesIoFieldFromGuiField() { m_selectedSourcesForIo.deleteChildren(); for ( const RifDataSourceForRftPlt& addr : m_selectedSources() ) { m_selectedSourcesForIo.push_back( new RimDataSourceForRftPlt( addr ) ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuPlotCurveSymbol::PointSymbolEnum RimWellRftPlot::statisticsCurveSymbolFromAddress( const RifEclipseRftAddress& address ) { switch ( address.wellLogChannel() ) { case RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_P10: return RiuPlotCurveSymbol::SYMBOL_TRIANGLE; case RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_P50: return RiuPlotCurveSymbol::SYMBOL_DOWN_TRIANGLE; case RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_P90: return RiuPlotCurveSymbol::SYMBOL_LEFT_TRIANGLE; case RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_MEAN: return RiuPlotCurveSymbol::SYMBOL_RIGHT_TRIANGLE; } return RiuPlotCurveSymbol::SYMBOL_RIGHT_TRIANGLE; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuPlotCurveSymbol::LabelPosition RimWellRftPlot::statisticsLabelPosFromAddress( const RifEclipseRftAddress& address ) { switch ( address.wellLogChannel() ) { case RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_P10: return RiuPlotCurveSymbol::LabelLeftOfSymbol; case RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_P50: return RiuPlotCurveSymbol::LabelAboveSymbol; case RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_P90: return RiuPlotCurveSymbol::LabelRightOfSymbol; case RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_MEAN: return RiuPlotCurveSymbol::LabelBelowSymbol; } return RiuPlotCurveSymbol::LabelAboveSymbol; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::Color3f RimWellRftPlot::findCurveColor( RimWellLogCurve* curve ) { RiaRftPltCurveDefinition curveDef = RimWellPlotTools::curveDefFromCurve( curve ); if ( RimWellRftEnsembleCurveSet* ensembleCurveSet = findEnsembleCurveSet( curveDef.address().ensemble() ) ) { return ensembleCurveSet->curveColor( curveDef.address().ensemble(), curveDef.address().summaryCase() ); } return m_dataSourceColors[curveDef.address()]; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::defineCurveColorsAndSymbols( const std::set& allCurveDefs ) { // Clear all ensemble legends RiuQwtPlotWidget* viewer = nullptr; RimWellLogTrack* track = dynamic_cast( plotByIndex( 0 ) ); if ( track ) viewer = track->viewer(); for ( const auto& ensembleLegendPair : m_ensembleLegendFrames ) { if ( viewer && ensembleLegendPair.second ) { viewer->removeOverlayFrame( ensembleLegendPair.second ); delete ensembleLegendPair.second; } } auto ensembles = selectedEnsembles(); for ( RimWellRftEnsembleCurveSet* curveSet : m_ensembleCurveSets() ) { CAF_ASSERT( curveSet ); auto ensemble_it = std::find_if( ensembles.begin(), ensembles.end(), [&curveSet]( const RimSummaryEnsemble* ensemble ) { return curveSet->ensemble() == ensemble; } ); if ( ensemble_it != ensembles.end() ) { if ( viewer && curveSet->legendConfig() ) { if ( !m_ensembleLegendFrames[curveSet] ) { auto m = new RiuDraggableOverlayFrame( viewer->getParentForOverlay(), viewer->overlayMargins() ); m->setContentFrame( curveSet->legendConfig()->makeLegendFrame() ); m_ensembleLegendFrames[curveSet] = m; } viewer->addOverlayFrame( m_ensembleLegendFrames[curveSet] ); } } } std::vector colorTable; RiaColorTables::summaryCurveDefaultPaletteColors().color3fArray().toStdVector( &colorTable ); std::vector symbolTable = { RiuPlotCurveSymbol::SYMBOL_ELLIPSE, RiuPlotCurveSymbol::SYMBOL_RECT, RiuPlotCurveSymbol::SYMBOL_DIAMOND, RiuPlotCurveSymbol::SYMBOL_CROSS, RiuPlotCurveSymbol::SYMBOL_XCROSS, RiuPlotCurveSymbol::SYMBOL_STAR1 }; // Add new curves for ( const RiaRftPltCurveDefinition& curveDefToAdd : allCurveDefs ) { auto colorTableIndex = m_dataSourceColors.size(); auto symbolTableIndex = m_timeStepSymbols.size(); RifDataSourceForRftPlt address = curveDefToAdd.address(); if ( address.ensemble() ) { // Strip the summary case from the address, so that all curves in the ensemble will get the same color address = RifDataSourceForRftPlt( address.ensemble() ); } if ( !m_dataSourceColors.contains( address ) ) { colorTableIndex = colorTableIndex % colorTable.size(); m_dataSourceColors[address] = colorTable[colorTableIndex]; } { if ( !m_timeStepSymbols.contains( curveDefToAdd.timeStep() ) ) { symbolTableIndex = symbolTableIndex % symbolTable.size(); m_timeStepSymbols[curveDefToAdd.timeStep()] = symbolTable[symbolTableIndex]; } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimWellRftPlot::selectedEnsembles() const { std::vector ensembleSets; for ( const RifDataSourceForRftPlt& dataSource : m_selectedSources() ) { if ( dataSource.ensemble() != nullptr ) { ensembleSets.push_back( dataSource.ensemble() ); } } return ensembleSets; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::createEnsembleCurveSets() { const std::vector rftEnsembles = RimWellPlotTools::rftEnsemblesForWell( m_wellPathNameOrSimWellName ); // First delete any curve sets not belonging to the given rftEnsembles std::vector curveSetsToDelete; for ( RimWellRftEnsembleCurveSet* curveSet : m_ensembleCurveSets() ) { if ( std::find( rftEnsembles.begin(), rftEnsembles.end(), curveSet->ensemble() ) == rftEnsembles.end() ) { curveSetsToDelete.push_back( curveSet ); } } for ( RimWellRftEnsembleCurveSet* curveSet : curveSetsToDelete ) { m_ensembleCurveSets.removeChild( curveSet ); delete curveSet; } std::vector colorTable; RiaColorTables::summaryCurveDefaultPaletteColors().color3fArray().toStdVector( &colorTable ); // Then add curve set for any ensembles we haven't already added for ( RimSummaryEnsemble* ensemble : rftEnsembles ) { auto it = std::find_if( m_ensembleCurveSets.begin(), m_ensembleCurveSets.end(), [ensemble]( const RimWellRftEnsembleCurveSet* curveSet ) { return curveSet->ensemble() == ensemble; } ); if ( it == m_ensembleCurveSets.end() ) { RimWellRftEnsembleCurveSet* curveSet = new RimWellRftEnsembleCurveSet; curveSet->setEnsemble( ensemble ); auto index = m_ensembleCurveSets.size(); auto color = colorTable.at( index % colorTable.size() ); curveSet->setCurveColor( color ); m_ensembleCurveSets.push_back( curveSet ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::detachAndDeleteLegendCurves() { for ( auto c : m_legendPlotCurves ) { c->detach(); delete c; } m_legendPlotCurves.clear(); m_legendCurveToEnsembleCurveSet.clear(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::cleanupLegendCurves() { detachAndDeleteLegendCurves(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const { menuBuilder << "RicCreateRftCorrelationReportFeature"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimWellRftEnsembleCurveSet* RimWellRftPlot::selectedEnsembleCurveSet() const { if ( auto selected = caf::SelectionManager::instance()->selectedItemOfType() ) { for ( auto cs : m_ensembleCurveSets ) { if ( cs == selected ) return selected; } } return nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::onSelectionManagerSelectionChanged( const std::set& /*changedSelectionLevels*/ ) { auto newCurveSet = selectedEnsembleCurveSet(); if ( newCurveSet != m_highlightedCurveSet ) { m_highlightedCurveSet = newCurveSet; loadDataAndUpdate(); } auto* newRealization = caf::SelectionManager::instance()->selectedItemOfType(); if ( newRealization != m_selectedRealization ) { m_selectedRealization = newRealization; highlightSelectedRealization(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimSummaryCase* RimWellRftPlot::findClosestRealization( const QPoint& canvasPos ) { auto* track = dynamic_cast( plotByIndex( 0 ) ); if ( !track ) return nullptr; double minDist = 15.0; // pixel threshold RimSummaryCase* closestCase = nullptr; for ( RimWellLogCurve* curve : track->curves() ) { auto* rftCurve = dynamic_cast( curve ); if ( !rftCurve || !rftCurve->summaryCase() || !rftCurve->ensemble() ) continue; auto* qwtCurve = dynamic_cast( rftCurve->plotCurve() ); if ( !qwtCurve ) continue; double dist = std::numeric_limits::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( plotByIndex( 0 ) ); if ( !track ) return; for ( RimWellLogCurve* curve : track->curves() ) { applyCurveAppearance( curve ); curve->updateCurveAppearance(); } if ( track->plotWidget() ) track->plotWidget()->scheduleReplot(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::onLegendItemClicked( std::shared_ptr plotItem, bool toggle, int /*sampleIndex*/ ) { auto wrapper = dynamic_cast( plotItem.get() ); if ( !wrapper ) return; auto qwtCurve = dynamic_cast( wrapper->qwtPlotItem() ); if ( !qwtCurve ) return; auto it = m_legendCurveToEnsembleCurveSet.find( qwtCurve ); if ( it != m_legendCurveToEnsembleCurveSet.end() ) RiuPlotMainWindowTools::selectOrToggleObject( it->second, toggle ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimWellRftEnsembleCurveSet* RimWellRftPlot::findEnsembleCurveSet( RimSummaryEnsemble* ensemble ) const { for ( RimWellRftEnsembleCurveSet* curveSet : m_ensembleCurveSets() ) { if ( ensemble == curveSet->ensemble() ) { return curveSet; } } return nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::rebuildCurves() { for ( auto c : m_ensembleCurveSets ) { c->clearEnsembleStatistics(); } createEnsembleCurveSets(); updateFormationsOnPlot(); syncCurvesFromUiSelection(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimWellRftPlot::selectedTimeSteps() const { return m_selectedTimeSteps(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::setSelectedTimeSteps( const std::vector& timeSteps ) { m_selectedTimeSteps = timeSteps; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellRftPlot::initializeDataSources( RimWellRftPlot* source ) { for ( auto curveSet : m_ensembleCurveSets ) { // Clear the ensemble statistics before applying initial selections // This is necessary to ensure that the statistics are recalculated based on the initial selections curveSet->clearEnsembleStatistics(); } if ( source ) { setOrInitializeDataSources( source->m_selectedSources ); } else { // If no source is provided, initialize with empty sources setOrInitializeDataSources( {} ); // Use the depth unit of the data in a new plot, with the unit of the grid model in the project as fallback auto depthUnit = RiaDefines::DepthUnitType::UNIT_NONE; for ( const auto& dataSource : m_selectedSources() ) { for ( auto unitSystem : dataSource.availableUnitSystems() ) { depthUnit = RiaDefines::fromEclipseUnit( unitSystem ); if ( depthUnit != RiaDefines::DepthUnitType::UNIT_NONE ) break; } if ( depthUnit != RiaDefines::DepthUnitType::UNIT_NONE ) break; } if ( depthUnit == RiaDefines::DepthUnitType::UNIT_NONE ) depthUnit = caseDepthUnit(); if ( depthUnit != RiaDefines::DepthUnitType::UNIT_NONE ) setDepthUnit( depthUnit ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::variant RimWellRftPlot::dataSource() const { // Return the first selected ensemble, if any // If no ensemble is selected, return the first selected summary case, if any for ( const auto& source : m_selectedSources() ) { if ( source.ensemble() ) return source.ensemble(); if ( source.summaryCase() ) return source.summaryCase(); } return {}; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimObservedFmuRftData* RimWellRftPlot::findObservedFmuData( const QString& wellPathName, const QDateTime& timeStep ) const { auto allObservedDataForWell = RimWellPlotTools::observedFmuRftDataForWell( wellPathName ); for ( auto observedData : allObservedDataForWell ) { if ( observedData->rftReader()->availableTimeSteps( wellPathName ).count( timeStep ) ) { return observedData; } } return nullptr; }