From 342f19128838ddd2232c57b01f79c69d161043b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= <82032112+jorgenherje@users.noreply.github.com> Date: Tue, 2 May 2023 11:18:45 +0200 Subject: [PATCH] Add filtering/exclude of rows in SummaryTable (#10193) - Add selector for excluding rows in table - Row selection is based on category, vector, threshold - Move data containers to utils class - TreeSelectionEditor: Add context menu to invert selection - Improved naming of menu items - Guard plotDefinition before connect signal/slots --------- Co-authored-by: Magne Sjaastad --- .../Flow/RimWellConnectivityTable.cpp | 2 - .../Summary/CMakeLists_files.cmake | 2 + .../Summary/RimSummaryTable.cpp | 258 ++++++++++-------- .../Summary/RimSummaryTable.h | 19 +- .../Summary/RimSummaryTableTools.cpp | 83 ++++++ .../Summary/RimSummaryTableTools.h | 53 ++++ .../UserInterface/RiuQwtPlotWidget.cpp | 21 +- .../cafPdmUiTreeSelectionEditor.cpp | 52 +++- .../cafPdmUiTreeSelectionEditor.h | 2 + .../cafPdmUiTreeSelectionQModel.cpp | 36 +++ .../cafPdmUiTreeSelectionQModel.h | 2 + 11 files changed, 402 insertions(+), 128 deletions(-) create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTableTools.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTableTools.h diff --git a/ApplicationLibCode/ProjectDataModel/Flow/RimWellConnectivityTable.cpp b/ApplicationLibCode/ProjectDataModel/Flow/RimWellConnectivityTable.cpp index fa61ed9ec2..93bc3d6644 100644 --- a/ApplicationLibCode/ProjectDataModel/Flow/RimWellConnectivityTable.cpp +++ b/ApplicationLibCode/ProjectDataModel/Flow/RimWellConnectivityTable.cpp @@ -152,11 +152,9 @@ RimWellConnectivityTable::RimWellConnectivityTable() // Producer/Injector tracer configuration CAF_PDM_InitFieldNoDefault( &m_selectedProducerTracersUiField, "SelectedProducerTracers", "Producer Tracers" ); - m_selectedProducerTracersUiField.xmlCapability()->disableIO(); m_selectedProducerTracersUiField.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); m_selectedProducerTracersUiField.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN ); CAF_PDM_InitFieldNoDefault( &m_selectedInjectorTracersUiField, "SelectedInjectorTracers", "Injector Tracers" ); - m_selectedInjectorTracersUiField.xmlCapability()->disableIO(); m_selectedInjectorTracersUiField.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); m_selectedInjectorTracersUiField.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN ); CAF_PDM_InitField( &m_syncSelectedInjectorsFromProducerSelection, "SyncSelectedProdInj", false, "Synch Communicators ->" ); diff --git a/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake index 3a36cd524c..7ca8a29fcf 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake @@ -49,6 +49,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RimCsvSummaryCase.h ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTable.h ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableCollection.h + ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableTools.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -102,6 +103,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimCsvSummaryCase.cpp ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTable.cpp ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableCollection.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableTools.cpp ) list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTable.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTable.cpp index 1ae4e6c5bf..ebf0c4cd14 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTable.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTable.cpp @@ -56,11 +56,11 @@ RimSummaryTable::RimSummaryTable() CAF_PDM_InitFieldNoDefault( &m_case, "SummaryCase", "Case" ); m_case.uiCapability()->setUiTreeChildrenHidden( true ); - CAF_PDM_InitFieldNoDefault( &m_vector, "Vectors", "Vector" ); + CAF_PDM_InitFieldNoDefault( &m_vector, "Vector", "Vector" ); m_vector.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); - CAF_PDM_InitFieldNoDefault( &m_categories, "Categories", "Category" ); - m_categories.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); - m_categories = RifEclipseSummaryAddress::SummaryVarCategory::SUMMARY_WELL; + CAF_PDM_InitFieldNoDefault( &m_category, "Categories", "Category" ); + m_category.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); + m_category = RifEclipseSummaryAddress::SummaryVarCategory::SUMMARY_WELL; CAF_PDM_InitFieldNoDefault( &m_resamplingSelection, "ResamplingSelection", "Date Resampling" ); m_resamplingSelection.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); @@ -68,6 +68,9 @@ RimSummaryTable::RimSummaryTable() CAF_PDM_InitField( &m_thresholdValue, "ThresholdValue", 0.0, "Threshold" ); + CAF_PDM_InitFieldNoDefault( &m_excludedRowsUiField, "ExcludedTableRows", "Exclude Rows" ); + m_excludedRowsUiField.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); + // Table settings CAF_PDM_InitField( &m_showValueLabels, "ShowValueLabels", false, "Show Value Labels" ); @@ -107,9 +110,10 @@ void RimSummaryTable::setDefaultCaseAndCategoryAndVectorName() { const auto summaryCases = getToplevelSummaryCases(); m_case = nullptr; - m_categories = RifEclipseSummaryAddress::SUMMARY_WELL; + m_category = RifEclipseSummaryAddress::SUMMARY_WELL; m_vector = ""; - m_tableName = createTableName(); + + m_tableName = createTableName(); if ( summaryCases.empty() ) return; m_case = summaryCases.front(); @@ -117,7 +121,7 @@ void RimSummaryTable::setDefaultCaseAndCategoryAndVectorName() const auto summaryReader = m_case->summaryReader(); if ( !summaryReader ) return; - const auto categoryVectors = getCategoryVectorsFromSummaryReader( summaryReader, m_categories() ); + const auto categoryVectors = getCategoryVectorFromSummaryReader( summaryReader, m_category() ); if ( !categoryVectors.empty() ) { m_vector = *categoryVectors.begin(); @@ -132,10 +136,10 @@ void RimSummaryTable::setFromCaseAndCategoryAndVectorName( RimSummaryCase* RifEclipseSummaryAddress::SummaryVarCategory category, const QString& vectorName ) { - m_case = summaryCase; - m_categories = category; - m_vector = vectorName; - m_tableName = createTableName(); + m_case = summaryCase; + m_category = category; + m_vector = vectorName; + m_tableName = createTableName(); onLoadDataAndUpdate(); } @@ -173,7 +177,7 @@ void RimSummaryTable::fieldChangedByUi( const caf::PdmFieldHandle* changedField, if ( m_case ) { auto* summaryReader = m_case->summaryReader(); - const auto categoryVectors = getCategoryVectorsFromSummaryReader( summaryReader, m_categories() ); + const auto categoryVectors = getCategoryVectorFromSummaryReader( summaryReader, m_category() ); if ( summaryReader && !categoryVectors.empty() ) { m_vector = *categoryVectors.begin(); @@ -189,12 +193,12 @@ void RimSummaryTable::fieldChangedByUi( const caf::PdmFieldHandle* changedField, } onLoadDataAndUpdate(); } - else if ( changedField == &m_categories ) + else if ( changedField == &m_category ) { if ( m_case ) { auto* summaryReader = m_case->summaryReader(); - const auto categoryVectors = getCategoryVectorsFromSummaryReader( summaryReader, m_categories() ); + const auto categoryVectors = getCategoryVectorFromSummaryReader( summaryReader, m_category() ); if ( summaryReader && !categoryVectors.empty() ) { m_vector = *categoryVectors.begin(); @@ -222,6 +226,10 @@ void RimSummaryTable::fieldChangedByUi( const caf::PdmFieldHandle* changedField, } onLoadDataAndUpdate(); } + else if ( changedField == &m_excludedRowsUiField ) + { + onLoadDataAndUpdate(); + } else if ( changedField == &m_isAutomaticName && m_isAutomaticName ) { m_tableName = createTableName(); @@ -263,10 +271,11 @@ void RimSummaryTable::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering dataGroup.add( &m_tableName ); dataGroup.add( &m_isAutomaticName ); dataGroup.add( &m_case ); - dataGroup.add( &m_categories ); + dataGroup.add( &m_category ); dataGroup.add( &m_vector ); dataGroup.add( &m_resamplingSelection ); dataGroup.add( &m_thresholdValue ); + dataGroup.add( &m_excludedRowsUiField ); caf::PdmUiGroup* tableSettingsGroup = uiOrdering.addNewGroup( "Table Settings" ); tableSettingsGroup->add( &m_showValueLabels ); @@ -300,7 +309,7 @@ QList RimSummaryTable::calculateValueOptions( const caf: options.push_back( caf::PdmOptionItemInfo( summaryCase->displayCaseName(), summaryCase, false, summaryCase->uiIconProvider() ) ); } } - else if ( fieldNeedingOptions == &m_categories ) + else if ( fieldNeedingOptions == &m_category ) { options.push_back( caf::PdmOptionItemInfo( caf::AppEnum::uiText( RifEclipseSummaryAddress::SummaryVarCategory::SUMMARY_WELL ), @@ -317,13 +326,21 @@ QList RimSummaryTable::calculateValueOptions( const caf: auto* summaryReader = m_case->summaryReader(); if ( summaryReader ) { - const auto categoryVectorsUnion = getCategoryVectorsFromSummaryReader( summaryReader, m_categories() ); + const auto categoryVectorsUnion = getCategoryVectorFromSummaryReader( summaryReader, m_category() ); for ( const auto& vectorName : categoryVectorsUnion ) { options.push_back( caf::PdmOptionItemInfo( vectorName, vectorName ) ); } } } + else if ( fieldNeedingOptions == &m_excludedRowsUiField ) + { + const auto vectorNames = RimSummaryTableTools::categoryNames( m_tableData.vectorDataCollection ); + for ( const auto& vectorName : vectorNames ) + { + options.push_back( caf::PdmOptionItemInfo( vectorName, vectorName ) ); + } + } else if ( fieldNeedingOptions == &m_axisTitleFontSize || fieldNeedingOptions == &m_axisLabelFontSize || fieldNeedingOptions == &m_valueLabelFontSize ) { @@ -338,6 +355,8 @@ QList RimSummaryTable::calculateValueOptions( const caf: void RimSummaryTable::onLoadDataAndUpdate() { updateMdiWindowVisibility(); + createTableData(); + setExcludedRowsUiSelectionsFromTableData(); if ( m_matrixPlotWidget == nullptr || m_case == nullptr ) { @@ -350,82 +369,12 @@ void RimSummaryTable::onLoadDataAndUpdate() return; } - // Struct for storing vector data - struct VectorData - { - QString category; - QString name; - std::vector values; - time_t firstTimeStep; - time_t lastTimeStep; - bool hasValueAboveThreshold; - }; - - // Create time step value for vectors with no values above threshold - const time_t invalidTimeStep = 0; - - // Get all summary addresses for selected category (group, region, well) - std::vector vectorDataCollection; - std::set timeStepsUnion; - const auto summaryAddresses = getSummaryAddressesFromReader( summaryReader, m_categories(), m_vector() ); - QString unitName; - for ( const auto& adr : summaryAddresses ) - { - std::vector values; - summaryReader->values( adr, &values ); - const std::vector timeSteps = summaryReader->timeSteps( adr ); - const QString vectorName = QString::fromStdString( adr.vectorName() ); - const QString categoryName = getCategoryNameFromAddress( adr ); - unitName = QString::fromStdString( summaryReader->unitName( adr ) ); - - // Get re-sampled time steps and values - const auto [resampledTimeSteps, resampledValues] = - RiaSummaryTools::resampledValuesForPeriod( adr, timeSteps, values, m_resamplingSelection() ); - - // Detect if values contain at least one value above threshold - const auto valueAboveThresholdItr = - std::find_if( resampledValues.begin(), resampledValues.end(), [&]( double value ) { return value > m_thresholdValue; } ); - const bool hasValueAboveThreshold = valueAboveThresholdItr != resampledValues.end(); - - // Find first and last time step with value above 0.0 when hasValueAboveThreshold flag is true (first and last should be - // valid/invalid simultaneously) - const auto firstTimeStepItr = - std::find_if( resampledValues.begin(), resampledValues.end(), [&]( double value ) { return value > 0.0; } ); - const auto lastTimeStepItr = - std::find_if( resampledValues.rbegin(), resampledValues.rend(), [&]( double value ) { return value > 0.0; } ); - const auto firstIdx = static_cast( std::distance( resampledValues.begin(), firstTimeStepItr ) ); - const auto lastIdx = resampledValues.size() - static_cast( std::distance( resampledValues.rbegin(), lastTimeStepItr ) ) - 1; - const auto firstTimeStep = hasValueAboveThreshold ? resampledTimeSteps[firstIdx] : invalidTimeStep; - const auto lastTimeStep = hasValueAboveThreshold ? resampledTimeSteps[lastIdx] : invalidTimeStep; - - // Add to vector of VectorData - VectorData vectorData{ .category = categoryName, - .name = vectorName, - .values = resampledValues, - .firstTimeStep = firstTimeStep, - .lastTimeStep = lastTimeStep, - .hasValueAboveThreshold = hasValueAboveThreshold }; - vectorDataCollection.push_back( vectorData ); - - // Build union of resampled time steps - timeStepsUnion.insert( resampledTimeSteps.begin(), resampledTimeSteps.end() ); - } - - // Sort vector data on date (vectors with no values above threshold placed last): - std::sort( vectorDataCollection.begin(), - vectorDataCollection.end(), - []( const VectorData& v1, const VectorData& v2 ) - { - if ( !v1.hasValueAboveThreshold ) return false; - if ( v1.hasValueAboveThreshold && !v2.hasValueAboveThreshold ) return true; - if ( v1.firstTimeStep < v2.firstTimeStep ) return true; - if ( v1.firstTimeStep == v2.firstTimeStep && v1.lastTimeStep < v2.lastTimeStep ) return true; - return false; - } ); + // Exclude rows that are selected + const auto excludedRows = std::set( m_excludedRowsUiField().begin(), m_excludedRowsUiField().end() ); // Convert to strings std::vector timeStepStrings; - for ( const auto& timeStep : timeStepsUnion ) + for ( const auto& timeStep : m_tableData.timeStepsUnion ) { timeStepStrings.push_back( RiaQDateTimeTools::fromTime_t( timeStep ).toString( dateFormatString() ) ); } @@ -433,28 +382,25 @@ void RimSummaryTable::onLoadDataAndUpdate() // Clear matrix plot m_matrixPlotWidget->clearPlotData(); m_matrixPlotWidget->setColumnHeaders( timeStepStrings ); - double maxValue = 0.0; - double minValue = 0.0; - for ( const auto& vectorData : vectorDataCollection ) + for ( const auto& vectorData : m_tableData.vectorDataCollection ) { - const auto maxRowValue = *std::max_element( vectorData.values.begin(), vectorData.values.end() ); - const auto minRowValue = *std::min_element( vectorData.values.begin(), vectorData.values.end() ); - if ( maxRowValue < m_thresholdValue() ) continue; - maxValue = std::max( maxValue, maxRowValue ); - minValue = std::min( minValue, minRowValue ); + if ( excludedRows.contains( vectorData.category ) ) continue; + m_matrixPlotWidget->setRowValues( vectorData.category, vectorData.values ); } if ( m_legendConfig ) { - m_legendConfig->setAutomaticRanges( minValue, maxValue, 0.0, 0.0 ); + m_legendConfig->setAutomaticRanges( m_tableData.minValue, m_tableData.maxValue, 0.0, 0.0 ); } // Set titles and font sizes - const QString title = - QString( "Summary Table - %1 [%2]
Date Resampling: %3
" ).arg( m_vector() ).arg( unitName ).arg( m_resamplingSelection().uiText() ); + const QString title = QString( "Summary Table - %1 [%2]
Date Resampling: %3
" ) + .arg( m_vector() ) + .arg( m_tableData.unitName ) + .arg( m_resamplingSelection().uiText() ); m_matrixPlotWidget->setPlotTitle( title ); - m_matrixPlotWidget->setRowTitle( QString( "%1s" ).arg( m_categories().uiText() ) ); + m_matrixPlotWidget->setRowTitle( QString( "%1s" ).arg( m_category().uiText() ) ); m_matrixPlotWidget->setColumnTitle( "Time steps" ); m_matrixPlotWidget->setPlotTitleFontSize( titleFontSize() ); m_matrixPlotWidget->setLegendFontSize( legendFontSize() ); @@ -591,7 +537,7 @@ std::set RimSummaryTable::getSummaryAddressesFromReade RifEclipseSummaryAddress::SummaryVarCategory category, const QString& vector ) const { - if ( !summaryReader ) return std::set(); + if ( !summaryReader ) return {}; std::set categoryAddresses; const std::set allResultAddresses = summaryReader->allResultAddresses(); @@ -607,15 +553,15 @@ std::set RimSummaryTable::getSummaryAddressesFromReade //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -std::set RimSummaryTable::getCategoryVectorsFromSummaryReader( const RifSummaryReaderInterface* summaryReader, - RifEclipseSummaryAddress::SummaryVarCategory category ) const +std::set RimSummaryTable::getCategoryVectorFromSummaryReader( const RifSummaryReaderInterface* summaryReader, + RifEclipseSummaryAddress::SummaryVarCategory category ) const { - if ( !summaryReader ) return std::set(); + if ( !summaryReader ) return {}; if ( category != RifEclipseSummaryAddress::SummaryVarCategory::SUMMARY_WELL && category != RifEclipseSummaryAddress::SummaryVarCategory::SUMMARY_GROUP && category != RifEclipseSummaryAddress::SummaryVarCategory::SUMMARY_REGION ) { - return std::set(); + return {}; } std::set categoryVectors; @@ -658,3 +604,101 @@ std::vector RimSummaryTable::getToplevelSummaryCases() const if ( !summaryCaseMainCollection ) return {}; return summaryCaseMainCollection->topLevelSummaryCases(); } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryTable::createTableData() +{ + m_tableData = TableData(); + m_tableData.thresholdValue = m_thresholdValue(); + + const auto summaryReader = m_case->summaryReader(); + if ( !summaryReader ) + { + return; + } + + // Create time step value for vectors with no values above threshold + const time_t invalidTimeStep = 0; + + // Get all summary addresses for selected category (group, region, well) + const auto summaryAddresses = getSummaryAddressesFromReader( summaryReader, m_category(), m_vector() ); + QString unitName; + for ( const auto& adr : summaryAddresses ) + { + std::vector values; + summaryReader->values( adr, &values ); + const std::vector timeSteps = summaryReader->timeSteps( adr ); + const QString vectorName = QString::fromStdString( adr.vectorName() ); + const QString categoryName = getCategoryNameFromAddress( adr ); + + // Get re-sampled time steps and values + const auto& [resampledTimeSteps, resampledValues] = + RiaSummaryTools::resampledValuesForPeriod( adr, timeSteps, values, m_resamplingSelection() ); + + if ( resampledValues.empty() ) continue; + + // Exclude vectors with values BELOW threshold - to include visualization of values equal to threshold! + const auto maxRowValue = *std::max_element( resampledValues.begin(), resampledValues.end() ); + const auto minRowValue = *std::min_element( resampledValues.begin(), resampledValues.end() ); + if ( maxRowValue < m_tableData.thresholdValue ) continue; + + // Detect if values contain at least one value ABOVE threshold + const bool hasValueAboveThreshold = RimSummaryTableTools::hasValueAboveThreshold( resampledValues, m_tableData.thresholdValue ); + + // Find first and last time step with value above 0.0 when hasValueAboveThreshold flag is true (first and last should be + // valid/invalid simultaneously) + const auto firstTimeStepItr = + std::find_if( resampledValues.begin(), resampledValues.end(), [&]( double value ) { return value > 0.0; } ); + const auto lastTimeStepItr = + std::find_if( resampledValues.rbegin(), resampledValues.rend(), [&]( double value ) { return value > 0.0; } ); + const auto firstIdx = static_cast( std::distance( resampledValues.begin(), firstTimeStepItr ) ); + const auto lastIdx = resampledValues.size() - static_cast( std::distance( resampledValues.rbegin(), lastTimeStepItr ) ) - 1; + const auto firstTimeStep = hasValueAboveThreshold ? resampledTimeSteps[firstIdx] : invalidTimeStep; + const auto lastTimeStep = hasValueAboveThreshold ? resampledTimeSteps[lastIdx] : invalidTimeStep; + + // Add to collection of VectorData for table data + VectorData vectorData{ .category = categoryName, + .name = vectorName, + .values = resampledValues, + .firstTimeStep = firstTimeStep, + .lastTimeStep = lastTimeStep }; + m_tableData.vectorDataCollection.push_back( vectorData ); + + // Update min/max values + m_tableData.maxValue = std::max( m_tableData.maxValue, maxRowValue ); + m_tableData.minValue = std::min( m_tableData.minValue, minRowValue ); + + // Build union of resampled time steps + m_tableData.timeStepsUnion.insert( resampledTimeSteps.begin(), resampledTimeSteps.end() ); + + // Set unit name + if ( m_tableData.unitName.isEmpty() ) + { + m_tableData.unitName = QString::fromStdString( summaryReader->unitName( adr ) ); + } + } + + // Sort vector data on date + RimSummaryTableTools::sortVectorDataOnDate( m_tableData ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryTable::setExcludedRowsUiSelectionsFromTableData() +{ + const auto initialSelections = std::set( m_excludedRowsUiField().begin(), m_excludedRowsUiField().end() ); + + std::vector newSelections; + const auto categoryNames = RimSummaryTableTools::categoryNames( m_tableData.vectorDataCollection ); + for ( const auto& categoryName : categoryNames ) + { + if ( initialSelections.contains( categoryName ) ) + { + newSelections.push_back( categoryName ); + } + } + m_excludedRowsUiField.setValueWithFieldChanged( newSelections ); +} diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTable.h b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTable.h index dfb3337132..34e28fcd49 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTable.h +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTable.h @@ -24,6 +24,8 @@ #include "RifEclipseSummaryAddress.h" +#include "RimSummaryTableTools.h" + #include "cafPdmField.h" #include "cafPdmPtrField.h" @@ -90,8 +92,8 @@ private: std::set getSummaryAddressesFromReader( const RifSummaryReaderInterface* summaryReader, RifEclipseSummaryAddress::SummaryVarCategory category, const QString& vector ) const; - std::set getCategoryVectorsFromSummaryReader( const RifSummaryReaderInterface* summaryReader, - RifEclipseSummaryAddress::SummaryVarCategory category ) const; + std::set getCategoryVectorFromSummaryReader( const RifSummaryReaderInterface* summaryReader, + RifEclipseSummaryAddress::SummaryVarCategory category ) const; QString getCategoryNameFromAddress( const RifEclipseSummaryAddress& address ) const; std::vector getToplevelSummaryCases() const; @@ -104,15 +106,26 @@ private: caf::PdmField m_isAutomaticName; caf::PdmPtrField m_case; - caf::PdmField> m_categories; + caf::PdmField> m_category; caf::PdmField m_vector; caf::PdmField> m_resamplingSelection; caf::PdmField m_thresholdValue; + caf::PdmField> m_excludedRowsUiField; + caf::PdmChildField m_legendConfig; caf::PdmField m_axisTitleFontSize; caf::PdmField m_axisLabelFontSize; caf::PdmField m_valueLabelFontSize; caf::PdmField m_showValueLabels; + +private: + using VectorData = RimSummaryTableTools::VectorData; + using TableData = RimSummaryTableTools::TableData; + + TableData m_tableData; + + void createTableData(); + void setExcludedRowsUiSelectionsFromTableData(); }; diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTableTools.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTableTools.cpp new file mode 100644 index 0000000000..ec55e22151 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTableTools.cpp @@ -0,0 +1,83 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2023- 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 "RimSummaryTableTools.h" + +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimSummaryTableTools::hasValueAboveThreshold( const std::vector& values, double thresholdValue ) +{ + // Detect if values contain at least one value above threshold + const auto valueAboveThresholdItr = std::find_if( values.begin(), values.end(), [&]( double value ) { return value > thresholdValue; } ); + return valueAboveThresholdItr != values.end(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector + RimSummaryTableTools::vectorsAboveThreshold( const std::vector& vectorDataCollection, double thresholdValue ) +{ + std::vector vectorsAboveThreshold; + for ( const auto& vectorData : vectorDataCollection ) + { + if ( hasValueAboveThreshold( vectorData.values, thresholdValue ) ) + { + vectorsAboveThreshold.push_back( vectorData ); + } + } + return vectorsAboveThreshold; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryTableTools::sortVectorDataOnDate( TableData& rTableData ) +{ + // Sort vector data on date (vectors with no values above threshold placed last): + std::sort( rTableData.vectorDataCollection.begin(), + rTableData.vectorDataCollection.end(), + [&rTableData]( const VectorData& v1, const VectorData& v2 ) + { + const bool firstHasValueAboveThreshold = hasValueAboveThreshold( v1.values, rTableData.thresholdValue ); + const bool secondHasValueAboveThreshold = hasValueAboveThreshold( v2.values, rTableData.thresholdValue ); + if ( !firstHasValueAboveThreshold ) return false; + if ( firstHasValueAboveThreshold && !secondHasValueAboveThreshold ) return true; + if ( v1.firstTimeStep < v2.firstTimeStep ) return true; + if ( v1.firstTimeStep == v2.firstTimeStep && v1.lastTimeStep < v2.lastTimeStep ) return true; + return false; + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimSummaryTableTools::categoryNames( const std::vector& vectorDataCollection ) +{ + if ( vectorDataCollection.empty() ) return {}; + + std::vector categoryNames; + for ( const auto& vectorData : vectorDataCollection ) + { + categoryNames.push_back( vectorData.category ); + } + return categoryNames; +} diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTableTools.h b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTableTools.h new file mode 100644 index 0000000000..3e86489f65 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryTableTools.h @@ -0,0 +1,53 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2023- 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 + +#include +#include + +namespace RimSummaryTableTools +{ +// Struct for storing vector data +struct VectorData +{ + QString category; + QString name; + std::vector values; + time_t firstTimeStep; + time_t lastTimeStep; +}; + +// Struct for storing table data +struct TableData +{ + std::vector vectorDataCollection; + std::set timeStepsUnion; + double maxValue = 0.0; + double minValue = 0.0; + double thresholdValue = 0.0; + QString unitName; +}; + +bool hasValueAboveThreshold( const std::vector& values, double thresholdValue ); +std::vector vectorsAboveThreshold( const std::vector& vectorDataCollection, double thresholdValue ); +void sortVectorDataOnDate( TableData& rTableData ); +std::vector categoryNames( const std::vector& vectorDataCollection ); +} // namespace RimSummaryTableTools diff --git a/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.cpp b/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.cpp index 7a9d04f270..168350bc24 100644 --- a/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.cpp +++ b/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.cpp @@ -94,15 +94,18 @@ RiuQwtPlotWidget::RiuQwtPlotWidget( RimPlot* plotDefinition, QWidget* parent ) setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ); - connect( this, SIGNAL( plotSelected( bool ) ), plotDefinition, SLOT( onPlotSelected( bool ) ) ); - connect( this, SIGNAL( axisSelected( RiuPlotAxis, bool ) ), plotDefinition, SLOT( onAxisSelected( RiuPlotAxis, bool ) ) ); - connect( this, - SIGNAL( plotItemSelected( std::shared_ptr, bool, int ) ), - plotDefinition, - SLOT( onPlotItemSelected( std::shared_ptr, bool, int ) ) ); - connect( this, SIGNAL( onKeyPressEvent( QKeyEvent* ) ), plotDefinition, SLOT( onKeyPressEvent( QKeyEvent* ) ) ); - connect( this, SIGNAL( onWheelEvent( QWheelEvent* ) ), plotDefinition, SLOT( onWheelEvent( QWheelEvent* ) ) ); - connect( this, SIGNAL( destroyed() ), plotDefinition, SLOT( onViewerDestroyed() ) ); + if ( plotDefinition ) + { + connect( this, SIGNAL( plotSelected( bool ) ), plotDefinition, SLOT( onPlotSelected( bool ) ) ); + connect( this, SIGNAL( axisSelected( RiuPlotAxis, bool ) ), plotDefinition, SLOT( onAxisSelected( RiuPlotAxis, bool ) ) ); + connect( this, + SIGNAL( plotItemSelected( std::shared_ptr, bool, int ) ), + plotDefinition, + SLOT( onPlotItemSelected( std::shared_ptr, bool, int ) ) ); + connect( this, SIGNAL( onKeyPressEvent( QKeyEvent* ) ), plotDefinition, SLOT( onKeyPressEvent( QKeyEvent* ) ) ); + connect( this, SIGNAL( onWheelEvent( QWheelEvent* ) ), plotDefinition, SLOT( onWheelEvent( QWheelEvent* ) ) ); + connect( this, SIGNAL( destroyed() ), plotDefinition, SLOT( onViewerDestroyed() ) ); + } ensureAxisIsCreated( RiuPlotAxis::defaultLeft() ); ensureAxisIsCreated( RiuPlotAxis::defaultBottom() ); diff --git a/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionEditor.cpp b/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionEditor.cpp index 94a0fce6ab..44136eeaa6 100644 --- a/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionEditor.cpp +++ b/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionEditor.cpp @@ -232,14 +232,14 @@ void PdmUiTreeSelectionEditor::configureAndUpdateUi( const QString& uiConfigName } else { - if ( options.size() == 0 ) + if ( options.empty() == 0 ) { m_toggleAllCheckBox->setChecked( false ); } else { QModelIndexList indices = allVisibleSourceModelIndices(); - if ( indices.size() > 0 ) + if ( !indices.empty() ) { size_t editableItems = 0u; size_t checkedEditableItems = 0u; @@ -388,7 +388,7 @@ void PdmUiTreeSelectionEditor::customMenuRequested( const QPoint& pos ) } } - if ( onlyHeadersInSelection && selectedIndexes.size() > 0 ) + if ( onlyHeadersInSelection && !selectedIndexes.empty() ) { { QAction* act = new QAction( "Sub Items On", this ); @@ -404,24 +404,41 @@ void PdmUiTreeSelectionEditor::customMenuRequested( const QPoint& pos ) menu.addAction( act ); } } - else if ( selectedIndexes.size() > 0 ) + else if ( !selectedIndexes.empty() ) { { - QAction* act = new QAction( "Set Selected On", this ); + QAction* act = new QAction( "Set Selected Checked", this ); connect( act, SIGNAL( triggered() ), SLOT( slotSetSelectedOn() ) ); menu.addAction( act ); } { - QAction* act = new QAction( "Set Selected Off", this ); + QAction* act = new QAction( "Set Selected Unchecked", this ); connect( act, SIGNAL( triggered() ), SLOT( slotSetSelectedOff() ) ); menu.addAction( act ); } + + menu.addSeparator(); + + if ( selectedIndexes.size() == 1 ) + { + QAction* act = new QAction( "Invert Checked State Of All", this ); + connect( act, SIGNAL( triggered() ), SLOT( slotInvertCheckedStateOfAll() ) ); + + menu.addAction( act ); + } + else + { + QAction* act = new QAction( "Invert Checked States of Selected", this ); + connect( act, SIGNAL( triggered() ), SLOT( slotInvertCheckedStateForSelection() ) ); + + menu.addAction( act ); + } } - if ( menu.actions().size() > 0 ) + if ( !menu.actions().empty() ) { // Qt doc: QAbstractScrollArea and its subclasses that map the context menu event to coordinates of the viewport(). QPoint globalPos = m_treeView->viewport()->mapToGlobal( pos ); @@ -509,6 +526,27 @@ void PdmUiTreeSelectionEditor::slotToggleAll() } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void PdmUiTreeSelectionEditor::slotInvertCheckedStateForSelection() +{ + QItemSelection selectionInProxyModel = m_treeView->selectionModel()->selection(); + QItemSelection selectionInSourceModel = m_proxyModel->mapSelectionToSource( selectionInProxyModel ); + + m_model->invertCheckedStateForItems( selectionInSourceModel.indexes() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void PdmUiTreeSelectionEditor::slotInvertCheckedStateOfAll() +{ + QModelIndexList indices = allVisibleSourceModelIndices(); + + m_model->invertCheckedStateForItems( indices ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionEditor.h b/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionEditor.h index 13b696b3eb..45a42df60f 100644 --- a/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionEditor.h +++ b/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionEditor.h @@ -109,6 +109,8 @@ private slots: void slotSetSubItemsOff(); void slotToggleAll(); + void slotInvertCheckedStateForSelection(); + void slotInvertCheckedStateOfAll(); void slotTextFilterChanged(); diff --git a/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionQModel.cpp b/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionQModel.cpp index a9228d3945..42dbeab081 100644 --- a/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionQModel.cpp +++ b/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionQModel.cpp @@ -137,6 +137,42 @@ void caf::PdmUiTreeSelectionQModel::setCheckedStateForItems( const QModelIndexLi PdmUiCommandSystemProxy::instance()->setUiValueToField( m_uiFieldHandle->uiField(), fieldValueSelection ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void caf::PdmUiTreeSelectionQModel::invertCheckedStateForItems( const QModelIndexList& indices ) +{ + if ( !m_uiFieldHandle || !m_uiFieldHandle->uiField() ) return; + + std::set currentSelectedIndices; + { + QVariant fieldValue = m_uiFieldHandle->uiField()->uiValue(); + QList fieldValueSelection = fieldValue.toList(); + + for ( const auto& v : fieldValueSelection ) + { + currentSelectedIndices.insert( v.toUInt() ); + } + } + + QList fieldValueSelection; + + for ( const auto& mi : indices ) + { + const caf::PdmOptionItemInfo* optionItemInfo = optionItem( mi ); + if ( !optionItemInfo->isReadOnly() ) + { + auto index = static_cast( optionIndex( mi ) ); + if ( currentSelectedIndices.count( index ) == 0 ) + { + fieldValueSelection.push_back( QVariant( index ) ); + } + } + } + + PdmUiCommandSystemProxy::instance()->setUiValueToField( m_uiFieldHandle->uiField(), fieldValueSelection ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionQModel.h b/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionQModel.h index dd8563ea27..f7da22b1c9 100644 --- a/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionQModel.h +++ b/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionQModel.h @@ -62,6 +62,8 @@ public: static int optionItemValueRole(); void setCheckedStateForItems( const QModelIndexList& indices, bool checked ); + void invertCheckedStateForItems( const QModelIndexList& indices ); + void enableSingleSelectionMode( bool enable ); int optionItemCount() const;