///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2026 Equinor ASA // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RimRftTornadoPlot.h" #include "RiaColorTools.h" #include "RiaPreferences.h" #include "RigEnsembleParameter.h" #include "RigStatisticsTools.h" #include "RimCorrelationBarChartTools.h" #include "RimEclipseResultCase.h" #include "RimRftCrossPlotTools.h" #include "RimSummaryEnsemble.h" #include "RimSummaryEnsembleTools.h" #include "Formations/RimWellFormationsFile.h" #include "RiuContextMenuLauncher.h" #include "RiuGroupedBarChartBuilder.h" #include "RiuPlotItem.h" #include "RiuQwtPlotWidget.h" #include "RiuRftCorrelationPlotTools.h" #include "cafPdmUiCheckBoxEditor.h" #include #include #include #include CAF_PDM_SOURCE_INIT( RimRftTornadoPlot, "RftTornadoPlot" ); namespace { constexpr double axisMinAbsolute = -0.7; constexpr double anchorMargin = 0.03; } // namespace //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimRftTornadoPlot::RimRftTornadoPlot() { CAF_PDM_InitObject( "RFT Tornado Plot", ":/CorrelationTornadoPlot16x16.png" ); CAF_PDM_InitFieldNoDefault( &m_ensemble, "Ensemble", "Ensemble" ); CAF_PDM_InitField( &m_wellName, "WellName", QString(), "Well Name" ); CAF_PDM_InitFieldNoDefault( &m_selectedTimeStep, "TimeStep", "Time Step" ); CAF_PDM_InitFieldNoDefault( &m_eclipseCase, "EclipseCase", "Eclipse Case (MD fallback)" ); CAF_PDM_InitField( &m_filterMode, "FilterMode", RimRftCrossPlotTools::DepthFilterModeEnum( RimRftCrossPlotTools::DepthFilterMode::NONE ), "Filter By" ); CAF_PDM_InitField( &m_depthRangeMin, "DepthRangeMin", 0.0, "Min Depth" ); CAF_PDM_InitField( &m_depthRangeMax, "DepthRangeMax", 5000.0, "Max Depth" ); CAF_PDM_InitFieldNoDefault( &m_wellFormations, "WellFormations", "Well Formations File" ); CAF_PDM_InitFieldNoDefault( &m_selectedZones, "SelectedZones", "Zones" ); CAF_PDM_InitField( &m_depthType, "DepthType", caf::AppEnum( RiaDefines::DepthType::MEASURED_DEPTH ), "Depth Type" ); m_depthType.uiCapability()->setUiHidden( true ); // driven by the parent RimRftCorrelationReportPlot CAF_PDM_InitField( &m_showAbsoluteValues, "ShowAbsoluteValues", false, "Show Absolute Values" ); CAF_PDM_InitField( &m_sortByAbsoluteValues, "SortByAbsoluteValues", true, "Sort by Absolute Values" ); CAF_PDM_InitField( &m_showOnlyTopNCorrelations, "ShowOnlyTopN", true, "Show Only Top Correlations" ); CAF_PDM_InitField( &m_topNFilterCount, "TopNFilterCount", 10, "Number of Rows" ); QColor qColor = QColor( "#3173b2" ); CAF_PDM_InitField( &m_barColor, "BarColor", RiaColorTools::fromQColorTo3f( qColor ), "Bar Color (Positive)" ); QColor highlightColor = QColor( "#f5a623" ); CAF_PDM_InitField( &m_highlightBarColor, "HighlightBarColor", RiaColorTools::fromQColorTo3f( highlightColor ), "Bar Color (Selected)" ); CAF_PDM_InitField( &m_useAutoPlotTitle, "UseAutoPlotTitle", true, "Auto Title" ); CAF_PDM_InitField( &m_description, "Description", QString( "RFT Tornado Plot" ), "Title" ); CAF_PDM_InitFieldNoDefault( &m_labelFontSize, "LabelFontSize", "Bar Label Font Size" ); CAF_PDM_InitFieldNoDefault( &m_axisTitleFontSize, "AxisTitleFontSize", "Axis Title Font Size" ); CAF_PDM_InitFieldNoDefault( &m_axisValueFontSize, "AxisValueFontSize", "Axis Value Font Size" ); m_labelFontSize = caf::FontTools::RelativeSize::Small; m_axisTitleFontSize = caf::FontTools::RelativeSize::Small; m_axisValueFontSize = caf::FontTools::RelativeSize::Small; m_showPlotLegends = false; setLegendsVisible( false ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimRftTornadoPlot::~RimRftTornadoPlot() { cleanupBeforeClose(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setEnsemble( RimSummaryEnsemble* ensemble ) { m_ensemble = ensemble; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setWellName( const QString& wellName ) { m_wellName = wellName; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setTimeStep( const QDateTime& timeStep ) { m_selectedTimeStep = timeStep; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setEclipseCase( RimEclipseResultCase* eclipseCase ) { m_eclipseCase = eclipseCase; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setDepthRange( double minMd, double maxMd ) { m_depthRangeMin = minMd; m_depthRangeMax = maxMd; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setDepthType( RiaDefines::DepthType depthType ) { m_depthType = depthType; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setFilterMode( RimRftCrossPlotTools::DepthFilterMode filterMode ) { m_filterMode = filterMode; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setWellFormations( RimWellFormationsFile* wellFormationsFile ) { m_wellFormations = wellFormationsFile; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setSelectedZones( const std::vector& zones ) { m_selectedZones = zones; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setSelectedParameter( const QString& paramName ) { m_selectedParameter = paramName; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::setParameterSelectedCallback( ParameterSelectedCallback callback ) { m_parameterSelectedCallback = std::move( callback ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuQwtPlotWidget* RimRftTornadoPlot::viewer() { return m_plotWidget; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuPlotWidget* RimRftTornadoPlot::plotWidget() { return m_plotWidget; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::updateAxes() { if ( !m_plotWidget ) return; const int titleSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisTitleFontSize() ); const int valueSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisValueFontSize() ); m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultLeft(), "Parameter" ); m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultLeft(), true ); m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultLeft(), titleSize, valueSize, false, Qt::AlignCenter ); if ( m_showAbsoluteValues() ) { m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultBottom(), "Pearson Correlation Coefficient ABS" ); // Leave room left of zero for the bar labels m_plotWidget->setAxisRange( RiuPlotAxis::defaultBottom(), axisMinAbsolute, 1.0 ); } else { m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultBottom(), "Pearson Correlation Coefficient" ); m_plotWidget->setAxisRange( RiuPlotAxis::defaultBottom(), -1.0, 1.0 ); } m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultBottom(), true ); m_plotWidget->setAxisFontsAndAlignment( RiuPlotAxis::defaultBottom(), titleSize, valueSize, false, Qt::AlignCenter ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimRftTornadoPlot::asciiDataForPlotExport() const { RiuGroupedBarChartBuilder chartBuilder; addDataToChartBuilder( chartBuilder ); return chartBuilder.plotContentAsText(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::detachAllCurves() { if ( m_plotWidget ) { RiuRftCorrelationPlotTools::detachBarChartItems( m_plotWidget ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimRftTornadoPlot::description() const { return m_description(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QWidget* RimRftTornadoPlot::viewWidget() { return m_plotWidget; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::deleteViewWidget() { cleanupBeforeClose(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::doRenderWindowContent( QPaintDevice* paintDevice ) { if ( m_plotWidget ) m_plotWidget->render( paintDevice ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuPlotWidget* RimRftTornadoPlot::doCreatePlotViewWidget( QWidget* parent ) { if ( !m_plotWidget ) { m_plotWidget = new RiuQwtPlotWidget( this, parent ); updatePlotTitle(); new RiuContextMenuLauncher( m_plotWidget, { "RicShowPlotDataFeature" } ); } return m_plotWidget; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::onLoadDataAndUpdate() { updateDockWindowVisibility(); if ( m_plotWidget ) { detachAllCurves(); RiuGroupedBarChartBuilder chartBuilder; chartBuilder.setBarColor( RiaColorTools::toQColor( m_barColor() ) ); m_lastCorrelations = addDataToChartBuilder( chartBuilder ); const int labelSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_labelFontSize() ); chartBuilder.setLabelFontSize( labelSize ); // Labels start at the left edge of the plot, in the empty space left of the bars. The anchor must be slightly inside // the axis range, as Qwt skips scale items positioned outside the canvas. Only used for absolute values, as signed // values have bars on both sides of zero. if ( m_showAbsoluteValues() ) chartBuilder.setBarTextAnchor( axisMinAbsolute + anchorMargin ); RiuRftCorrelationPlotTools::addHorizontalBarChart( m_plotWidget, chartBuilder, m_showOnlyTopNCorrelations() ? m_topNFilterCount() : -1 ); RimCorrelationBarChartTools::highlightSelectedParameterBar( m_plotWidget, m_selectedParameter, RiaColorTools::toQColor( m_barColor() ), RiaColorTools::toQColor( m_highlightBarColor() ) ); RiuRftCorrelationPlotTools::removeLegend( m_plotWidget ); updateAxes(); updatePlotTitle(); m_plotWidget->scheduleReplot(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { auto* settingsGroup = uiOrdering.addNewGroup( "Tornado Settings" ); settingsGroup->add( &m_showAbsoluteValues ); if ( !m_showAbsoluteValues() ) settingsGroup->add( &m_sortByAbsoluteValues ); settingsGroup->add( &m_showOnlyTopNCorrelations ); if ( m_showOnlyTopNCorrelations() ) settingsGroup->add( &m_topNFilterCount ); auto* colorGroup = uiOrdering.addNewGroup( "Colors" ); colorGroup->setCollapsedByDefault(); colorGroup->add( &m_barColor ); colorGroup->add( &m_highlightBarColor ); auto* plotGroup = uiOrdering.addNewGroup( "Plot Settings" ); plotGroup->setCollapsedByDefault(); plotGroup->add( &m_useAutoPlotTitle ); plotGroup->add( &m_description ); plotGroup->add( &m_labelFontSize ); plotGroup->add( &m_axisTitleFontSize ); plotGroup->add( &m_axisValueFontSize ); m_description.uiCapability()->setUiReadOnly( m_useAutoPlotTitle() ); uiOrdering.skipRemainingFields( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { RimPlot::fieldChangedByUi( changedField, oldValue, newValue ); loadDataAndUpdate(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::onPlotItemSelected( std::shared_ptr plotItem, bool /*toggle*/, int /*sampleIndex*/ ) { const QString paramName = RimCorrelationBarChartTools::parameterNameFromPlotItem( plotItem ); if ( !paramName.isEmpty() && m_parameterSelectedCallback ) m_parameterSelectedCallback( paramName ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimRftTornadoPlot::depthIntervals() const { return RimRftCrossPlotTools::buildDepthIntervals( m_filterMode(), m_depthRangeMin(), m_depthRangeMax(), m_wellFormations(), m_wellName(), m_selectedZones(), m_depthType() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::map RimRftTornadoPlot::addDataToChartBuilder( RiuGroupedBarChartBuilder& chartBuilder ) const { std::map correlations; if ( !m_ensemble() || m_wellName().isEmpty() || !m_selectedTimeStep().isValid() ) return correlations; const auto& allCases = m_ensemble->allSummaryCases(); // Build pressure vector per case (indices match allCases order) const std::vector pressurePerCase = RimRftCrossPlotTools::computeMeanPressurePerCase( m_ensemble(), m_wellName(), m_selectedTimeStep(), m_eclipseCase(), depthIntervals(), m_depthType() ); // For each numeric parameter, compute Pearson correlation against pressurePerCase for ( const auto& param : RimSummaryEnsembleTools::alphabeticEnsembleParameters( allCases ) ) { if ( !param.isNumeric() ) continue; if ( static_cast( param.values.size() ) != allCases.size() ) continue; std::vector paramValues; std::vector pressureValues; paramValues.reserve( allCases.size() ); pressureValues.reserve( allCases.size() ); for ( size_t i = 0; i < allCases.size(); ++i ) { if ( std::isinf( pressurePerCase[i] ) ) continue; paramValues.push_back( param.values[i].toDouble() ); pressureValues.push_back( pressurePerCase[i] ); } if ( paramValues.size() < 2 ) continue; double pearson = RigStatisticsTools::pearsonCorrelation( paramValues, pressureValues ); if ( std::isinf( pearson ) || std::isnan( pearson ) ) continue; correlations[param.name] = pearson; RimCorrelationBarChartTools::addCorrelationBar( chartBuilder, param.name, pearson, m_showAbsoluteValues(), m_sortByAbsoluteValues() ); } return correlations; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::updatePlotTitle() { if ( !m_plotWidget ) return; if ( m_useAutoPlotTitle() && m_ensemble() ) { const QString filterDescription = RimRftCrossPlotTools::depthFilterDescription( m_filterMode(), m_depthType(), m_depthRangeMin(), m_depthRangeMax(), m_selectedZones() ); const QString rangeStr = filterDescription.isEmpty() ? QString() : QString( " [%1]" ).arg( filterDescription ); m_description = QString( "Parameter Correlation vs RFT Pressure%1, %2" ).arg( rangeStr ).arg( m_ensemble->name() ); } m_plotWidget->setPlotTitle( m_description() ); m_plotWidget->setPlotTitleEnabled( m_showPlotTitle() ); m_plotWidget->setPlotTitleFontSize( titleFontSize() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimRftTornadoPlot::cleanupBeforeClose() { detachAllCurves(); if ( m_plotWidget ) { m_plotWidget->setParent( nullptr ); delete m_plotWidget; m_plotWidget = nullptr; } }