///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2017 Statoil 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 "RiuRelativePermeabilityPlotPanel.h" #include "RiaCurveDataTools.h" #include "RiaDefines.h" #include "RiaEclipseUnitTools.h" #include "RiaInterpolationTools.h" #include "RiaPhaseTools.h" #include "RiaPlotDefines.h" #include "RiaResultNames.h" #include "RigFlowDiagSolverInterface.h" #include "RiuDockedQwtPlot.h" #include "RiuGuiTheme.h" #include "RiuPlotCurveSymbol.h" #include "RiuQwtPlotTools.h" #include "RiuQwtSymbol.h" #include "RiuRelativePermeabilityPlotUpdater.h" #include "RiuTextDialog.h" #include "cafAssert.h" #include "qwt_legend.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_scale_engine.h" #include "qwt_symbol.h" #include "qwt_text.h" #include #include #include #include #include #include #include #include #include #include #include #include //================================================================================================== // // // //================================================================================================== class RelPermQwtPlot : public RiuDockedQwtPlot { public: RelPermQwtPlot( QWidget* parent ) : RiuDockedQwtPlot( parent ) { } QSize sizeHint() const override { return QSize( 100, 100 ); } QSize minimumSizeHint() const override { return QSize( 0, 0 ); } }; //================================================================================================== // // // //================================================================================================== class RiuRelPermQwtPicker : public QwtPicker { public: RiuRelPermQwtPicker( QwtPlot* plot, std::function textProvider ) : QwtPicker( QwtPicker::NoRubberBand, QwtPicker::AlwaysOn, plot->canvas() ) , m_textProvider( textProvider ) { setStateMachine( new QwtPickerTrackerMachine ); } QwtText trackerText( const QPoint& ) const override { QwtText text( m_textProvider() ); text.setRenderFlags( Qt::AlignLeft ); return text; } private: std::function m_textProvider; }; //================================================================================================== /// /// \class RiuRelativePermeabilityPlotPanel /// /// /// //================================================================================================== //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuRelativePermeabilityPlotPanel::RiuRelativePermeabilityPlotPanel( QWidget* parent ) : QWidget( parent ) , m_unitSystem( RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN ) , m_swat( HUGE_VAL ) , m_sgas( HUGE_VAL ) , m_plotUpdater( new RiuRelativePermeabilityPlotUpdater( this ) ) , m_qwtPlot( nullptr ) , m_curveSetGroupBox( nullptr ) , m_showDrainageCheckBox( nullptr ) , m_showImbibitionCheckBox( nullptr ) , m_selectedCurvesButtonGroup( nullptr ) , m_groupBox( nullptr ) , m_logarithmicScaleKrAxisCheckBox( nullptr ) , m_showUnscaledCheckBox( nullptr ) , m_showScaledCheckBox( nullptr ) , m_fixedXAxisCheckBox( nullptr ) , m_fixedLeftYAxisCheckBox( nullptr ) , m_plotOneMinusSgasCheckBox( nullptr ) , m_trackerPlotMarker( nullptr ) { m_qwtPlot = new RelPermQwtPlot( this ); m_qwtPlot->setProperty( "qss-class", "RelPermPlot" ); setPlotDefaults( m_qwtPlot ); applyFontSizes( false ); // Initialize picker for curve value tracking m_qwtPicker = new RiuRelPermQwtPicker( m_qwtPlot, [this]() { return m_trackerLabel; } ); RiuGuiTheme::styleQwtItem( m_qwtPicker ); connect( m_qwtPicker, SIGNAL( activated( bool ) ), this, SLOT( slotPickerActivated( bool ) ) ); connect( m_qwtPicker, SIGNAL( moved( const QPoint& ) ), this, SLOT( slotPickerPointChanged( const QPoint& ) ) ); m_selectedCurvesButtonGroup = new QButtonGroup( this ); m_selectedCurvesButtonGroup->setExclusive( false ); m_selectedCurvesButtonGroup->addButton( new QCheckBox( "KRW" ), RigFlowDiagDefines::RelPermCurve::KRW ); m_selectedCurvesButtonGroup->addButton( new QCheckBox( "KRG" ), RigFlowDiagDefines::RelPermCurve::KRG ); m_selectedCurvesButtonGroup->addButton( new QCheckBox( "KROW" ), RigFlowDiagDefines::RelPermCurve::KROW ); m_selectedCurvesButtonGroup->addButton( new QCheckBox( "KROG" ), RigFlowDiagDefines::RelPermCurve::KROG ); m_selectedCurvesButtonGroup->addButton( new QCheckBox( "PCOW" ), RigFlowDiagDefines::RelPermCurve::PCOW ); m_selectedCurvesButtonGroup->addButton( new QCheckBox( "PCOG" ), RigFlowDiagDefines::RelPermCurve::PCOG ); // Create Curve Set groupbox m_curveSetGroupBox = new QGroupBox( "Curve Set" ); QHBoxLayout* curveSetLayout = new QHBoxLayout; m_curveSetGroupBox->setLayout( curveSetLayout ); m_showDrainageCheckBox = new QCheckBox( "Drainage" ); m_showImbibitionCheckBox = new QCheckBox( "Imbibition" ); m_showDrainageCheckBox->setChecked( true ); m_showImbibitionCheckBox->setChecked( false ); curveSetLayout->addWidget( m_showDrainageCheckBox ); curveSetLayout->addWidget( m_showImbibitionCheckBox ); m_groupBox = new QGroupBox( "Curves" ); QGridLayout* groupBoxLayout = new QGridLayout; m_groupBox->setLayout( groupBoxLayout ); QList checkButtonList = m_selectedCurvesButtonGroup->buttons(); for ( int i = 0; i < checkButtonList.size(); i++ ) { checkButtonList[i]->setChecked( true ); groupBoxLayout->addWidget( checkButtonList[i], i / 2, i % 2 ); } m_logarithmicScaleKrAxisCheckBox = new QCheckBox( "Log Scale Kr Axis" ); m_showUnscaledCheckBox = new QCheckBox( "Show Unscaled" ); m_showScaledCheckBox = new QCheckBox( "Show Scaled" ); m_fixedXAxisCheckBox = new QCheckBox( "Fixed [0, 1] X-axis" ); m_fixedLeftYAxisCheckBox = new QCheckBox( "Fixed [0, 1] Kr-axis" ); m_plotOneMinusSgasCheckBox = new QCheckBox( "Plot 1-SGAS" ); m_plotOneMinusSgasCheckBox->setToolTip( "Plot gas curves (and PCGW) as a function of (1 - SGAS), aligning the gas saturation axis with the water saturation axis. " "Only available for two-phase gas-water systems." ); m_fixedXAxisCheckBox->setChecked( true ); m_fixedLeftYAxisCheckBox->setChecked( true ); m_showScaledCheckBox->setChecked( true ); QCheckBox* showCurveSelection = new QCheckBox( "Show Curve Selection" ); showCurveSelection->setCheckState( Qt::Unchecked ); connect( showCurveSelection, SIGNAL( stateChanged( int ) ), SLOT( slotShowCurveSelectionWidgets( int ) ) ); QVBoxLayout* leftLayout = new QVBoxLayout; leftLayout->addWidget( m_curveSetGroupBox ); leftLayout->addWidget( showCurveSelection ); leftLayout->addWidget( m_groupBox ); leftLayout->addWidget( m_logarithmicScaleKrAxisCheckBox ); leftLayout->addWidget( m_showScaledCheckBox ); leftLayout->addWidget( m_showUnscaledCheckBox ); leftLayout->addWidget( m_fixedXAxisCheckBox ); leftLayout->addWidget( m_fixedLeftYAxisCheckBox ); leftLayout->addWidget( m_plotOneMinusSgasCheckBox ); leftLayout->addStretch( 1 ); QHBoxLayout* mainLayout = new QHBoxLayout(); mainLayout->addLayout( leftLayout ); mainLayout->addWidget( m_qwtPlot ); mainLayout->setContentsMargins( 5, 0, 0, 0 ); setLayout( mainLayout ); connect( m_selectedCurvesButtonGroup, SIGNAL( idClicked( int ) ), SLOT( slotButtonInButtonGroupClicked( int ) ) ); connect( m_showDrainageCheckBox, SIGNAL( stateChanged( int ) ), SLOT( slotSomeCheckBoxStateChanged( int ) ) ); connect( m_showImbibitionCheckBox, SIGNAL( stateChanged( int ) ), SLOT( slotSomeCheckBoxStateChanged( int ) ) ); connect( m_logarithmicScaleKrAxisCheckBox, SIGNAL( stateChanged( int ) ), SLOT( slotSomeCheckBoxStateChanged( int ) ) ); connect( m_showUnscaledCheckBox, SIGNAL( stateChanged( int ) ), SLOT( slotSomeCheckBoxStateChanged( int ) ) ); connect( m_showScaledCheckBox, SIGNAL( stateChanged( int ) ), SLOT( slotSomeCheckBoxStateChanged( int ) ) ); connect( m_fixedXAxisCheckBox, SIGNAL( stateChanged( int ) ), SLOT( slotSomeCheckBoxStateChanged( int ) ) ); connect( m_fixedLeftYAxisCheckBox, SIGNAL( stateChanged( int ) ), SLOT( slotSomeCheckBoxStateChanged( int ) ) ); connect( m_plotOneMinusSgasCheckBox, SIGNAL( stateChanged( int ) ), SLOT( slotSomeCheckBoxStateChanged( int ) ) ); slotShowCurveSelectionWidgets( showCurveSelection->checkState() ); plotUiSelectedCurves(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuRelativePermeabilityPlotPanel::~RiuRelativePermeabilityPlotPanel() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::setPlotDefaults( QwtPlot* plot ) { RiuQwtPlotTools::setCommonPlotBehaviour( plot ); { QwtText plotTitle = plot->title(); QFont titleFont = plotTitle.font(); titleFont.setPointSize( 10 ); plotTitle.setFont( titleFont ); plot->setTitle( plotTitle ); } plot->setAxesCount( QwtAxis::XBottom, 1 ); plot->setAxesCount( QwtAxis::YLeft, 1 ); plot->setAxisMaxMinor( QwtAxis::XBottom, 2 ); plot->setAxisMaxMinor( QwtAxis::YLeft, 3 ); QwtLegend* legend = new QwtLegend( plot ); plot->insertLegend( legend, QwtPlot::BottomLegend ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::setPlotData( RiaDefines::EclipseUnitSystem unitSystem, const std::vector& relPermCurves, double swat, double sgas, const QString& caseName, const QString& cellReferenceText, const std::set& availablePhases ) { // cvf::Trace::show("Set RelPerm plot data"); m_unitSystem = unitSystem; m_allCurvesArr = relPermCurves; m_swat = swat; m_sgas = sgas; m_caseName = caseName; m_cellReferenceText = cellReferenceText; m_availablePhases = availablePhases; updateCheckboxTexts(); updatePlotOneMinusSgasCheckBoxState(); plotUiSelectedCurves(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::enableImbibitionCurveSelection( bool enable ) { if ( !enable ) { m_showImbibitionCheckBox->setChecked( false ); m_showImbibitionCheckBox->setDisabled( true ); } else { m_showImbibitionCheckBox->setDisabled( false ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::clearPlot() { // cvf::Trace::show("Clear RelPerm plot data"); if ( m_allCurvesArr.empty() && m_cellReferenceText.isEmpty() ) { return; } m_unitSystem = RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; m_allCurvesArr.clear(); m_swat = HUGE_VAL; m_sgas = HUGE_VAL; m_caseName.clear(); m_cellReferenceText.clear(); plotCurvesInQwt( m_unitSystem, m_allCurvesArr, m_swat, m_sgas, m_cellReferenceText, false, true, true, m_qwtPlot, &m_myPlotMarkers, false, false, false, m_availablePhases ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuRelativePermeabilityPlotUpdater* RiuRelativePermeabilityPlotPanel::plotUpdater() { return m_plotUpdater.get(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::applyFontSizes( bool replot /*= true*/ ) { m_qwtPlot->applyFontSizes( replot ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::plotUiSelectedCurves() { std::vector selectedCurves = gatherUiSelectedCurves(); const bool useLogScale = m_logarithmicScaleKrAxisCheckBox->isChecked(); const bool fixedXAxis = m_fixedXAxisCheckBox->isChecked(); const bool fixedYAxis = m_fixedLeftYAxisCheckBox->isChecked(); plotCurvesInQwt( m_unitSystem, selectedCurves, m_swat, m_sgas, m_cellReferenceText, useLogScale, fixedXAxis, fixedYAxis, m_qwtPlot, &m_myPlotMarkers, m_showScaledCheckBox->isChecked(), m_showUnscaledCheckBox->isChecked(), m_plotOneMinusSgasCheckBox->isChecked(), m_availablePhases ); } //-------------------------------------------------------------------------------------------------- /// Add a transparent curve to make tooltip available on given points. //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::addTransparentCurve( QwtPlot* plot, const std::vector& points, const std::vector& axes, bool logScaleLeftAxis ) { QwtPlotCurve* curveLeftAxis = new QwtPlotCurve(); QwtPlotCurve* curveRightAxis = new QwtPlotCurve(); QVector pointsOnLeftAxis; QVector pointsOnRightAxis; // Each point is defined by either left or right axis CAF_ASSERT( points.size() == axes.size() ); for ( size_t i = 0; i < points.size(); i++ ) { if ( !RiaCurveDataTools::isValidValue( points[i].y(), logScaleLeftAxis ) ) continue; if ( axes[i] == LEFT_YAXIS ) { pointsOnLeftAxis.push_back( points[i] ); } else { pointsOnRightAxis.push_back( points[i] ); } } curveLeftAxis->setSamples( pointsOnLeftAxis ); curveRightAxis->setSamples( pointsOnRightAxis ); curveLeftAxis->setYAxis( QwtAxis::YLeft ); curveRightAxis->setYAxis( QwtAxis::YRight ); curveLeftAxis->setStyle( QwtPlotCurve::NoCurve ); curveRightAxis->setStyle( QwtPlotCurve::NoCurve ); curveLeftAxis->setLegendAttribute( QwtPlotCurve::LegendNoAttribute ); curveRightAxis->setLegendAttribute( QwtPlotCurve::LegendNoAttribute ); curveLeftAxis->attach( plot ); curveRightAxis->attach( plot ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::plotCurvesInQwt( RiaDefines::EclipseUnitSystem unitSystem, const std::vector& curveArr, double swat, double sgas, QString cellReferenceText, bool logScaleLeftAxis, bool fixedXAxis, bool fixedLeftYAxis, QwtPlot* plot, std::vector* myPlotMarkers, bool showScaled, bool showUnscaled, bool plotOneMinusSgas, const std::set& availablePhases ) { bool skipUnscaledLegends = showScaled && showUnscaled; plot->detachItems( QwtPlotItem::Rtti_PlotCurve ); // Workaround for detaching only plot markers that we have added // Needed as long as the curve point tracker is also using plot markers for its marking // plot->detachItems(QwtPlotItem::Rtti_PlotMarker); for ( QwtPlotMarker* marker : *myPlotMarkers ) { marker->detach(); delete marker; } myPlotMarkers->clear(); std::vector points; std::vector axes; const QColor waterColor = RiuGuiTheme::getColorByVariableName( "curveColorWater" ); const QColor gasColor = RiuGuiTheme::getColorByVariableName( "curveColorGas" ); // Only apply the 1-SGAS transformation for two-phase gas-water systems const bool applyOneMinusSgas = plotOneMinusSgas && RiaPhaseTools::isTwoPhaseGasWater( availablePhases ); // Use a local, possibly saturation-transformed, copy of the curves for plotting. The same transform is also // applied when generating the exported ascii data, see asciiDataForUiSelectedCurves(), so that the two stay in sync. const std::vector curvesToPlot = applyOneMinusSgasTransform( curveArr, applyOneMinusSgas ); bool shouldEnableRightYAxis = false; for ( size_t i = 0; i < curvesToPlot.size(); i++ ) { const RigFlowDiagDefines::RelPermCurve& curve = curvesToPlot[i]; const auto unscaledCurve = ( curve.epsMode == RigFlowDiagDefines::RelPermCurve::EPS_OFF ); // Which axis should this curve be plotted on WhichYAxis plotOnWhichYAxis = LEFT_YAXIS; if ( curve.ident == RigFlowDiagDefines::RelPermCurve::PCOW || curve.ident == RigFlowDiagDefines::RelPermCurve::PCOG ) { plotOnWhichYAxis = RIGHT_YAXIS; } // Determine display name for the curve std::string displayName = curve.name; if ( curve.ident == RigFlowDiagDefines::RelPermCurve::PCOG ) { std::string preferredName; if ( curve.curveSet == RigFlowDiagDefines::RelPermCurve::IMBIBITION ) { preferredName += "I"; } preferredName += RiaPhaseTools::getPreferredPcogName( availablePhases ).toStdString(); displayName = preferredName; } QwtPlotCurve* qwtCurve = new QwtPlotCurve( displayName.c_str() ); CAF_ASSERT( curve.saturationVals.size() == curve.yVals.size() ); qwtCurve->setSamples( curve.saturationVals.data(), curve.yVals.data(), static_cast( curve.saturationVals.size() ) ); // Use the display name which may be modified for GAS/WATER cases qwtCurve->setTitle( displayName.c_str() ); qwtCurve->setStyle( QwtPlotCurve::Lines ); auto penStyle = Qt::SolidLine; if ( curve.ident == RigFlowDiagDefines::RelPermCurve::PCOW || curve.ident == RigFlowDiagDefines::RelPermCurve::PCOG ) { penStyle = Qt::DashLine; } const QPen curvePen( QBrush(), 1, penStyle ); qwtCurve->setPen( curvePen ); auto* curveSymbol = unscaledCurve ? new RiuQwtSymbol( RiuPlotCurveSymbol::SYMBOL_CROSS ) : new RiuQwtSymbol( RiuPlotCurveSymbol::SYMBOL_ELLIPSE ); curveSymbol->setSize( 6, 6 ); curveSymbol->setBrush( Qt::NoBrush ); qwtCurve->setSymbol( curveSymbol ); qwtCurve->setLegendAttribute( QwtPlotCurve::LegendShowLine, true ); qwtCurve->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, false ); qwtCurve->setLegendAttribute( QwtPlotCurve::LegendShowBrush, true ); const bool showLegend = !( unscaledCurve && skipUnscaledLegends ); qwtCurve->setItemAttribute( QwtPlotItem::Legend, showLegend ); qwtCurve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); if ( plotOnWhichYAxis == RIGHT_YAXIS ) { qwtCurve->setYAxis( { QwtAxis::YRight, 0 } ); shouldEnableRightYAxis = true; } qwtCurve->attach( plot ); RiuGuiTheme::styleQwtItem( qwtCurve ); // Add markers to indicate where SWAT and/or SGAS saturation intersects the respective curves // Note that if we're using log scale we must guard against non-positive values if ( swat != HUGE_VAL ) { if ( curve.ident == RigFlowDiagDefines::RelPermCurve::KRW || curve.ident == RigFlowDiagDefines::RelPermCurve::KROW || curve.ident == RigFlowDiagDefines::RelPermCurve::PCOW ) { addCurveConstSaturationIntersectionMarker( curve, swat, waterColor, plotOnWhichYAxis, plot, myPlotMarkers, &points, &axes ); } } if ( sgas != HUGE_VAL ) { if ( curve.ident == RigFlowDiagDefines::RelPermCurve::KRG || curve.ident == RigFlowDiagDefines::RelPermCurve::KROG || curve.ident == RigFlowDiagDefines::RelPermCurve::PCOG ) { const double effectiveSgas = applyOneMinusSgas ? 1.0 - sgas : sgas; addCurveConstSaturationIntersectionMarker( curve, effectiveSgas, gasColor, plotOnWhichYAxis, plot, myPlotMarkers, &points, &axes ); } } } if ( shouldEnableRightYAxis ) { plot->setAxesCount( QwtAxis::YRight, 1 ); plot->setAxisVisible( QwtAxis::YRight, true ); } else { plot->setAxesCount( QwtAxis::YRight, 0 ); plot->setAxisVisible( QwtAxis::YRight, false ); } addTransparentCurve( plot, points, axes, logScaleLeftAxis ); // Add vertical marker lines to indicate cell SWAT and/or SGAS saturations if ( swat != HUGE_VAL ) { addVerticalSaturationMarkerLine( swat, RiaResultNames::swat(), waterColor, plot, myPlotMarkers ); } if ( sgas != HUGE_VAL ) { const double effectiveSgas = applyOneMinusSgas ? 1.0 - sgas : sgas; const QString sgasLabel = applyOneMinusSgas ? "1-" + RiaResultNames::sgas() : RiaResultNames::sgas(); addVerticalSaturationMarkerLine( effectiveSgas, sgasLabel, gasColor, plot, myPlotMarkers ); } if ( logScaleLeftAxis ) { if ( !dynamic_cast( plot->axisScaleEngine( QwtAxis::YLeft ) ) ) { plot->setAxisScaleEngine( QwtAxis::YLeft, new QwtLogScaleEngine ); } } else { if ( !dynamic_cast( plot->axisScaleEngine( QwtAxis::YLeft ) ) ) { plot->setAxisScaleEngine( QwtAxis::YLeft, new QwtLinearScaleEngine ); } } if ( fixedXAxis ) { plot->setAxisScale( QwtAxis::XBottom, 0.0, 1.0 ); plot->setAxisAutoScale( QwtAxis::XBottom, false ); } else { plot->setAxisAutoScale( QwtAxis::XBottom, true ); } if ( fixedLeftYAxis ) { if ( logScaleLeftAxis ) { plot->setAxisScale( QwtAxis::YLeft, 1.0e-6, 1.0 ); } else { plot->setAxisScale( QwtAxis::YLeft, 0.0, 1.0 ); } plot->setAxisAutoScale( QwtAxis::YLeft, false ); } else { plot->setAxisAutoScale( QwtAxis::YLeft, true ); } if ( showScaled ) { QwtPlotCurve* curve = getLegendCurve( "Scaled", true /*scaled*/ ); curve->attach( plot ); } if ( showUnscaled ) { QwtPlotCurve* curve = getLegendCurve( "Unscaled", false /*scaled*/ ); curve->attach( plot ); } QString titleStr = "Relative Permeability"; if ( !cellReferenceText.isEmpty() ) { titleStr += ", " + cellReferenceText; } plot->setTitle( titleStr ); plot->setAxisTitle( QwtAxis::XBottom, determineXAxisTitleFromCurveCollection( curveArr, applyOneMinusSgas ) ); plot->setAxisTitle( QwtAxis::YLeft, "Kr" ); plot->setAxisTitle( QwtAxis::YRight, QString( "Pc [%1]" ).arg( RiaEclipseUnitTools::unitStringPressure( unitSystem ) ) ); plot->replot(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QwtPlotCurve* RiuRelativePermeabilityPlotPanel::getLegendCurve( QString title, bool scaled ) { QwtPlotCurve* curve = new QwtPlotCurve( title ); curve->setTitle( title ); curve->setStyle( QwtPlotCurve::Lines ); const QPen curvePen( QBrush(), 1, Qt::SolidLine ); curve->setPen( curvePen ); auto* curveSymbol = scaled ? new RiuQwtSymbol( RiuPlotCurveSymbol::SYMBOL_ELLIPSE ) : new RiuQwtSymbol( RiuPlotCurveSymbol::SYMBOL_CROSS ); curveSymbol->setSize( 6, 6 ); curveSymbol->setBrush( QBrush() ); curve->setSymbol( curveSymbol ); curve->setLegendAttribute( QwtPlotCurve::LegendShowLine, true ); curve->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, true ); curve->setLegendAttribute( QwtPlotCurve::LegendShowBrush, false ); curve->setItemAttribute( QwtPlotItem::Legend, true ); return curve; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RiuRelativePermeabilityPlotPanel::determineXAxisTitleFromCurveCollection( const std::vector& curveArr, bool plotOneMinusSgas ) { bool sawWater = false; bool sawGas = false; for ( const RigFlowDiagDefines::RelPermCurve& curve : curveArr ) { if ( curve.isWaterCurve() ) sawWater = true; if ( curve.isGasCurve() ) sawGas = true; } const QString gasAxisLabel = plotOneMinusSgas ? "1-SGAS" : "SGAS"; QString title = ""; if ( sawWater && sawGas ) title = "SWAT/" + gasAxisLabel; else if ( sawWater ) title = "SWAT"; else if ( sawGas ) title = gasAxisLabel; return title; } //-------------------------------------------------------------------------------------------------- /// Returns a copy of curveArr where the saturation values of the gas curves are transformed to 1-SGAS when /// applyTransform is true. Used both when plotting the curves and when generating the exported ascii data, so /// that the two always stay in sync. //-------------------------------------------------------------------------------------------------- std::vector RiuRelativePermeabilityPlotPanel::applyOneMinusSgasTransform( const std::vector& curveArr, bool applyTransform ) { if ( !applyTransform ) return curveArr; std::vector transformedCurves = curveArr; for ( RigFlowDiagDefines::RelPermCurve& curve : transformedCurves ) { if ( !curve.isGasCurve() ) continue; for ( double& saturationVal : curve.saturationVals ) { saturationVal = 1.0 - saturationVal; } // Keep x ascending, as required by the interpolation used for the intersection markers std::reverse( curve.saturationVals.begin(), curve.saturationVals.end() ); std::reverse( curve.yVals.begin(), curve.yVals.end() ); } return transformedCurves; } //-------------------------------------------------------------------------------------------------- /// Add a vertical labeled marker line at the specified saturation value //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::addVerticalSaturationMarkerLine( double saturationValue, QString label, QColor color, QwtPlot* plot, std::vector* myPlotMarkers ) { QwtPlotMarker* lineMarker = new QwtPlotMarker; lineMarker->setXValue( saturationValue ); lineMarker->setLineStyle( QwtPlotMarker::VLine ); lineMarker->setLinePen( QPen( color, 1, Qt::DotLine ) ); lineMarker->setLabel( label ); lineMarker->setLabelAlignment( Qt::AlignTop | Qt::AlignRight ); lineMarker->setLabelOrientation( Qt::Vertical ); lineMarker->attach( plot ); myPlotMarkers->push_back( lineMarker ); } //-------------------------------------------------------------------------------------------------- /// Add a marker at the intersection of the passed curve and the constant saturation value //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::addCurveConstSaturationIntersectionMarker( const RigFlowDiagDefines::RelPermCurve& curve, double saturationValue, QColor markerColor, WhichYAxis whichYAxis, QwtPlot* plot, std::vector* myPlotMarkers, std::vector* points, std::vector* axes ) { const double yVal = RiaInterpolationTools::linear( curve.saturationVals, curve.yVals, saturationValue ); if ( yVal != HUGE_VAL ) { QwtPlotMarker* pointMarker = new QwtPlotMarker; pointMarker->setValue( saturationValue, yVal ); QwtSymbol* symbol = new QwtSymbol( QwtSymbol::Ellipse ); symbol->setSize( 13, 13 ); symbol->setPen( QPen( markerColor, 2 ) ); symbol->setBrush( Qt::NoBrush ); pointMarker->setSymbol( symbol ); pointMarker->attach( plot ); if ( whichYAxis == RIGHT_YAXIS ) { pointMarker->setYAxis( QwtAxis::YRight ); } myPlotMarkers->push_back( pointMarker ); axes->push_back( whichYAxis ); points->push_back( QPointF( saturationValue, yVal ) ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RiuRelativePermeabilityPlotPanel::gatherUiSelectedCurves() const { std::vector selectedCurves; // Determine which curves to actually plot based on selection in GUI const bool showScaled = m_showScaledCheckBox->isChecked(); const bool showUnscaled = m_showUnscaledCheckBox->isChecked(); const bool showDrainage = m_showDrainageCheckBox->isChecked(); const bool showImbibition = m_showImbibitionCheckBox->isChecked(); for ( size_t i = 0; i < m_allCurvesArr.size(); i++ ) { const RigFlowDiagDefines::RelPermCurve::Ident curveIdent = m_allCurvesArr[i].ident; const RigFlowDiagDefines::RelPermCurve::EpsMode curveEpsMode = m_allCurvesArr[i].epsMode; const RigFlowDiagDefines::RelPermCurve::CurveSet curveSet = m_allCurvesArr[i].curveSet; // Check if curve type is selected const bool curveSetSelected = ( curveSet == RigFlowDiagDefines::RelPermCurve::DRAINAGE && showDrainage ) || ( curveSet == RigFlowDiagDefines::RelPermCurve::IMBIBITION && showImbibition ); if ( curveSetSelected && m_selectedCurvesButtonGroup->button( curveIdent ) && m_selectedCurvesButtonGroup->button( curveIdent )->isChecked() ) { if ( ( ( curveEpsMode == RigFlowDiagDefines::RelPermCurve::EPS_ON ) && showScaled ) || ( ( curveEpsMode == RigFlowDiagDefines::RelPermCurve::EPS_OFF ) && showUnscaled ) ) { selectedCurves.push_back( m_allCurvesArr[i] ); } } } return selectedCurves; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RiuRelativePermeabilityPlotPanel::asciiDataForUiSelectedCurves() const { std::vector selectedCurves = gatherUiSelectedCurves(); // Apply the same saturation transform as used when plotting, so the exported data matches the graph const bool applyOneMinusSgas = m_plotOneMinusSgasCheckBox->isChecked() && RiaPhaseTools::isTwoPhaseGasWater( m_availablePhases ); selectedCurves = applyOneMinusSgasTransform( selectedCurves, applyOneMinusSgas ); QString outTxt; // Info header outTxt += m_caseName + ", " + m_cellReferenceText + "\n"; // Column headers for ( size_t icurve = 0; icurve < selectedCurves.size(); icurve++ ) { if ( icurve > 0 ) outTxt += "\t"; const RigFlowDiagDefines::RelPermCurve& curve = selectedCurves[icurve]; if ( curve.isWaterCurve() ) outTxt += "SWAT"; else if ( curve.isGasCurve() ) outTxt += applyOneMinusSgas ? "1-SGAS" : "SGAS"; else outTxt += "Saturation"; outTxt += "\t"; outTxt += curve.name.c_str(); } // Table data size_t sampleIndex = 0; bool iterationContributedData = true; while ( iterationContributedData ) { iterationContributedData = false; QString lineStr = "\n"; for ( size_t icurve = 0; icurve < selectedCurves.size(); icurve++ ) { if ( icurve > 0 ) lineStr += "\t"; const RigFlowDiagDefines::RelPermCurve& curve = selectedCurves[icurve]; if ( sampleIndex < curve.saturationVals.size() && sampleIndex < curve.yVals.size() ) { lineStr += QString::number( curve.saturationVals[sampleIndex], 'g', 6 ); lineStr += "\t"; lineStr += QString::number( curve.yVals[sampleIndex], 'g', 6 ); iterationContributedData = true; } else { lineStr += "\t"; } } if ( iterationContributedData ) { outTxt += lineStr; } sampleIndex++; } return outTxt; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::updateCheckboxTexts() { // Update PCOG checkbox text to PCGW for GAS/WATER cases if ( auto button = m_selectedCurvesButtonGroup->button( RigFlowDiagDefines::RelPermCurve::PCOG ) ) { QString preferredName = RiaPhaseTools::getPreferredPcogName( m_availablePhases ); button->setText( preferredName ); } } //-------------------------------------------------------------------------------------------------- /// Only allow the "Plot 1-SGAS" option for two-phase gas-water systems //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::updatePlotOneMinusSgasCheckBoxState() { const bool enable = RiaPhaseTools::isTwoPhaseGasWater( m_availablePhases ); if ( !enable ) { m_plotOneMinusSgasCheckBox->setChecked( false ); } m_plotOneMinusSgasCheckBox->setEnabled( enable ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::contextMenuEvent( QContextMenuEvent* event ) { QMenu menu; const int curveCount = m_qwtPlot->itemList( QwtPlotItem::Rtti_PlotCurve ).count(); QAction* act = menu.addAction( "Show Plot Data", this, SLOT( slotCurrentPlotDataInTextDialog() ) ); act->setEnabled( curveCount > 0 ); menu.exec( event->globalPos() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::slotCurrentPlotDataInTextDialog() { QString outTxt = asciiDataForUiSelectedCurves(); RiuTextDialog* textDialog = new RiuTextDialog( this ); textDialog->setMinimumSize( 400, 600 ); textDialog->setWindowTitle( "Relative Permeability Data" ); textDialog->setText( outTxt ); textDialog->show(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::slotShowCurveSelectionWidgets( int state ) { bool setVisible = ( state != Qt::CheckState::Unchecked ); m_groupBox->setVisible( setVisible ); m_showUnscaledCheckBox->setVisible( setVisible ); m_showScaledCheckBox->setVisible( setVisible ); m_logarithmicScaleKrAxisCheckBox->setVisible( setVisible ); m_fixedXAxisCheckBox->setVisible( setVisible ); m_fixedLeftYAxisCheckBox->setVisible( setVisible ); m_plotOneMinusSgasCheckBox->setVisible( setVisible ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::slotButtonInButtonGroupClicked( int ) { plotUiSelectedCurves(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::slotSomeCheckBoxStateChanged( int ) { plotUiSelectedCurves(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::showEvent( QShowEvent* event ) { if ( m_plotUpdater != nullptr ) m_plotUpdater->doDelayedUpdate(); QWidget::showEvent( event ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::slotPickerPointChanged( const QPoint& pt ) { updateTrackerPlotMarkerAndLabelFromPicker(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::slotPickerActivated( bool on ) { updateTrackerPlotMarkerAndLabelFromPicker(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuRelativePermeabilityPlotPanel::updateTrackerPlotMarkerAndLabelFromPicker() { bool hasValidSamplePoint = false; QPointF samplePoint; QString curveTitle = ""; if ( m_qwtPicker && m_qwtPicker->isActive() ) { const QPoint trackerPos = m_qwtPicker->trackerPosition(); int pointSampleIdx = -1; const QwtPlotCurve* closestQwtCurve = closestCurveSample( trackerPos, &pointSampleIdx ); if ( closestQwtCurve && pointSampleIdx >= 0 ) { samplePoint = closestQwtCurve->sample( pointSampleIdx ); hasValidSamplePoint = true; curveTitle = closestQwtCurve->title().text(); } } m_trackerLabel = ""; bool needsReplot = false; if ( hasValidSamplePoint ) { if ( !m_trackerPlotMarker ) { m_trackerPlotMarker = new QwtPlotMarker; m_trackerPlotMarker->setTitle( QString( "TrackedPoint" ) ); QwtSymbol* symbol = new QwtSymbol( QwtSymbol::Ellipse ); symbol->setSize( 13, 13 ); symbol->setBrush( Qt::NoBrush ); m_trackerPlotMarker->setSymbol( symbol ); m_trackerPlotMarker->attach( m_qwtPlot ); RiuGuiTheme::styleQwtItem( m_trackerPlotMarker ); needsReplot = true; } if ( m_trackerPlotMarker->value() != samplePoint ) { m_trackerPlotMarker->setValue( samplePoint ); needsReplot = true; } m_trackerLabel = QString( "%1: (%2, %3)" ).arg( curveTitle ).arg( samplePoint.x(), 0, 'f', 3 ).arg( samplePoint.y(), 0, 'f', 3 ); } else { if ( m_trackerPlotMarker ) { m_trackerPlotMarker->detach(); delete m_trackerPlotMarker; m_trackerPlotMarker = nullptr; needsReplot = true; } } if ( needsReplot ) { m_qwtPlot->replot(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- const QwtPlotCurve* RiuRelativePermeabilityPlotPanel::closestCurveSample( const QPoint& cursorPosition, int* closestSampleIndex ) const { if ( !m_qwtPlot ) return nullptr; // Get all curves currently attached to the plot const QwtPlotItemList& plotItems = m_qwtPlot->itemList( QwtPlotItem::Rtti_PlotCurve ); double minDistSquared = std::numeric_limits::max(); const QwtPlotCurve* closestCurve = nullptr; int closestPtIdx = -1; for ( auto plotItem : plotItems ) { const QwtPlotCurve* candidateCurve = dynamic_cast( plotItem ); if ( !candidateCurve || !candidateCurve->isVisible() ) continue; for ( size_t ptIdx = 0; ptIdx < candidateCurve->dataSize(); ptIdx++ ) { QPointF samplePoint = candidateCurve->sample( static_cast( ptIdx ) ); QPoint screenPoint = QPoint( m_qwtPlot->transform( candidateCurve->xAxis(), samplePoint.x() ), m_qwtPlot->transform( candidateCurve->yAxis(), samplePoint.y() ) ); double distSquared = pow( screenPoint.x() - cursorPosition.x(), 2 ) + pow( screenPoint.y() - cursorPosition.y(), 2 ); if ( distSquared < minDistSquared ) { minDistSquared = distSquared; closestCurve = candidateCurve; closestPtIdx = static_cast( ptIdx ); } } } const double maxAllowedDistanceSquared = 20.0 * 20.0; // 20 pixels if ( minDistSquared <= maxAllowedDistanceSquared ) { if ( closestSampleIndex ) *closestSampleIndex = closestPtIdx; return closestCurve; } return nullptr; }