///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2020 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 "RimCorrelationMatrixPlot.h" #include "RiaColorTools.h" #include "RiaPreferences.h" #include "RiaQDateTimeTools.h" #include "RiaStatisticsTools.h" #include "RiuPlotMainWindowTools.h" #include "RiuQwtLinearScaleEngine.h" #include "RiuQwtPlotTools.h" #include "RiuQwtPlotWidget.h" #include "RifSummaryReaderInterface.h" #include "RimDerivedSummaryCase.h" #include "RimEnsembleCurveSet.h" #include "RimPlotAxisProperties.h" #include "RimPlotAxisPropertiesInterface.h" #include "RimPlotDataFilterCollection.h" #include "RimRegularLegendConfig.h" #include "RimSummaryAddress.h" #include "RimSummaryCase.h" #include "RimSummaryCaseCollection.h" #include "RimSummaryPlotAxisFormatter.h" #include "cafPdmUiComboBoxEditor.h" #include "cafPdmUiTreeOrdering.h" #include "cafPdmUiTreeSelectionEditor.h" #include "cvfScalarMapper.h" #include "qwt_plot_marker.h" #include "qwt_plot_shapeitem.h" #include "qwt_scale_draw.h" #include "qwt_text.h" #include #include #include #include namespace caf { template <> void caf::AppEnum::setUp() { addItem( RimCorrelationMatrixPlot::Sorting::NO_SORTING, "NO_SORTING", "No Sorting" ); addItem( RimCorrelationMatrixPlot::Sorting::ROWS, "SORT_ROWS", "Sort Rows" ); addItem( RimCorrelationMatrixPlot::Sorting::COLUMNS, "SORT_COLUMNS", "Sort Columns" ); addItem( RimCorrelationMatrixPlot::Sorting::BOTH, "SORT_BOTH", "Sort Both Rows/Columns" ); setDefault( RimCorrelationMatrixPlot::Sorting::BOTH ); } } // namespace caf CAF_PDM_SOURCE_INIT( RimCorrelationMatrixPlot, "CorrelationMatrixPlot" ); class CorrelationMatrixShapeItem : public QwtPlotShapeItem { public: CorrelationMatrixShapeItem( const QString& title = QString() ) : QwtPlotShapeItem( title ) { } public: QString parameter; RiaSummaryCurveDefinition curveDef; }; class TextScaleDraw : public QwtScaleDraw { public: TextScaleDraw( const std::map& tickLabels ) : m_tickLabels( tickLabels ) { } QwtText label( double value ) const override { size_t intValue = static_cast( value + 0.25 ); auto it = m_tickLabels.find( intValue ); return it != m_tickLabels.end() ? it->second : ""; } private: std::map m_tickLabels; }; template class CorrelationMatrixRowOrColumn { public: CorrelationMatrixRowOrColumn( const KeyType& key, const std::vector& correlations, const std::vector& values ) : m_key( key ) , m_correlations( correlations ) , m_values( values ) , m_correlationSum( 0.0 ) , m_correlationAbsSum( 0.0 ) { bool anyValid = false; for ( auto value : correlations ) { if ( RiaCurveDataTools::isValidValue( value, false ) ) { m_correlationSum += value; m_correlationAbsSum += std::abs( value ); anyValid = true; } } if ( !anyValid ) { m_correlationSum = std::numeric_limits::infinity(); m_correlationAbsSum = std::numeric_limits::infinity(); } } KeyType m_key; std::vector m_correlations; std::vector m_values; double m_correlationSum; double m_correlationAbsSum; }; using CorrelationMatrixColumn = CorrelationMatrixRowOrColumn; using CorrelationMatrixRow = CorrelationMatrixRowOrColumn; //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimCorrelationMatrixPlot::RimCorrelationMatrixPlot() : RimAbstractCorrelationPlot() , matrixCellSelected( this ) { CAF_PDM_InitObject( "Correlation Plot", ":/CorrelationMatrixPlot16x16.png", "", "" ); CAF_PDM_InitField( &m_showAbsoluteValues, "CorrelationAbsValues", false, "Show Absolute Values", "", "", "" ); CAF_PDM_InitFieldNoDefault( &m_sortByValues, "CorrelationSorting", "Sort Matrix by Values", "", "", "" ); CAF_PDM_InitField( &m_sortByAbsoluteValues, "CorrelationAbsSorting", true, "Sort by Absolute Values", "", "", "" ); CAF_PDM_InitField( &m_excludeParametersWithoutVariation, "ExcludeParamsWithoutVariation", true, "Exclude Parameters Without Variation", "", "", "" ); CAF_PDM_InitField( &m_showOnlyTopNCorrelations, "ShowOnlyTopNCorrelations", true, "Show Only Top Correlations", "", "", "" ); CAF_PDM_InitField( &m_topNFilterCount, "TopNFilterCount", 20, "Number rows/columns", "", "", "" ); CAF_PDM_InitFieldNoDefault( &m_legendConfig, "LegendConfig", "", "", "", "" ); CAF_PDM_InitFieldNoDefault( &m_selectedParametersList, "SelectedParameters", "Select Parameters", "", "", "" ); m_selectedParametersList.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::TOP ); m_selectedParametersList.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); m_legendConfig = new RimRegularLegendConfig(); m_legendConfig->setAutomaticRanges( -1.0, 1.0, -1.0, 1.0 ); m_legendConfig->setColorLegend( RimRegularLegendConfig::mapToColorLegend( RimRegularLegendConfig::ColorRangesType::RED_WHITE_BLUE ) ); setLegendsVisible( false ); this->uiCapability()->setUiTreeChildrenHidden( true ); m_selectMultipleVectors = true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimCorrelationMatrixPlot::~RimCorrelationMatrixPlot() { if ( isMdiWindow() ) removeMdiWindowFromMdiArea(); cleanupBeforeClose(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimCorrelationMatrixPlot::showAbsoluteValues() const { return m_showAbsoluteValues; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimCorrelationMatrixPlot::sortByAbsoluteValues() const { return m_sortByAbsoluteValues; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimRegularLegendConfig* RimCorrelationMatrixPlot::legendConfig() { return m_legendConfig(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::selectAllParameters() { m_selectedParametersList.v().clear(); std::set params = variationSortedEnsembleParameters(); for ( auto param : params ) { if ( !m_excludeParametersWithoutVariation() || param.variationBin > EnsembleParameter::NO_VARIATION ) { m_selectedParametersList.v().push_back( param.name ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimCorrelationMatrixPlot::showTopNCorrelations() const { return m_showOnlyTopNCorrelations(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RimCorrelationMatrixPlot::topNFilterCount() const { return m_topNFilterCount(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { RimAbstractCorrelationPlot::fieldChangedByUi( changedField, oldValue, newValue ); bool sendSelectedSignal = false; if ( changedField == &m_showAbsoluteValues || changedField == &m_sortByValues || changedField == &m_sortByAbsoluteValues || changedField == &m_showOnlyTopNCorrelations || changedField == &m_topNFilterCount || changedField == &m_excludeParametersWithoutVariation || changedField == &m_selectedParametersList ) { if ( changedField == &m_excludeParametersWithoutVariation ) { selectAllParameters(); } updateLegend(); loadDataAndUpdate(); updateConnectedEditors(); sendSelectedSignal = true; } if ( changedField == &m_pushButtonSelectSummaryAddress ) { sendSelectedSignal = true; } if ( sendSelectedSignal ) { auto curves = curveDefinitions(); auto parameters = m_selectedParametersList(); if ( !curves.empty() && !parameters.empty() ) { auto firstCurve = curves.front(); auto firstParameter = parameters.front(); matrixCellSelected.send( { firstParameter, firstCurve } ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { caf::PdmUiGroup* correlationGroup = uiOrdering.addNewGroup( "Correlation Settings" ); correlationGroup->add( &m_excludeParametersWithoutVariation ); correlationGroup->add( &m_selectedParametersList ); correlationGroup->add( &m_showAbsoluteValues ); correlationGroup->add( &m_sortByValues ); if ( !m_showAbsoluteValues() && m_sortByValues() != Sorting::NO_SORTING ) { correlationGroup->add( &m_sortByAbsoluteValues ); } if ( m_sortByValues() != Sorting::NO_SORTING ) { correlationGroup->add( &m_showOnlyTopNCorrelations ); if ( m_showOnlyTopNCorrelations() ) { correlationGroup->add( &m_topNFilterCount ); } } caf::PdmUiGroup* curveDataGroup = uiOrdering.addNewGroup( "Summary Vector" ); m_selectedVarsUiField = selectedQuantitiesText(); curveDataGroup->add( &m_selectedVarsUiField ); curveDataGroup->add( &m_pushButtonSelectSummaryAddress, { false, 1, 0 } ); curveDataGroup->add( &m_timeStepFilter ); curveDataGroup->add( &m_timeStep ); if ( uiConfigName != "report" ) { caf::PdmUiGroup* plotGroup = uiOrdering.addNewGroup( "Plot Settings" ); plotGroup->add( &m_showPlotTitle ); plotGroup->add( &m_useAutoPlotTitle ); plotGroup->add( &m_description ); m_description.uiCapability()->setUiReadOnly( m_useAutoPlotTitle() ); RimPlot::defineUiOrdering( uiConfigName, *plotGroup ); plotGroup->add( &m_titleFontSize ); plotGroup->add( &m_labelFontSize ); plotGroup->add( &m_axisTitleFontSize ); plotGroup->add( &m_axisValueFontSize ); m_legendConfig->uiOrdering( "ColorsOnly", *plotGroup ); } uiOrdering.skipRemainingFields( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimCorrelationMatrixPlot::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions, bool* useOptionsOnly ) { QList options = RimAbstractCorrelationPlot::calculateValueOptions( fieldNeedingOptions, useOptionsOnly ); if ( fieldNeedingOptions == &m_selectedParametersList ) { std::set params = variationSortedEnsembleParameters(); for ( auto param : params ) { if ( !m_excludeParametersWithoutVariation() || param.variationBin > EnsembleParameter::NO_VARIATION ) { options.push_back( caf::PdmOptionItemInfo( param.uiName(), param.name ) ); } } } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::onLoadDataAndUpdate() { updateMdiWindowVisibility(); m_selectedVarsUiField = selectedQuantitiesText(); if ( m_plotWidget ) { m_plotWidget->detachItems( QwtPlotItem::Rtti_PlotBarChart ); m_plotWidget->detachItems( QwtPlotItem::Rtti_PlotScale ); m_plotWidget->detachItems( QwtPlotItem::Rtti_PlotItem ); updateLegend(); createMatrix(); m_plotWidget->insertLegend( nullptr ); this->updateAxes(); this->updatePlotTitle(); m_plotWidget->scheduleReplot(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::childFieldChangedByUi( const caf::PdmFieldHandle* changedChildField ) { this->loadDataAndUpdate(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::updateAxes() { if ( !m_plotWidget ) return; m_plotWidget->setAxisScaleDraw( QwtPlot::yLeft, new TextScaleDraw( m_resultLabels ) ); m_plotWidget->setAxisScaleEngine( QwtPlot::yLeft, new RiuQwtLinearScaleEngine ); m_plotWidget->setAxisTitleText( QwtPlot::yLeft, "Result Vector" ); m_plotWidget->setAxisTitleEnabled( QwtPlot::yLeft, true ); m_plotWidget->setAxisFontsAndAlignment( QwtPlot::yLeft, axisTitleFontSize(), axisValueFontSize(), false, Qt::AlignCenter ); m_plotWidget->setAxisLabelsAndTicksEnabled( QwtPlot::yLeft, true, false ); m_plotWidget->setAxisRange( QwtPlot::yLeft, 0.0, (double)m_resultLabels.size() + 1 ); m_plotWidget->setMajorAndMinorTickIntervalsAndRange( QwtPlot::yLeft, 1.0, 0.0, 0.5, (double)m_resultLabels.size() - 0.5, 0.0, m_resultLabels.size() ); auto scaleDraw = new TextScaleDraw( m_paramLabels ); scaleDraw->setLabelRotation( 30.0 ); m_plotWidget->setAxisScaleDraw( QwtPlot::xBottom, scaleDraw ); m_plotWidget->setAxisScaleEngine( QwtPlot::xBottom, new RiuQwtLinearScaleEngine ); m_plotWidget->setAxisTitleText( QwtPlot::xBottom, "Ensemble Parameter" ); m_plotWidget->setAxisTitleEnabled( QwtPlot::xBottom, true ); m_plotWidget->setAxisFontsAndAlignment( QwtPlot::xBottom, axisTitleFontSize(), axisValueFontSize(), false, Qt::AlignCenter | Qt::AlignTop ); m_plotWidget->setAxisLabelsAndTicksEnabled( QwtPlot::xBottom, true, false ); m_plotWidget->setAxisRange( QwtPlot::xBottom, 0.0, (double)m_paramLabels.size() + 1 ); m_plotWidget->setMajorAndMinorTickIntervalsAndRange( QwtPlot::xBottom, 1.0, 0.0, 0.5, (double)m_paramLabels.size() - 0.5, 0.0, (double)m_paramLabels.size() ); m_plotWidget->setAxisLabelAlignment( QwtPlot::xBottom, Qt::AlignRight ); } template void eraseInvalidEntries( std::vector>& matrix ) { matrix.erase( std::remove_if( matrix.begin(), matrix.end(), [=]( const CorrelationMatrixRowOrColumn& entry ) { bool isValid = RiaCurveDataTools::isValidValue( entry.m_correlationSum, false ); return !isValid; } ), matrix.end() ); } template void sortEntries( std::vector>& matrix, bool sortByAbsoluteValues ) { std::sort( matrix.begin(), matrix.end(), [&sortByAbsoluteValues]( const CorrelationMatrixRowOrColumn& lhs, const CorrelationMatrixRowOrColumn& rhs ) -> bool { if ( sortByAbsoluteValues ) return lhs.m_correlationAbsSum > rhs.m_correlationAbsSum; else return lhs.m_correlationSum > rhs.m_correlationSum; } ); } template std::vector> transpose( const std::vector>& matrix ) { std::vector> transposedMatrix; for ( size_t rowIdx = 0u; !matrix.empty() && rowIdx < matrix[0].m_correlations.size(); ++rowIdx ) { ValueType key = matrix[0].m_values[rowIdx]; std::vector correlations; std::vector values; for ( size_t colIdx = 0u; colIdx < matrix.size(); ++colIdx ) { correlations.push_back( matrix[colIdx].m_correlations[rowIdx] ); values.push_back( matrix[colIdx].m_key ); } transposedMatrix.push_back( CorrelationMatrixRowOrColumn( key, correlations, values ) ); } return transposedMatrix; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::createMatrix() { time_t selectedTimestep = m_timeStep().toSecsSinceEpoch(); m_paramLabels.clear(); m_resultLabels.clear(); auto curveDefs = curveDefinitions(); if ( curveDefs.empty() ) return; QStringList ensembleNames; std::vector correlationMatrixColumns; for ( QString paramName : m_selectedParametersList() ) { bool anyValidResults = false; std::vector correlations; std::vector selectedCurveDefs; for ( auto curveDef : curveDefs ) { auto ensemble = curveDef.ensemble(); auto address = curveDef.summaryAddress(); if ( ensemble ) { std::vector caseValuesAtTimestep; std::vector parameterValues; EnsembleParameter parameter = ensemble->ensembleParameter( paramName ); if ( parameter.isValid() ) { double correlation = std::numeric_limits::infinity(); for ( size_t caseIdx = 0u; caseIdx < ensemble->allSummaryCases().size(); ++caseIdx ) { auto summaryCase = ensemble->allSummaryCases()[caseIdx]; RifSummaryReaderInterface* reader = summaryCase->summaryReader(); if ( reader ) { std::vector values; double closestValue = std::numeric_limits::infinity(); time_t closestTimeStep = 0; if ( reader->values( address, &values ) ) { const std::vector& timeSteps = reader->timeSteps( address ); for ( size_t i = 0; i < timeSteps.size(); ++i ) { if ( timeDiff( timeSteps[i], selectedTimestep ) < timeDiff( selectedTimestep, closestTimeStep ) ) { closestValue = values[i]; closestTimeStep = timeSteps[i]; } } } if ( closestValue != std::numeric_limits::infinity() ) { caseValuesAtTimestep.push_back( closestValue ); double paramValue = parameter.values[caseIdx].toDouble(); parameterValues.push_back( paramValue ); } } } if ( parameterValues.empty() ) continue; correlation = RiaStatisticsTools::pearsonCorrelation( parameterValues, caseValuesAtTimestep ); bool validResult = RiaCurveDataTools::isValidValue( correlation, false ); if ( validResult ) { if ( m_showAbsoluteValues() ) correlation = std::abs( correlation ); anyValidResults = true; } correlations.push_back( correlation ); selectedCurveDefs.push_back( curveDef ); ensembleNames.push_back( ensemble->name() ); } } } if ( anyValidResults ) { correlationMatrixColumns.push_back( CorrelationMatrixColumn( paramName, correlations, selectedCurveDefs ) ); } } eraseInvalidEntries( correlationMatrixColumns ); if ( m_sortByValues() == Sorting::COLUMNS || m_sortByValues() == Sorting::BOTH ) { sortEntries( correlationMatrixColumns, m_sortByAbsoluteValues() || m_showAbsoluteValues() ); if ( m_showOnlyTopNCorrelations && (size_t)m_topNFilterCount < correlationMatrixColumns.size() ) { correlationMatrixColumns.erase( correlationMatrixColumns.begin() + m_topNFilterCount(), correlationMatrixColumns.end() ); } } auto correlationMatrixRows = transpose( correlationMatrixColumns ); eraseInvalidEntries( correlationMatrixRows ); if ( m_sortByValues() == Sorting::ROWS || m_sortByValues() == Sorting::BOTH ) { sortEntries( correlationMatrixRows, m_sortByAbsoluteValues() || m_showAbsoluteValues() ); if ( m_showOnlyTopNCorrelations && (size_t)m_topNFilterCount < correlationMatrixRows.size() ) { correlationMatrixRows.erase( correlationMatrixRows.begin() + m_topNFilterCount(), correlationMatrixRows.end() ); } } ensembleNames.removeDuplicates(); QString combinedEnsembleNames = ensembleNames.join( ";; " ); for ( size_t rowIdx = 0u; rowIdx < correlationMatrixRows.size(); ++rowIdx ) { for ( size_t colIdx = 0u; colIdx < correlationMatrixRows[rowIdx].m_correlations.size(); ++colIdx ) { double correlation = correlationMatrixRows[rowIdx].m_correlations[colIdx]; auto label = QString( "%1" ).arg( correlation, 0, 'f', 2 ); cvf::Color3ub color = m_legendConfig->scalarMapper()->mapToColor( correlation ); QColor qColor( color.r(), color.g(), color.b() ); auto rectangle = RiuQwtPlotTools::createBoxShapeT( label, (double)colIdx, (double)colIdx + 1.0, (double)rowIdx, (double)rowIdx + 1, qColor ); rectangle->curveDef = correlationMatrixRows[rowIdx].m_key; rectangle->parameter = correlationMatrixRows[rowIdx].m_values[colIdx]; QwtText textLabel( label ); cvf::Color3f contrastColor = RiaColorTools::contrastColor( cvf::Color3f( color ) ); textLabel.setColor( RiaColorTools::toQColor( contrastColor ) ); QFont font = textLabel.font(); font.setPixelSize( caf::FontTools::pointSizeToPixelSize( labelFontSize() ) ); textLabel.setFont( font ); QwtPlotMarker* marker = new QwtPlotMarker(); marker->setLabel( textLabel ); marker->setXValue( colIdx + 0.5 ); marker->setYValue( rowIdx + 0.5 ); rectangle->attach( m_plotWidget ); marker->attach( m_plotWidget ); m_paramLabels[colIdx] = correlationMatrixRows[rowIdx].m_values[colIdx]; } // Remove ensemble name from label if we only have one ensemble // If we have multiple ensembles, no labels contain the combined ensemble names. QString resultLabel = correlationMatrixRows[rowIdx].m_key.curveDefinitionText(); resultLabel.remove( combinedEnsembleNames + ", " ); m_resultLabels[rowIdx] = resultLabel; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::updatePlotTitle() { if ( m_useAutoPlotTitle ) { m_description = QString( "Correlation Matrix for Parameters vs Result Vectors at %2" ).arg( timeStepString() ); } if ( m_plotWidget ) { m_plotWidget->setPlotTitle( m_description ); m_plotWidget->setPlotTitleEnabled( m_showPlotTitle && !isSubPlot() ); if ( isMdiWindow() ) { m_plotWidget->setPlotTitleFontSize( titleFontSize() ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::updateLegend() { if ( m_legendConfig ) m_legendConfig->recreateLegend(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimCorrelationMatrixPlot::onPlotItemSelected( QwtPlotItem* plotItem, bool toggle, int sampleIndex ) { CorrelationMatrixShapeItem* matrixItem = dynamic_cast( plotItem ); if ( matrixItem ) { matrixCellSelected.send( std::make_pair( matrixItem->parameter, matrixItem->curveDef ) ); } }