From fac5eb5f3b7fe4d384e7ef06bdc9ac8e5ba2486e Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Wed, 14 Oct 2026 15:22:20 +0200 Subject: [PATCH] #8735: Add SummaryPlotCollection multi plot commands and subplots --- .../RicSummaryPlotFeatureImpl.cpp | 25 ++++ .../RicSummaryPlotFeatureImpl.h | 3 + .../Summary/RimEnsembleCurveSetCollection.cpp | 11 +- .../Summary/RimSummaryPlot.cpp | 2 +- .../CMakeLists_files.cmake | 1 + .../RimcSummaryMultiPlot.cpp | 78 +++++++++++ .../RimcSummaryMultiPlot.h | 48 +++++++ .../RimcSummaryPlotCollection.cpp | 128 +++++++++++++++--- .../RimcSummaryPlotCollection.h | 34 ++++- 9 files changed, 308 insertions(+), 22 deletions(-) create mode 100644 ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.cpp create mode 100644 ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.h diff --git a/ApplicationLibCode/Commands/SummaryPlotCommands/RicSummaryPlotFeatureImpl.cpp b/ApplicationLibCode/Commands/SummaryPlotCommands/RicSummaryPlotFeatureImpl.cpp index b007ddd5a4..0668c6dc71 100644 --- a/ApplicationLibCode/Commands/SummaryPlotCommands/RicSummaryPlotFeatureImpl.cpp +++ b/ApplicationLibCode/Commands/SummaryPlotCommands/RicSummaryPlotFeatureImpl.cpp @@ -54,6 +54,7 @@ #include "RimProject.h" #include "RimSummaryCase.h" #include "RimSummaryCurve.h" +#include "RimSummaryEnsemble.h" #include "RimSummaryMultiPlotCollection.h" #include "RimSummaryPlot.h" @@ -547,6 +548,30 @@ RimSummaryPlot* RicSummaryPlotFeatureImpl::createSummaryPlotForEnsemble( const s return newPlot; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimSummaryPlot* RicSummaryPlotFeatureImpl::createSummaryPlotForEnsembles( const std::vector& ensembles, + QStringList summaryAddressFilters ) +{ + RimSummaryPlot* newPlot = new RimSummaryPlot(); + newPlot->enableAutoPlotTitle( true ); + + for ( auto ensemble : ensembles ) + { + if ( !ensemble ) continue; + + auto addresses = applySummaryAddressFiltersToCases( ensemble->allSummaryCases(), summaryAddressFilters ); + for ( const auto& addr : addresses ) + { + newPlot->ensembleCurveSetCollection()->addCurveSet( createCurveSet( ensemble, addr, EnsembleColoringType::NONE, "" ) ); + } + } + newPlot->applyDefaultCurveAppearances(); + + return newPlot; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/Commands/SummaryPlotCommands/RicSummaryPlotFeatureImpl.h b/ApplicationLibCode/Commands/SummaryPlotCommands/RicSummaryPlotFeatureImpl.h index c3d92d1cfa..12fe2472c1 100644 --- a/ApplicationLibCode/Commands/SummaryPlotCommands/RicSummaryPlotFeatureImpl.h +++ b/ApplicationLibCode/Commands/SummaryPlotCommands/RicSummaryPlotFeatureImpl.h @@ -58,6 +58,9 @@ public: EnsembleColoringType ensembleColoringStyle = EnsembleColoringType::NONE, QString ensembleColoringParameter = "" ); + // Creates one plot with one curve set per address and ensemble, to allow comparison of ensembles in the same plot + static RimSummaryPlot* createSummaryPlotForEnsembles( const std::vector& ensembles, QStringList summaryAddressFilters ); + static RimSummaryPlot* createSummaryPlotForCases( const std::vector& summaryCasesToUse, QStringList summaryAddressFilters, bool addHistoryCurves = false ); diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimEnsembleCurveSetCollection.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimEnsembleCurveSetCollection.cpp index e779bd7084..1b90584236 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimEnsembleCurveSetCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimEnsembleCurveSetCollection.cpp @@ -31,6 +31,8 @@ #include "RiuPlotCurve.h" #include "cafPdmFieldReorderCapability.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmObjectScriptingCapability.h" CAF_PDM_SOURCE_INIT( RimEnsembleCurveSetCollection, "RimEnsembleCurveSetCollection" ); @@ -39,9 +41,14 @@ CAF_PDM_SOURCE_INIT( RimEnsembleCurveSetCollection, "RimEnsembleCurveSetCollecti //-------------------------------------------------------------------------------------------------- RimEnsembleCurveSetCollection::RimEnsembleCurveSetCollection() { - CAF_PDM_InitObject( "Ensemble Curve Sets", ":/EnsembleCurveSets16x16.png" ); + CAF_PDM_InitScriptableObjectWithNameAndComment( "Ensemble Curve Sets", + ":/EnsembleCurveSets16x16.png", + "", + "", + "EnsembleCurveSetCollection", + "A collection of ensemble curve sets" ); - CAF_PDM_InitFieldNoDefault( &m_curveSets, "EnsembleCurveSets", "Ensemble Curve Sets" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_curveSets, "EnsembleCurveSets", "Ensemble Curve Sets" ); m_curveSets.uiCapability()->setUiTreeChildrenHidden( false ); caf::PdmFieldReorderCapability::addToFieldWithCallback( &m_curveSets, this, &RimEnsembleCurveSetCollection::onCurveSetsReordered ); diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.cpp index 15f6f8b994..a57c121a5b 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.cpp @@ -173,7 +173,7 @@ RimSummaryPlot::RimSummaryPlot() m_summaryCurveCollection = new RimSummaryCurveCollection; m_summaryCurveCollection->curvesChanged.connect( this, &RimSummaryPlot::onCurveCollectionChanged ); - CAF_PDM_InitFieldNoDefault( &m_ensembleCurveSetCollection, "EnsembleCurveSetCollection", "" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_ensembleCurveSetCollection, "EnsembleCurveSetCollection", "" ); m_ensembleCurveSetCollection = new RimEnsembleCurveSetCollection(); CAF_PDM_InitFieldNoDefault( &m_gridTimeHistoryCurves, "GridTimeHistoryCurves", "" ); diff --git a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake index bf4ced8a56..c263a0156c 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake @@ -1,5 +1,6 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimcSummaryPlotCollection.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimcSummaryMultiPlot.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcSummaryCase.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcSummaryResampleData.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcProject.cpp diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.cpp new file mode 100644 index 0000000000..d359192132 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.cpp @@ -0,0 +1,78 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimcSummaryMultiPlot.h" + +#include "RiaTextStringTools.h" +#include "Summary/RiaSummaryPlotTools.h" + +#include "RimSummaryCase.h" +#include "RimSummaryEnsemble.h" +#include "RimSummaryMultiPlot.h" +#include "RimSummaryPlot.h" +#include "RimcSummaryPlotCollection.h" + +#include "cafPdmAbstractFieldScriptingCapability.h" +#include "cafPdmFieldScriptingCapability.h" + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimSummaryMultiPlot, RimcSummaryMultiPlot_appendSubplot, "AppendSubplot" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimcSummaryMultiPlot_appendSubplot::RimcSummaryMultiPlot_appendSubplot( caf::PdmObjectHandle* self ) + : caf::PdmObjectCreationMethod( self ) +{ + CAF_PDM_InitObject( "Append Subplot", "", "", "Append a new subplot to the summary plot" ); + + CAF_PDM_InitScriptableFieldNoDefault( &m_summaryCases, "SummaryCases", "", "", "", "Summary Cases" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_ensembles, "Ensembles", "", "", "", "Ensembles plotted together in the subplot" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_addressString, "Address", "", "", "", "Formatted address string specifying the plot options" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimcSummaryMultiPlot_appendSubplot::execute() +{ + // A plot without an address is created in a separate multi plot + if ( RiaTextStringTools::splitSkipEmptyParts( m_addressString(), ";" ).isEmpty() ) return std::unexpected( "Specify an address." ); + + auto multiPlot = self(); + + RimSummaryPlot* newPlot = RimcSummaryPlotCollection_newSummaryPlot::createSummaryPlot( m_summaryCases.ptrReferencedObjectsByType(), + m_ensembles.ptrReferencedObjectsByType(), + m_addressString() ); + if ( !newPlot ) return std::unexpected( "Unable to create a subplot. Specify summary cases or ensembles." ); + + newPlot->loadDataAndUpdate(); + RiaSummaryPlotTools::appendPlotsToSummaryMultiPlot( multiPlot, { newPlot } ); + + multiPlot->loadDataAndUpdate(); + multiPlot->updateAllRequiredEditors(); + + return newPlot; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimcSummaryMultiPlot_appendSubplot::classKeywordReturnedType() const +{ + return RimSummaryPlot::classKeywordStatic(); +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.h b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.h new file mode 100644 index 0000000000..16736d0de7 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.h @@ -0,0 +1,48 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafPdmField.h" +#include "cafPdmObjectHandle.h" +#include "cafPdmObjectMethod.h" +#include "cafPdmPtrArrayField.h" + +#include + +class RimSummaryCase; +class RimSummaryEnsemble; + +//================================================================================================== +/// +//================================================================================================== +class RimcSummaryMultiPlot_appendSubplot : public caf::PdmObjectCreationMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimcSummaryMultiPlot_appendSubplot( caf::PdmObjectHandle* self ); + + std::expected execute() override; + QString classKeywordReturnedType() const override; + +private: + caf::PdmField m_addressString; + caf::PdmPtrArrayField m_summaryCases; + caf::PdmPtrArrayField m_ensembles; +}; diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.cpp index 7a32a63423..aafa428e12 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.cpp @@ -27,6 +27,7 @@ #include "RimSummaryCase.h" #include "RimSummaryEnsemble.h" +#include "RimSummaryMultiPlot.h" #include "RimSummaryPlot.h" #include "RimSummaryPlotCollection.h" @@ -37,6 +38,9 @@ #include +#include +#include + CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimSummaryPlotCollection, RimcSummaryPlotCollection_newSummaryPlot, "NewSummaryPlot" ); //-------------------------------------------------------------------------------------------------- @@ -49,6 +53,7 @@ RimcSummaryPlotCollection_newSummaryPlot::RimcSummaryPlotCollection_newSummaryPl CAF_PDM_InitScriptableFieldNoDefault( &m_summaryCases, "SummaryCases", "", "", "", "Summary Cases" ); CAF_PDM_InitScriptableFieldNoDefault( &m_ensemble, "Ensemble", "", "", "", "Ensemble" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_ensembles, "Ensembles", "", "", "", "Ensembles plotted together in the same plot" ); CAF_PDM_InitScriptableFieldNoDefault( &m_addressString, "Address", "", "", "", "Formatted address string specifying the plot options" ); } @@ -57,31 +62,74 @@ RimcSummaryPlotCollection_newSummaryPlot::RimcSummaryPlotCollection_newSummaryPl //-------------------------------------------------------------------------------------------------- std::expected RimcSummaryPlotCollection_newSummaryPlot::execute() { - QStringList addressStrings = RiaTextStringTools::splitSkipEmptyParts( m_addressString(), ";" ); + RimSummaryPlot* newPlot = + createSummaryPlot( m_summaryCases.ptrReferencedObjectsByType(), allEnsembles( m_ensemble(), m_ensembles ), m_addressString() ); + + // Plots created without an address are already added to a multi plot + if ( newPlot && !newPlot->firstAncestorOrThisOfType() ) + { + newPlot->loadDataAndUpdate(); + RiaSummaryPlotTools::createAndAppendSingleSummaryMultiPlot( newPlot ); + } + + return newPlot; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector + RimcSummaryPlotCollection_newSummaryPlot::allEnsembles( RimSummaryEnsemble* ensemble, + const caf::PdmPtrArrayField& ensembleArray ) +{ + std::vector ensembles = ensembleArray.ptrReferencedObjectsByType(); + if ( ensemble && std::find( ensembles.begin(), ensembles.end(), ensemble ) == ensembles.end() ) + { + ensembles.insert( ensembles.begin(), ensemble ); + } + return ensembles; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimSummaryPlot* RimcSummaryPlotCollection_newSummaryPlot::createSummaryPlot( const std::vector& summaryCases, + const std::vector& ensembleCandidates, + const QString& address ) +{ + QStringList addressStrings = RiaTextStringTools::splitSkipEmptyParts( address, ";" ); + + // References that cannot be resolved are returned as nullptr + std::vector ensembles; + std::copy_if( ensembleCandidates.begin(), + ensembleCandidates.end(), + std::back_inserter( ensembles ), + []( auto e ) { return e != nullptr; } ); RimSummaryPlot* newPlot = nullptr; - if ( m_ensemble ) + + if ( !ensembles.empty() ) { if ( !addressStrings.empty() ) { - auto summaryCases = m_ensemble->allSummaryCases(); - // Summary addresses are created on demand, make sure they are available before applying address filters - for ( auto summaryCase : summaryCases ) + for ( auto ensemble : ensembles ) { - if ( auto reader = summaryCase ? summaryCase->summaryReader() : nullptr ) reader->createAddressesIfRequired(); + for ( auto summaryCase : ensemble->allSummaryCases() ) + { + if ( auto reader = summaryCase ? summaryCase->summaryReader() : nullptr ) reader->createAddressesIfRequired(); + } } - newPlot = RicSummaryPlotFeatureImpl::createSummaryPlotForEnsemble( summaryCases, m_ensemble, addressStrings ); + newPlot = RicSummaryPlotFeatureImpl::createSummaryPlotForEnsembles( ensembles, addressStrings ); } else { - newPlot = RicNewSummaryEnsembleCurveSetFeature::createPlotForCurveSetsAndUpdate( { m_ensemble } ); + newPlot = RicNewSummaryEnsembleCurveSetFeature::createPlotForCurveSetsAndUpdate( ensembles ); } } - if ( !m_summaryCases.empty() ) + if ( !summaryCases.empty() ) { - std::vector summaryCases = m_summaryCases.ptrReferencedObjectsByType(); if ( !addressStrings.empty() ) { newPlot = RicSummaryPlotFeatureImpl::createSummaryPlotForCases( summaryCases, addressStrings ); @@ -92,12 +140,6 @@ std::expected RimcSummaryPlotCollection_newSumma } } - if ( newPlot ) - { - newPlot->loadDataAndUpdate(); - RiaSummaryPlotTools::createAndAppendSingleSummaryMultiPlot( newPlot ); - } - return newPlot; } @@ -108,3 +150,57 @@ QString RimcSummaryPlotCollection_newSummaryPlot::classKeywordReturnedType() con { return RimSummaryPlot::classKeywordStatic(); } + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimSummaryPlotCollection, RimcSummaryPlotCollection_newSummaryMultiPlot, "NewSummaryMultiPlot" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimcSummaryPlotCollection_newSummaryMultiPlot::RimcSummaryPlotCollection_newSummaryMultiPlot( caf::PdmObjectHandle* self ) + : caf::PdmObjectCreationMethod( self ) +{ + CAF_PDM_InitObject( "Create Summary Multi Plot", "", "", "Create a new multi plot with one subplot for each address" ); + + CAF_PDM_InitScriptableFieldNoDefault( &m_summaryCases, "SummaryCases", "", "", "", "Summary Cases" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_ensembles, "Ensembles", "", "", "", "Ensembles plotted together in each subplot" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_addressStrings, "Addresses", "", "", "", "One formatted address string for each subplot" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimcSummaryPlotCollection_newSummaryMultiPlot::execute() +{ + if ( m_addressStrings().empty() ) return std::unexpected( "Specify at least one address." ); + + // A subplot without an address is created in a separate multi plot + for ( const QString& address : m_addressStrings() ) + { + if ( RiaTextStringTools::splitSkipEmptyParts( address, ";" ).isEmpty() ) return std::unexpected( "Addresses cannot be empty." ); + } + + std::vector plots; + for ( const QString& address : m_addressStrings() ) + { + RimSummaryPlot* plot = RimcSummaryPlotCollection_newSummaryPlot::createSummaryPlot( m_summaryCases.ptrReferencedObjectsByType(), + m_ensembles.ptrReferencedObjectsByType(), + address ); + if ( plot ) + { + plot->loadDataAndUpdate(); + plots.push_back( plot ); + } + } + + if ( plots.empty() ) return std::unexpected( "Unable to create subplots. Specify summary cases or ensembles." ); + + return RiaSummaryPlotTools::createAndAppendSummaryMultiPlot( plots ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimcSummaryPlotCollection_newSummaryMultiPlot::classKeywordReturnedType() const +{ + return RimSummaryMultiPlot::classKeywordStatic(); +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.h b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.h index a3cd63265e..bb8cfc36ca 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.h +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.h @@ -31,6 +31,7 @@ class RimSummaryCase; class RimSummaryPlotCollection; class RimSummaryEnsemble; +class RimSummaryPlot; //================================================================================================== /// @@ -45,8 +46,35 @@ public: std::expected execute() override; QString classKeywordReturnedType() const override; + static std::vector allEnsembles( RimSummaryEnsemble* ensemble, + const caf::PdmPtrArrayField& ensembleArray ); + + static RimSummaryPlot* createSummaryPlot( const std::vector& summaryCases, + const std::vector& ensembles, + const QString& address ); + private: - caf::PdmField m_addressString; - caf::PdmPtrArrayField m_summaryCases; - caf::PdmPtrField m_ensemble; + caf::PdmField m_addressString; + caf::PdmPtrArrayField m_summaryCases; + caf::PdmPtrField m_ensemble; + caf::PdmPtrArrayField m_ensembles; +}; + +//================================================================================================== +/// +//================================================================================================== +class RimcSummaryPlotCollection_newSummaryMultiPlot : public caf::PdmObjectCreationMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimcSummaryPlotCollection_newSummaryMultiPlot( caf::PdmObjectHandle* self ); + + std::expected execute() override; + QString classKeywordReturnedType() const override; + +private: + caf::PdmField> m_addressStrings; + caf::PdmPtrArrayField m_summaryCases; + caf::PdmPtrArrayField m_ensembles; };