///////////////////////////////////////////////////////////////////////////////// // // 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 "RimParameterResultCrossPlot.h" #include "RiaColorTables.h" #include "RifSummaryReaderInterface.h" #include "RigEnsembleParameter.h" #include "RimPlotDataFilterCollection.h" #include "RimProject.h" #include "RimSummaryAddress.h" #include "RimSummaryCase.h" #include "RimSummaryEnsemble.h" #include "RiuPlotCurve.h" #include "RiuQwtPlotCurve.h" #include "RiuQwtPlotWidget.h" #include "RiuQwtSymbol.h" #include "cafPdmUiComboBoxEditor.h" #include "qwt_picker_machine.h" #include "qwt_plot.h" #include "qwt_plot_curve.h" #include "qwt_plot_picker.h" #include "qwt_text.h" #include #include #include CAF_PDM_SOURCE_INIT( RimParameterResultCrossPlot, "ParameterResultCrossPlot" ); //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimParameterResultCrossPlot::RimParameterResultCrossPlot() : RimAbstractCorrelationPlot() { CAF_PDM_InitObject( "ParameterResultCross Plot", ":/CorrelationCrossPlot16x16.png" ); CAF_PDM_InitField( &m_ensembleParameter, "EnsembleParameter", QString( "" ), "Ensemble Parameter" ); m_ensembleParameter.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); m_selectMultipleVectors = true; m_legendFontSize = caf::FontTools::RelativeSize::Small; m_xRange = std::make_pair( std::numeric_limits::infinity(), -std::numeric_limits::infinity() ); m_yRange = std::make_pair( std::numeric_limits::infinity(), -std::numeric_limits::infinity() ); m_showPlotLegends = false; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimParameterResultCrossPlot::~RimParameterResultCrossPlot() { if ( isMdiWindow() ) removeMdiWindowFromMdiArea(); cleanupBeforeClose(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimParameterResultCrossPlot::setEnsembleParameter( const QString& ensembleParameter ) { m_ensembleParameter = ensembleParameter; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimParameterResultCrossPlot::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { RimAbstractCorrelationPlot::fieldChangedByUi( changedField, oldValue, newValue ); if ( changedField == &m_ensembleParameter ) { loadDataAndUpdate(); updateConnectedEditors(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimParameterResultCrossPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { appendDataSourceFields( uiConfigName, uiOrdering ); caf::PdmUiGroup* crossPlotGroup = uiOrdering.addNewGroup( "Cross Plot Parameters" ); crossPlotGroup->add( &m_ensembleParameter ); caf::PdmUiGroup* plotGroup = uiOrdering.addNewGroup( "Plot Settings" ); plotGroup->add( &m_showPlotTitle ); plotGroup->add( &m_useAutoPlotTitle ); plotGroup->add( &m_description ); RimPlot::defineUiOrdering( uiConfigName, *plotGroup ); plotGroup->add( &m_titleFontSize ); plotGroup->add( &m_legendFontSize ); plotGroup->add( &m_axisTitleFontSize ); plotGroup->add( &m_axisValueFontSize ); m_description.uiCapability()->setUiReadOnly( m_useAutoPlotTitle() ); uiOrdering.skipRemainingFields( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimParameterResultCrossPlot::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { QList options = RimAbstractCorrelationPlot::calculateValueOptions( fieldNeedingOptions ); if ( fieldNeedingOptions == &m_ensembleParameter ) { for ( const auto& param : ensembleParameters() ) { options.push_back( caf::PdmOptionItemInfo( param.uiName(), param.name ) ); } } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimParameterResultCrossPlot::onLoadDataAndUpdate() { updateMdiWindowVisibility(); m_selectedVarsUiField = selectedVectorNamesText(); if ( m_plotWidget && m_analyserOfSelectedCurveDefs ) { createPoints(); updateAxes(); updatePlotTitle(); m_plotWidget->scheduleReplot(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimParameterResultCrossPlot::updateAxes() { if ( !m_plotWidget ) return; 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->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 ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QStringList caseNamesOfValidEnsembleCases( const RimSummaryEnsemble* ensemble ) { QStringList caseNames; for ( auto summaryCase : ensemble->allSummaryCases() ) { RifSummaryReaderInterface* reader = summaryCase->summaryReader(); if ( !reader ) continue; if ( !summaryCase->caseRealizationParameters() ) continue; caseNames.push_back( summaryCase->displayCaseName() ); } return caseNames; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimParameterResultCrossPlot::createPoints() { detachAllCurves(); m_valuesForTextReport.clear(); time_t selectedTimestep = m_timeStep().toSecsSinceEpoch(); caf::ColorTable colorTable = RiaColorTables::categoryPaletteColors(); if ( ensembles().empty() ) return; if ( addresses().empty() ) return; bool showEnsembleName = ensembles().size() > 1u; m_xRange = std::make_pair( std::numeric_limits::infinity(), -std::numeric_limits::infinity() ); m_yRange = std::make_pair( std::numeric_limits::infinity(), -std::numeric_limits::infinity() ); 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 activeCases = filterEnsembleCases( ensemble ); for ( size_t caseIdx = 0u; caseIdx < ensemble->allSummaryCases().size(); ++caseIdx ) { auto summaryCase = ensemble->allSummaryCases()[caseIdx]; if ( activeCases.count( summaryCase ) == 0 ) continue; RifSummaryReaderInterface* reader = summaryCase->summaryReader(); if ( !reader ) continue; if ( !summaryCase->caseRealizationParameters() ) continue; double closestValue = std::numeric_limits::infinity(); time_t closestTimeStep = 0; auto [isOk, values] = reader->values( address ); if ( isOk ) { 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() ) { std::vector caseValuesAtTimestep; std::vector 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() ); } } addressIdx++; } ensembleIdx++; } } namespace internal { //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- class CurveTracker : public QwtPlotPicker { public: CurveTracker( QwtPlot* plot ) : QwtPlotPicker( plot->canvas() ) { setStateMachine( new QwtPickerTrackerMachine() ); setRubberBand( QwtPicker::NoRubberBand ); setTrackerMode( QwtPicker::AlwaysOn ); } protected: QwtText trackerText( const QPoint& pos ) const override { double minDistance = std::numeric_limits::max(); QString closestCurveLabel; for ( QwtPlotItem* item : plot()->itemList() ) { if ( item->rtti() == QwtPlotItem::Rtti_PlotCurve ) { auto curve = static_cast( item ); double distance = std::numeric_limits::max(); curve->closestPoint( pos, &distance ); if ( distance < minDistance ) { minDistance = distance; closestCurveLabel = curve->title().text(); } } } if ( minDistance < 20.0 ) { QwtText text( closestCurveLabel ); text.setBackgroundBrush( QBrush( Qt::white ) ); text.setColor( Qt::black ); return text; } return QwtText(); } }; } // namespace internal //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuPlotWidget* RimParameterResultCrossPlot::doCreatePlotViewWidget( QWidget* mainWindowParent /*= nullptr */ ) { RimAbstractCorrelationPlot::doCreatePlotViewWidget( mainWindowParent ); if ( m_plotWidget ) { // Add a curve tracker to display the name of the realization when mouse is close to a sample in the cross plot new internal::CurveTracker( m_plotWidget->qwtPlot() ); } return m_plotWidget; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimParameterResultCrossPlot::asciiDataForPlotExport() const { QString asciiData; asciiData += "Parameter\tResult\n"; for ( const auto& valuePair : m_valuesForTextReport ) { asciiData += QString( "%1\t%2\n" ).arg( valuePair.first ).arg( valuePair.second ); } return asciiData; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimParameterResultCrossPlot::updatePlotTitle() { if ( m_useAutoPlotTitle && !ensembles().empty() ) { auto ensemble = *ensembles().begin(); QString vectorName = completeAddressText(); m_description = QString( "Cross Plot %1, %2 x %3 at %4" ).arg( ensemble->name() ).arg( m_ensembleParameter ).arg( vectorName ).arg( timeStepString() ); } m_plotWidget->setPlotTitle( m_description ); m_plotWidget->setPlotTitleEnabled( m_showPlotTitle && !isSubPlot() ); m_plotWidget->setPlotTitleFontSize( titleFontSize() ); }