From c728979bc13705ed3c5f026a85a5e9576bd47dbb Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Fri, 7 Mar 2025 11:05:39 +0100 Subject: [PATCH] #12203 Improve selection and highlight of curves in summary plot Skip distance check when clicking in the plot, always select closest curve When pressing CTRL, toggle highlighted curves Allow multi select of realizations in Project Tree Avoid curve highlight operation during zoom Make sure wheel zoom works for a single plot --- .../ProjectDataModel/RimMultiPlot.h | 2 +- .../ProjectDataModel/RimPlot.cpp | 8 ++ ApplicationLibCode/ProjectDataModel/RimPlot.h | 4 + .../Summary/RimSummaryEnsembleTools.cpp | 73 +++++++++------ .../Summary/RimSummaryEnsembleTools.h | 9 +- .../Summary/RimSummaryMultiPlot.cpp | 13 ++- .../Summary/RimSummaryMultiPlot.h | 2 + .../Summary/RimSummaryPlot.cpp | 15 ++++ .../ProjectDataModel/Summary/RimSummaryPlot.h | 5 +- .../UserInterface/RiuPlotMainWindow.cpp | 16 ++-- .../UserInterface/RiuQwtPlotWidget.cpp | 90 ++++++++++++++++--- .../UserInterface/RiuQwtPlotWidget.h | 4 +- 12 files changed, 186 insertions(+), 55 deletions(-) diff --git a/ApplicationLibCode/ProjectDataModel/RimMultiPlot.h b/ApplicationLibCode/ProjectDataModel/RimMultiPlot.h index 68868a6df9..9215f2abc4 100644 --- a/ApplicationLibCode/ProjectDataModel/RimMultiPlot.h +++ b/ApplicationLibCode/ProjectDataModel/RimMultiPlot.h @@ -137,7 +137,7 @@ protected: void updateZoom(); void recreatePlotWidgets(); - void onPlotAdditionOrRemoval(); + virtual void onPlotAdditionOrRemoval(); bool isMouseCursorInsidePlot(); diff --git a/ApplicationLibCode/ProjectDataModel/RimPlot.cpp b/ApplicationLibCode/ProjectDataModel/RimPlot.cpp index b65ab2ea2e..4759dde0f2 100644 --- a/ApplicationLibCode/ProjectDataModel/RimPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimPlot.cpp @@ -300,6 +300,14 @@ bool RimPlot::isCurveHighlightSupported() const return false; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::any RimPlot::valueForKey( std::string key ) const +{ + return {}; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/RimPlot.h b/ApplicationLibCode/ProjectDataModel/RimPlot.h index 250d06ec6b..c0a80e1560 100644 --- a/ApplicationLibCode/ProjectDataModel/RimPlot.h +++ b/ApplicationLibCode/ProjectDataModel/RimPlot.h @@ -32,6 +32,8 @@ #include #include +#include + class QPaintDevice; class QWheelEvent; class RiuPlotWidget; @@ -104,6 +106,8 @@ public: virtual std::vector visibleCurvesForLegend(); virtual bool isCurveHighlightSupported() const; + virtual std::any valueForKey( std::string key ) const; + protected: virtual RiuPlotWidget* doCreatePlotViewWidget( QWidget* parent ) = 0; diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsembleTools.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsembleTools.cpp index da224ae1cc..8ed5ea96ac 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsembleTools.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsembleTools.cpp @@ -349,36 +349,54 @@ bool RimSummaryEnsembleTools::isEnsembleCurve( RimPlotCurve* sourceCurve ) //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RimSummaryEnsembleTools::highlightCurvesForSameRealizationFromCurve( RimPlotCurve* sourceCurve ) +std::vector RimSummaryEnsembleTools::summaryCasesFromCurves( const std::vector& sourceCurves ) { - auto sourceSummaryCurve = dynamic_cast( sourceCurve ); - if ( !sourceSummaryCurve ) return; + std::vector sourceCases; - auto ensembleCurveSet = sourceSummaryCurve->firstAncestorOfType(); - if ( !ensembleCurveSet ) return; - - auto sourceCase = sourceSummaryCurve->summaryCaseY(); - if ( !sourceCase ) return; - - // Select the realization object in Data Sources Tree view - if ( auto mainWindow = RiuPlotMainWindow::instance() ) + for ( auto sourceCurve : sourceCurves ) { - if ( auto treeView = mainWindow->getTreeViewWithItem( sourceCase ) ) - { - treeView->selectAsCurrentItem( sourceCase ); - } + auto sourceSummaryCurve = dynamic_cast( sourceCurve ); + if ( !sourceSummaryCurve ) continue; + + auto ensembleCurveSet = sourceSummaryCurve->firstAncestorOfType(); + if ( !ensembleCurveSet ) continue; + + auto sourceCase = sourceSummaryCurve->summaryCaseY(); + if ( !sourceCase ) continue; + + sourceCases.push_back( sourceCase ); } - highlightCurvesForSameRealization( sourceCase ); + return sourceCases; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RimSummaryEnsembleTools::highlightCurvesForSameRealization( RimSummaryCase* sourceCase ) +void RimSummaryEnsembleTools::selectSummaryCasesInProjectTree( const std::vector& sourceCases ) { - if ( !sourceCase ) return; + if ( sourceCases.empty() ) return; + if ( auto mainWindow = RiuPlotMainWindow::instance() ) + { + if ( auto treeView = mainWindow->getTreeViewWithItem( sourceCases.front() ) ) + { + std::vector sourceItems; + for ( auto sourceCase : sourceCases ) + { + sourceItems.push_back( sourceCase ); + } + + treeView->selectItems( sourceItems ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryEnsembleTools::highlightCurvesForSummaryCases( const std::vector& sourceCases ) +{ auto summaryPlotColl = RiaSummaryTools::summaryMultiPlotCollection(); for ( auto multiPlot : summaryPlotColl->multiPlots() ) @@ -390,25 +408,30 @@ void RimSummaryEnsembleTools::highlightCurvesForSameRealization( RimSummaryCase* auto summaryCurves = plot->summaryAndEnsembleCurves(); - std::vector curvesForSameRealization; + std::vector curvesForSameRealization; for ( auto curve : summaryCurves ) { - if ( sourceCase == curve->summaryCaseY() ) + auto summaryCaseY = curve->summaryCaseY(); + if ( std::find( sourceCases.begin(), sourceCases.end(), summaryCaseY ) != sourceCases.end() ) { curvesForSameRealization.push_back( curve ); } } + bool updateCurveOrder = false; + plotWidget->resetPlotItemHighlighting( updateCurveOrder ); + if ( !curvesForSameRealization.empty() ) { - bool updateCurveOrder = false; - plotWidget->resetPlotItemHighlighting( updateCurveOrder ); + std::sort( curvesForSameRealization.begin(), curvesForSameRealization.end() ); + curvesForSameRealization.erase( std::unique( curvesForSameRealization.begin(), curvesForSameRealization.end() ), + curvesForSameRealization.end() ); - std::set realizationCurvesSet( curvesForSameRealization.begin(), curvesForSameRealization.end() ); - plotWidget->highlightCurvesUpdateOrder( realizationCurvesSet ); - plotWidget->replot(); + plotWidget->highlightCurvesUpdateOrder( curvesForSameRealization ); } + + plotWidget->replot(); } } } diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsembleTools.h b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsembleTools.h index 442c5c65f9..9e198a92ee 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsembleTools.h +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsembleTools.h @@ -38,9 +38,10 @@ std::vector createVariationSortedEnsembleParameters( const size_t calculateEnsembleParametersIntersectionHash( const std::vector& summaryCases ); -bool isEnsembleCurve( RimPlotCurve* sourceCurve ); -void highlightCurvesForSameRealizationFromCurve( RimPlotCurve* sourceCurve ); -void highlightCurvesForSameRealization( RimSummaryCase* sourceCase ); -void resetHighlightAllPlots(); +bool isEnsembleCurve( RimPlotCurve* sourceCurve ); +std::vector summaryCasesFromCurves( const std::vector& sourceCurves ); +void selectSummaryCasesInProjectTree( const std::vector& sourceCases ); +void highlightCurvesForSummaryCases( const std::vector& sourceCases ); +void resetHighlightAllPlots(); } // namespace RimSummaryEnsembleTools diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryMultiPlot.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryMultiPlot.cpp index 3c771b0bf6..12ed404fb0 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryMultiPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryMultiPlot.cpp @@ -223,9 +223,6 @@ void RimSummaryMultiPlot::insertPlot( RimPlot* plot, size_t index ) RimMultiPlot::insertPlot( plot, index ); } - - if ( summaryPlots().size() == 1 ) m_disableWheelZoom = false; - if ( summaryPlots().size() == 2 ) m_disableWheelZoom = true; } //-------------------------------------------------------------------------------------------------- @@ -1551,6 +1548,16 @@ QWidget* RimSummaryMultiPlot::createViewWidget( QWidget* mainWindowParent ) return m_viewer; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryMultiPlot::onPlotAdditionOrRemoval() +{ + m_disableWheelZoom = ( summaryPlots().size() > 1 ); + + RimMultiPlot::onPlotAdditionOrRemoval(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryMultiPlot.h b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryMultiPlot.h index 6eaff4c197..57f0001c2e 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryMultiPlot.h +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryMultiPlot.h @@ -125,6 +125,8 @@ protected: QWidget* createViewWidget( QWidget* mainWindowParent ) override; + void onPlotAdditionOrRemoval() override; + private: void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override; diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.cpp index d3dbec8527..331a1fa8ab 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.cpp @@ -1121,6 +1121,19 @@ void RimSummaryPlot::enableCurvePointTracking( bool enable ) m_summaryPlot->enableCurvePointTracking( enable ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::any RimSummaryPlot::valueForKey( std::string key ) const +{ + if ( key == "TimeStampZoomOperation" ) + { + return m_lastZoomTime; + } + + return {}; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -2618,6 +2631,8 @@ void RimSummaryPlot::onPlotZoomed() timeAxisSettingsChanged( nullptr ); axisChanged.send( this ); + + m_lastZoomTime = std::chrono::steady_clock::now(); } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.h b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.h index bf4c34f59d..3488e5cdf2 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.h +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryPlot.h @@ -210,7 +210,8 @@ public: RimSummaryPlotSourceStepping* sourceStepper(); void scheduleReplotIfVisible(); - void enableCurvePointTracking( bool enable ); + void enableCurvePointTracking( bool enable ); + std::any valueForKey( std::string key ) const override; public: // RimViewWindow overrides @@ -351,4 +352,6 @@ private: bool m_isValid; RiuPlotWidget::Legend m_legendPosition; + + std::chrono::steady_clock::time_point m_lastZoomTime; }; diff --git a/ApplicationLibCode/UserInterface/RiuPlotMainWindow.cpp b/ApplicationLibCode/UserInterface/RiuPlotMainWindow.cpp index c92417e830..8486c36391 100644 --- a/ApplicationLibCode/UserInterface/RiuPlotMainWindow.cpp +++ b/ApplicationLibCode/UserInterface/RiuPlotMainWindow.cpp @@ -933,6 +933,17 @@ void RiuPlotMainWindow::selectedObjectsChanged( caf::PdmUiTreeView* projectTree, propertyView->showProperties( firstSelectedObject ); + std::vector summaryCases; + for ( auto uiItem : uiItems ) + { + if ( auto summaryCase = dynamic_cast( uiItem ) ) + { + summaryCases.push_back( summaryCase ); + } + } + + RimSummaryEnsembleTools::highlightCurvesForSummaryCases( summaryCases ); + if ( uiItems.size() == 1 && m_allowActiveViewChangeFromSelection ) { // Find the reservoir view or the Plot that the selected item is within @@ -944,11 +955,6 @@ void RiuPlotMainWindow::selectedObjectsChanged( caf::PdmUiTreeView* projectTree, if ( !firstSelectedObject ) return; - if ( auto summaryCase = dynamic_cast( firstSelectedObject ) ) - { - RimSummaryEnsembleTools::highlightCurvesForSameRealization( summaryCase ); - } - RimViewWindow* selectedWindow = dynamic_cast( firstSelectedObject ); if ( !selectedWindow ) { diff --git a/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.cpp b/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.cpp index 45b68f3c53..c9b4f0b23e 100644 --- a/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.cpp +++ b/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.cpp @@ -611,7 +611,38 @@ bool RiuQwtPlotWidget::eventFilter( QObject* watched, QEvent* event ) { endZoomOperations(); - selectClosestPlotItem( mouseEvent->pos(), toggleItemInSelection ); + auto hasRecentlyBeenZoomed = [this]() -> bool + { + if ( !m_plotDefinition ) return false; + auto lastZoom = m_plotDefinition->valueForKey( "TimeStampZoomOperation" ); + if ( lastZoom.has_value() ) + { + try + { + auto retrieved_time = std::any_cast( lastZoom ); + auto t0 = std::chrono::steady_clock::now(); + + auto timeDiff = std::chrono::duration_cast( t0 - retrieved_time ).count(); + if ( timeDiff < 10 ) + { + return true; + } + } + catch ( ... ) + { + } + } + return false; + }; + + if ( !hasRecentlyBeenZoomed() ) + { + // Avoid selecting the curve if a zoom operation has been performed recently + // It is confusing to select a curve when the user is trying to zoom + + selectClosestPlotItem( mouseEvent->pos(), toggleItemInSelection ); + } + m_clickPosition = QPoint(); return true; } @@ -931,22 +962,47 @@ void RiuQwtPlotWidget::selectClosestPlotItem( const QPoint& pos, bool toggleItem findClosestPlotItem( pos, &closestItem, &closestCurvePoint, &distanceFromClick ); RiuPlotMainWindowTools::showPlotMainWindow(); - if ( closestItem && distanceFromClick < 20 ) + if ( closestItem ) { + RimPlotCurve* clickedCurve = nullptr; + if ( auto curve = dynamic_cast( closestItem ) ) + { + clickedCurve = curve->ownerRimCurve(); + } + + std::vector curvesToSelect; + + bool wasToggledOff = false; + if ( toggleItemInSelection ) + { + for ( auto highlightedCurve : m_hightlightedCurves ) + { + if ( toggleItemInSelection && ( highlightedCurve == clickedCurve ) ) + { + wasToggledOff = true; + continue; + } + + curvesToSelect.push_back( highlightedCurve ); + } + } + + if ( !wasToggledOff && clickedCurve ) curvesToSelect.push_back( clickedCurve ); + bool updateCurveOrder = false; resetPlotItemHighlighting( updateCurveOrder ); - auto curve = dynamic_cast( closestItem ); - if ( curve && curve->ownerRimCurve() ) + if ( !curvesToSelect.empty() ) { - const auto rimCurve = curve->ownerRimCurve(); - if ( RimSummaryEnsembleTools::isEnsembleCurve( rimCurve ) ) + if ( RimSummaryEnsembleTools::isEnsembleCurve( curvesToSelect.front() ) ) { - RimSummaryEnsembleTools::highlightCurvesForSameRealizationFromCurve( rimCurve ); + auto summaryCases = RimSummaryEnsembleTools::summaryCasesFromCurves( curvesToSelect ); + RimSummaryEnsembleTools::selectSummaryCasesInProjectTree( summaryCases ); + RimSummaryEnsembleTools::highlightCurvesForSummaryCases( summaryCases ); } else { - highlightCurvesUpdateOrder( { rimCurve } ); + highlightCurvesUpdateOrder( curvesToSelect ); } } else @@ -989,7 +1045,7 @@ void RiuQwtPlotWidget::replot() //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RiuQwtPlotWidget::highlightCurvesUpdateOrder( const std::set& curves ) +void RiuQwtPlotWidget::highlightCurvesUpdateOrder( const std::vector& curves ) { highlightPlotCurves( curves ); @@ -999,7 +1055,7 @@ void RiuQwtPlotWidget::highlightCurvesUpdateOrder( const std::set //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RiuQwtPlotWidget::highlightPlotCurves( const std::set& curves ) +void RiuQwtPlotWidget::highlightPlotCurves( const std::vector& curves ) { if ( !m_plotDefinition || !m_plotDefinition->isCurveHighlightSupported() ) { @@ -1014,7 +1070,8 @@ void RiuQwtPlotWidget::highlightPlotCurves( const std::set& curve auto currentRimPlotCurve = riuPlotCurve->ownerRimCurve(); - // Do not modify curve objects with no associated Rim object, as the Rim object is used to restore color after highlight manipulation + // Do not modify curve objects with no associated Rim object, as the Rim object is used to restore color after highlight + // manipulation if ( !currentRimPlotCurve ) continue; auto* plotCurve = dynamic_cast( plotItem ); @@ -1036,7 +1093,9 @@ void RiuQwtPlotWidget::highlightPlotCurves( const std::set& curve } double zValue = plotCurve->z(); - if ( curves.count( currentRimPlotCurve ) > 0 ) + + auto it = std::find( curves.begin(), curves.end(), currentRimPlotCurve ); + if ( it != curves.end() ) { auto highlightColor = curveColor; @@ -1204,7 +1263,7 @@ void RiuQwtPlotWidget::resetPlotAxisHighlighting() //-------------------------------------------------------------------------------------------------- void RiuQwtPlotWidget::highlightPlotItemsForQwtAxis( QwtAxisId axisId ) { - std::set curves; + std::vector curves; auto plotItemList = m_plot->itemList(); for ( QwtPlotItem* plotItem : plotItemList ) @@ -1218,12 +1277,15 @@ void RiuQwtPlotWidget::highlightPlotItemsForQwtAxis( QwtAxisId axisId ) { if ( auto curve = qwtPlotCurve->ownerRimCurve() ) { - curves.insert( curve ); + curves.push_back( curve ); } } } } + std::sort( curves.begin(), curves.end() ); + curves.erase( std::unique( curves.begin(), curves.end() ), curves.end() ); + highlightCurvesUpdateOrder( curves ); } diff --git a/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.h b/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.h index ddbb7cc57f..5da9d6bf54 100644 --- a/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.h +++ b/ApplicationLibCode/UserInterface/RiuQwtPlotWidget.h @@ -165,7 +165,7 @@ public: QwtAxisId toQwtPlotAxis( RiuPlotAxis axis ) const; void highlightPlotItem( const QwtPlotItem* plotItem ); - void highlightCurvesUpdateOrder( const std::set& curves ); + void highlightCurvesUpdateOrder( const std::vector& curves ); void resetPlotItemHighlighting( bool doUpdateCurveOrder = true ); std::vector highlightedCurves() const; @@ -210,7 +210,7 @@ private: void highlightPlotAxes( QwtAxisId axisIdX, QwtAxisId axisIdY ); void highlightPlotItemsForQwtAxis( QwtAxisId axisId ); - void highlightPlotCurves( const std::set& curves ); + void highlightPlotCurves( const std::vector& curves ); void highlightPlotShapeItems( const std::set& closestItems ); void resetPlotCurveHighlighting(); void resetPlotShapeItemHighlighting();