#12235 Correlation Plot: Add range filter for x and y values

Add range filter for x and y axis, and compute the correlation based on the values inside the range window. Show the excluded realizations as text.
This commit is contained in:
Magne Sjaastad
2025-03-14 14:03:09 +01:00
parent 7e33fee1ac
commit 42342e0a1a
5 changed files with 299 additions and 66 deletions
@@ -419,6 +419,11 @@ std::set<RimSummaryCase*> RimAbstractCorrelationPlot::filterEnsembleCases( RimSu
setOfCases.insert( summaryCasesVector.begin(), summaryCasesVector.end() );
}
for ( auto excludedCase : m_excludedCases )
{
setOfCases.erase( excludedCase );
}
return setOfCases;
}
@@ -458,7 +463,23 @@ void RimAbstractCorrelationPlot::setCaseFilterDataSource( RimEnsembleCurveSet* e
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::set<RigEnsembleParameter> RimAbstractCorrelationPlot::ensembleParameters()
void RimAbstractCorrelationPlot::setExcludedSummaryCases( const std::vector<RimSummaryCase*>& summaryCases )
{
m_excludedCases = summaryCases;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimSummaryCase*> RimAbstractCorrelationPlot::excludedSummaryCases() const
{
return m_excludedCases;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::set<RigEnsembleParameter> RimAbstractCorrelationPlot::ensembleParameters() const
{
std::set<RigEnsembleParameter> ensembleParms;
@@ -492,7 +513,7 @@ std::set<RigEnsembleParameter> RimAbstractCorrelationPlot::variationSortedEnsemb
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RigEnsembleParameter RimAbstractCorrelationPlot::ensembleParameter( const QString& ensembleParameterName )
RigEnsembleParameter RimAbstractCorrelationPlot::ensembleParameter( const QString& ensembleParameterName ) const
{
std::set<RigEnsembleParameter> ensembleParms = ensembleParameters();
for ( const RigEnsembleParameter& eParam : ensembleParms )
@@ -61,6 +61,9 @@ public:
RimEnsembleCurveSet* caseFilterDataSource() const;
void setCaseFilterDataSource( RimEnsembleCurveSet* ensemble );
void setExcludedSummaryCases( const std::vector<RimSummaryCase*>& summaryCases );
std::vector<RimSummaryCase*> excludedSummaryCases() const;
RiuQwtPlotWidget* viewer();
RiuPlotWidget* plotWidget() override;
void detachAllCurves() override;
@@ -86,9 +89,9 @@ protected:
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
std::set<RifEclipseSummaryAddress> addresses() const;
std::set<RigEnsembleParameter> ensembleParameters();
std::set<RigEnsembleParameter> ensembleParameters() const;
std::set<RigEnsembleParameter> variationSortedEnsembleParameters();
RigEnsembleParameter ensembleParameter( const QString& ensembleParameterName );
RigEnsembleParameter ensembleParameter( const QString& ensembleParameterName ) const;
// RimViewWindow overrides
QWidget* viewWidget() override;
@@ -153,4 +156,6 @@ private:
caf::PdmField<bool> m_useCaseFilter;
caf::PdmPtrField<RimEnsembleCurveSet*> m_curveSetForFiltering;
caf::PdmField<bool> m_editCaseFilter;
std::vector<RimSummaryCase*> m_excludedCases;
};
@@ -15,6 +15,7 @@
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimCorrelationReportPlot.h"
#include "RiaPreferences.h"
@@ -304,6 +305,10 @@ void RimCorrelationReportPlot::onLoadDataAndUpdate()
if ( m_showWindow )
{
auto cases = m_parameterResultCrossPlot->summaryCasesExcludedByFilter();
m_correlationMatrixPlot->setExcludedSummaryCases( cases );
m_correlationPlot->setExcludedSummaryCases( cases );
m_correlationMatrixPlot->setLabelFontSize( m_labelFontSize() );
m_correlationMatrixPlot->setAxisTitleFontSize( m_axisTitleFontSize() );
m_correlationMatrixPlot->setAxisValueFontSize( m_axisValueFontSize() );
@@ -358,6 +363,10 @@ void RimCorrelationReportPlot::defineUiOrdering( QString uiConfigName, caf::PdmU
plotGroup->add( &m_axisTitleFontSize );
plotGroup->add( &m_axisValueFontSize );
m_correlationMatrixPlot->legendConfig()->uiOrdering( "ColorsOnly", *plotGroup );
auto filterGroup = uiOrdering.addNewGroup( "Filter" );
m_parameterResultCrossPlot->appendFilterFields( *filterGroup );
uiOrdering.skipRemainingFields( true );
}
@@ -406,6 +415,9 @@ QList<caf::PdmOptionItemInfo> RimCorrelationReportPlot::calculateValueOptions( c
return options;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCorrelationReportPlot::onDataSelection( const caf::SignalEmitter* emitter,
std::pair<QString, RiaSummaryCurveDefinition> parameterAndCurveDef )
{
@@ -421,4 +433,6 @@ void RimCorrelationReportPlot::onDataSelection( const caf::SignalEmitter*
{
m_viewer->updateSubTitles();
}
updateConnectedEditors();
}
@@ -32,17 +32,22 @@
#include "RiuPlotCurve.h"
#include "RiuQwtPlotCurve.h"
#include "RiuQwtPlotRectAnnotation.h"
#include "RiuQwtPlotWidget.h"
#include "RiuQwtSymbol.h"
#include "cafPdmUiComboBoxEditor.h"
#include "cafPdmUiTextEditor.h"
#include "cafPdmUiValueRangeEditor.h"
#include "qwt_picker_machine.h"
#include "qwt_plot.h"
#include "qwt_plot_curve.h"
#include "qwt_plot_marker.h"
#include "qwt_plot_picker.h"
#include "qwt_text.h"
#include <QColor>
#include <QStringList>
#include <limits>
@@ -61,12 +66,20 @@ RimParameterResultCrossPlot::RimParameterResultCrossPlot()
CAF_PDM_InitField( &m_ensembleParameter, "EnsembleParameter", QString( "" ), "Ensemble Parameter" );
m_ensembleParameter.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_useParameterFilter, "UseParameterFilter", false, "Use Parameter Filter" );
CAF_PDM_InitField( &m_summaryFilterRange, "SummaryFilterRange", std::make_pair( 0.0, 0.0 ), "Summary Value Range", "", "", "" );
CAF_PDM_InitField( &m_parameterFilterRange, "ParameterFilterRange", std::make_pair( 0.0, 0.0 ), "Parameter Value Range", "", "", "" );
CAF_PDM_InitFieldNoDefault( &m_excludedCasesText, "ExcludedCasesText", "Excluded Cases" );
m_excludedCasesText.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() );
m_excludedCasesText.registerGetMethod( this, &RimParameterResultCrossPlot::excludedCasesText );
m_selectMultipleVectors = true;
m_legendFontSize = caf::FontTools::RelativeSize::Small;
m_xRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );
m_yRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );
m_xValueRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );
m_yValueRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );
m_showPlotLegends = false;
}
@@ -86,6 +99,47 @@ RimParameterResultCrossPlot::~RimParameterResultCrossPlot()
void RimParameterResultCrossPlot::setEnsembleParameter( const QString& ensembleParameter )
{
m_ensembleParameter = ensembleParameter;
updateValueRanges();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimSummaryCase*> RimParameterResultCrossPlot::summaryCasesExcludedByFilter() const
{
if ( ensembles().size() != 1 ) return {};
if ( !m_useParameterFilter() ) return {};
std::vector<RimSummaryCase*> cases;
auto plotValues = createCaseData();
for ( const auto& caseData : plotValues )
{
if ( caseData.summaryValue < m_summaryFilterRange().first || caseData.summaryValue > m_summaryFilterRange().second ||
caseData.parameterValue < m_parameterFilterRange().first || caseData.parameterValue > m_parameterFilterRange().second )
{
cases.push_back( caseData.summaryCase );
}
}
return cases;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::appendFilterFields( caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &m_useParameterFilter );
if ( m_useParameterFilter )
{
uiOrdering.add( &m_summaryFilterRange );
uiOrdering.add( &m_parameterFilterRange );
uiOrdering.add( &m_excludedCasesText );
}
uiOrdering.skipRemainingFields();
}
//--------------------------------------------------------------------------------------------------
@@ -122,6 +176,10 @@ void RimParameterResultCrossPlot::defineUiOrdering( QString uiConfigName, caf::P
plotGroup->add( &m_axisValueFontSize );
m_description.uiCapability()->setUiReadOnly( m_useAutoPlotTitle() );
auto filterGroup = uiOrdering.addNewGroup( "Filter" );
appendFilterFields( *filterGroup );
uiOrdering.skipRemainingFields( true );
}
@@ -141,6 +199,37 @@ QList<caf::PdmOptionItemInfo> RimParameterResultCrossPlot::calculateValueOptions
return options;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::defineEditorAttribute( const caf::PdmFieldHandle* field,
QString uiConfigName,
caf::PdmUiEditorAttribute* attribute )
{
if ( field == &m_summaryFilterRange )
{
if ( auto attr = dynamic_cast<caf::PdmUiDoubleSliderEditorAttribute*>( attribute ) )
{
attr->m_decimals = 2;
attr->m_sliderTickCount = 100;
attr->m_minimum = m_yValueRange.first;
attr->m_maximum = m_yValueRange.second;
}
}
else if ( field == &m_parameterFilterRange )
{
if ( auto attr = dynamic_cast<caf::PdmUiDoubleSliderEditorAttribute*>( attribute ) )
{
attr->m_decimals = 2;
attr->m_sliderTickCount = 100;
attr->m_minimum = m_xValueRange.first;
attr->m_maximum = m_xValueRange.second;
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -153,6 +242,32 @@ void RimParameterResultCrossPlot::onLoadDataAndUpdate()
if ( m_plotWidget && m_analyserOfSelectedCurveDefs )
{
createPoints();
if ( m_useParameterFilter )
{
if ( !m_rectAnnotation )
{
m_rectAnnotation = std::make_unique<RiuQwtPlotRectAnnotation>();
}
m_rectAnnotation->setInterval( m_parameterFilterRange().first,
m_parameterFilterRange().second,
m_summaryFilterRange().first,
m_summaryFilterRange().second );
QColor penColor( "orange" );
QPen pen( penColor );
m_rectAnnotation->setPen( pen );
QColor brushColor = penColor;
double transparency = 0.3;
brushColor.setAlphaF( transparency );
QBrush brush( brushColor );
m_rectAnnotation->setBrush( brush );
m_rectAnnotation->attach( m_plotWidget->qwtPlot() );
}
updateAxes();
updatePlotTitle();
m_plotWidget->scheduleReplot();
@@ -169,16 +284,12 @@ void RimParameterResultCrossPlot::updateAxes()
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultLeft(), completeAddressText() );
m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultLeft(), true );
m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultLeft(), axisTitleFontSize(), axisValueFontSize(), false, Qt::AlignCenter );
double yRangeWidth = m_yRange.second - m_yRange.first;
m_plotWidget->setAxisRange( RiuPlotAxis::defaultLeft(), m_yRange.first - yRangeWidth * 0.1, m_yRange.second + yRangeWidth * 0.1 );
m_plotWidget->setAxisRange( RiuPlotAxis::defaultLeft(), m_yValueRange.first, m_yValueRange.second );
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultBottom(), m_ensembleParameter );
m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultBottom(), true );
m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultBottom(), axisTitleFontSize(), axisValueFontSize(), false, Qt::AlignCenter );
double xRangeWidth = m_xRange.second - m_xRange.first;
m_plotWidget->setAxisRange( RiuPlotAxis::defaultBottom(), m_xRange.first - xRangeWidth * 0.1, m_xRange.second + xRangeWidth * 0.1 );
m_plotWidget->setAxisRange( RiuPlotAxis::defaultBottom(), m_xValueRange.first, m_xValueRange.second );
}
//--------------------------------------------------------------------------------------------------
@@ -205,9 +316,6 @@ QStringList caseNamesOfValidEnsembleCases( const RimSummaryEnsemble* ensemble )
void RimParameterResultCrossPlot::createPoints()
{
detachAllCurves();
m_valuesForTextReport.clear();
time_t selectedTimestep = m_timeStep().toSecsSinceEpoch();
caf::ColorTable colorTable = RiaColorTables::categoryPaletteColors();
@@ -216,18 +324,104 @@ void RimParameterResultCrossPlot::createPoints()
bool showEnsembleName = ensembles().size() > 1u;
m_xRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );
m_yRange = std::make_pair( std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() );
int idx = 0;
auto caseData = createCaseData();
for ( const auto& [paramValue, closestValue, summaryCase] : caseData )
{
RiuQwtPlotCurve* plotCurve = new RiuQwtPlotCurve;
plotCurve->setSamplesValues( { paramValue }, { closestValue } );
plotCurve->setStyle( QwtPlotCurve::NoCurve );
RiuQwtSymbol* symbol = new RiuQwtSymbol( RiuPlotCurveSymbol::SYMBOL_ELLIPSE );
symbol->setSize( legendFontSize(), legendFontSize() );
symbol->setColor( colorTable.cycledQColor( idx++ ) );
plotCurve->setSymbol( symbol );
QStringList curveName;
if ( showEnsembleName && summaryCase->ensemble() )
{
curveName += summaryCase->ensemble()->name();
}
curveName += summaryCase->displayCaseName();
plotCurve->setTitle( curveName.join( " - " ) );
plotCurve->attach( m_plotWidget->qwtPlot() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::updateValueRanges()
{
auto parameterMin = std::numeric_limits<double>::infinity();
auto parameterMax = -std::numeric_limits<double>::infinity();
auto summaryMin = std::numeric_limits<double>::infinity();
auto summaryMax = -std::numeric_limits<double>::infinity();
auto caseData = createCaseData();
for ( const auto& [paramValue, summaryValue, summaryCase] : caseData )
{
parameterMin = std::min( parameterMin, paramValue );
parameterMax = std::max( parameterMax, paramValue );
summaryMin = std::min( summaryMin, summaryValue );
summaryMax = std::max( summaryMax, summaryValue );
}
double parameterRange = parameterMax - parameterMin;
double summaryRange = summaryMax - summaryMin;
m_xValueRange = { parameterMin - parameterRange * 0.1, parameterMax + parameterRange * 0.1 };
m_yValueRange = { summaryMin - summaryRange * 0.1, summaryMax + summaryRange * 0.1 };
m_summaryFilterRange = { m_yValueRange.first, m_yValueRange.second };
m_parameterFilterRange = { m_xValueRange.first, m_xValueRange.second };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimParameterResultCrossPlot::excludedCasesText() const
{
auto cases = summaryCasesExcludedByFilter();
QStringList caseNames;
for ( auto summaryCase : cases )
{
caseNames.push_back( summaryCase->displayCaseName() );
}
return caseNames.join( ", " );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimParameterResultCrossPlot::detachAllCurves()
{
if ( m_plotWidget && m_rectAnnotation )
{
m_rectAnnotation->detach();
}
RimAbstractCorrelationPlot::detachAllCurves();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimParameterResultCrossPlot::CaseData> RimParameterResultCrossPlot::createCaseData() const
{
RigEnsembleParameter parameter = ensembleParameter( m_ensembleParameter );
if ( !( parameter.isNumeric() && parameter.isValid() ) ) return {};
std::vector<RimParameterResultCrossPlot::CaseData> caseData;
time_t selectedTimestep = m_timeStep().toSecsSinceEpoch();
int ensembleIdx = 0;
for ( auto ensemble : ensembles() )
{
int addressIdx = 0;
for ( auto address : addresses() )
{
RigEnsembleParameter parameter = ensembleParameter( m_ensembleParameter );
if ( !( parameter.isNumeric() && parameter.isValid() ) ) return;
std::set<RimSummaryCase*> activeCases = filterEnsembleCases( ensemble );
for ( size_t caseIdx = 0u; caseIdx < ensemble->allSummaryCases().size(); ++caseIdx )
@@ -240,9 +434,9 @@ void RimParameterResultCrossPlot::createPoints()
if ( !summaryCase->caseRealizationParameters() ) continue;
double closestValue = std::numeric_limits<double>::infinity();
time_t closestTimeStep = 0;
auto [isOk, values] = reader->values( address );
double summaryValue = std::numeric_limits<double>::infinity();
time_t closestTimeStep = 0;
auto [isOk, summaryValues] = reader->values( address );
if ( isOk )
{
const std::vector<time_t>& timeSteps = reader->timeSteps( address );
@@ -250,50 +444,21 @@ void RimParameterResultCrossPlot::createPoints()
{
if ( timeDiff( timeSteps[i], selectedTimestep ) < timeDiff( selectedTimestep, closestTimeStep ) )
{
closestValue = values[i];
summaryValue = summaryValues[i];
closestTimeStep = timeSteps[i];
}
}
}
if ( closestValue != std::numeric_limits<double>::infinity() )
if ( summaryValue != std::numeric_limits<double>::infinity() )
{
std::vector<double> caseValuesAtTimestep;
std::vector<double> parameterValues;
caseValuesAtTimestep.push_back( closestValue );
double paramValue = parameter.values[caseIdx].toDouble();
parameterValues.push_back( paramValue );
m_valuesForTextReport.push_back( { paramValue, closestValue } );
m_xRange.first = std::min( m_xRange.first, paramValue );
m_xRange.second = std::max( m_xRange.second, paramValue );
m_yRange.first = std::min( m_yRange.first, closestValue );
m_yRange.second = std::max( m_yRange.second, closestValue );
RiuQwtPlotCurve* plotCurve = new RiuQwtPlotCurve;
plotCurve->setSamplesValues( parameterValues, caseValuesAtTimestep );
plotCurve->setStyle( QwtPlotCurve::NoCurve );
RiuQwtSymbol* symbol = new RiuQwtSymbol( RiuPlotCurveSymbol::cycledSymbolStyle( ensembleIdx, addressIdx ), "" );
symbol->setSize( legendFontSize(), legendFontSize() );
symbol->setColor( colorTable.cycledQColor( caseIdx ) );
plotCurve->setSymbol( symbol );
QStringList curveName;
if ( showEnsembleName ) curveName += ensemble->name();
curveName += summaryCase->displayCaseName();
plotCurve->setTitle( curveName.join( " - " ) );
plotCurve->attach( m_plotWidget->qwtPlot() );
caseData.push_back(
{ .parameterValue = parameter.values[caseIdx].toDouble(), .summaryValue = summaryValue, .summaryCase = summaryCase } );
}
}
addressIdx++;
}
ensembleIdx++;
}
return caseData;
}
namespace internal
@@ -370,10 +535,13 @@ QString RimParameterResultCrossPlot::asciiDataForPlotExport() const
{
QString asciiData;
asciiData += "Parameter\tResult\n";
for ( const auto& valuePair : m_valuesForTextReport )
auto data = createCaseData();
asciiData += "Realization\tParameter\tResult\n";
for ( const auto& [parameterValue, summaryValue, summaryCase] : data )
{
asciiData += QString( "%1\t%2\n" ).arg( valuePair.first ).arg( valuePair.second );
auto caseName = summaryCase->displayCaseName();
asciiData += QString( "%1\t%2\t%3\n" ).arg( caseName ).arg( parameterValue ).arg( summaryValue );
}
return asciiData;
@@ -20,6 +20,8 @@
#include "RimAbstractCorrelationPlot.h"
class RiuQwtPlotRectAnnotation;
//==================================================================================================
///
///
@@ -33,10 +35,16 @@ public:
~RimParameterResultCrossPlot() override;
void setEnsembleParameter( const QString& ensembleParameter );
std::vector<RimSummaryCase*> summaryCasesExcludedByFilter() const;
void appendFilterFields( caf::PdmUiOrdering& uiOrdering );
void detachAllCurves() override;
private:
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override;
RiuPlotWidget* doCreatePlotViewWidget( QWidget* mainWindowParent = nullptr ) override;
@@ -45,12 +53,29 @@ private:
QString asciiDataForPlotExport() const override;
void updatePlotTitle() override;
void createPoints();
void updateValueRanges();
QString excludedCasesText() const;
struct CaseData
{
double parameterValue;
double summaryValue;
RimSummaryCase* summaryCase;
};
std::vector<CaseData> createCaseData() const;
private:
caf::PdmField<QString> m_ensembleParameter;
std::pair<double, double> m_xRange;
std::pair<double, double> m_yRange;
caf::PdmField<bool> m_useParameterFilter;
caf::PdmField<std::pair<double, double>> m_summaryFilterRange;
caf::PdmField<std::pair<double, double>> m_parameterFilterRange;
caf::PdmProxyValueField<QString> m_excludedCasesText;
std::vector<std::pair<double, double>> m_valuesForTextReport;
std::pair<double, double> m_xValueRange;
std::pair<double, double> m_yValueRange;
std::unique_ptr<RiuQwtPlotRectAnnotation> m_rectAnnotation;
};