Grid Cross Plot: Add regression curves.

This commit is contained in:
Kristian Bendiksen 2023-06-16 11:15:56 +02:00
parent e1dacf7617
commit fdf4309d82
23 changed files with 1634 additions and 143 deletions

View File

@ -52,6 +52,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaNetworkTools.h
${CMAKE_CURRENT_LIST_DIR}/RiaOpenMPTools.h
${CMAKE_CURRENT_LIST_DIR}/RiaNumericalTools.h
${CMAKE_CURRENT_LIST_DIR}/RiaRegressionTextTools.h
)
set(SOURCE_GROUP_SOURCE_FILES
@ -101,6 +102,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaNetworkTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaOpenMPTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaNumericalTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaRegressionTextTools.cpp
)
list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})

View File

@ -0,0 +1,116 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiaRegressionTextTools.h"
#include "ExponentialRegression.hpp"
#include "LinearRegression.hpp"
#include "LogarithmicRegression.hpp"
#include "LogisticRegression.hpp"
#include "PolynomialRegression.hpp"
#include "PowerFitRegression.hpp"
#include <QStringList>
#include <cmath>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaRegressionTextTools::generateRegressionText( const regression::LinearRegression& reg )
{
QString sign = reg.intercept() < 0.0 ? "-" : "+";
return QString( "y = %1x %2 %3" ).arg( formatDouble( reg.slope() ) ).arg( sign ).arg( formatDouble( std::fabs( reg.intercept() ) ) ) +
QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaRegressionTextTools::generateRegressionText( const regression::PolynomialRegression& reg )
{
QString str = "y = ";
bool isFirst = true;
std::vector<double> coeffs = reg.coeffisients();
QStringList parts;
for ( size_t i = 0; i < coeffs.size(); i++ )
{
double coeff = coeffs[i];
// Skip zero coeffs
if ( coeff != 0.0 )
{
if ( coeff < 0.0 )
parts.append( "-" );
else if ( !isFirst )
parts.append( "+" );
if ( i == 0 )
{
parts.append( QString( "%1" ).arg( formatDouble( std::fabs( coeff ) ) ) );
}
else if ( i == 1 )
{
parts.append( QString( "%1x" ).arg( formatDouble( std::fabs( coeff ) ) ) );
}
else
{
parts.append( QString( " %1x<sup>%2</sup>" ).arg( formatDouble( std::fabs( coeff ) ) ).arg( i ) );
}
isFirst = false;
}
}
return str + parts.join( " " ) + QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaRegressionTextTools::generateRegressionText( const regression::PowerFitRegression& reg )
{
return QString( "y = %1 + x<sup>%2</sup>" ).arg( formatDouble( reg.scale() ) ).arg( formatDouble( reg.exponent() ) ) +
QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaRegressionTextTools::generateRegressionText( const regression::ExponentialRegression& reg )
{
return QString( "y = %1 * e<sup>%2x</sup>" ).arg( formatDouble( reg.a() ) ).arg( formatDouble( reg.b() ) ) +
QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaRegressionTextTools::generateRegressionText( const regression::LogarithmicRegression& reg )
{
return QString( "y = %1 + %2 * ln(x)" ).arg( formatDouble( reg.a() ) ).arg( formatDouble( reg.b() ) ) +
QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaRegressionTextTools::formatDouble( double v )
{
return QString::number( v, 'g', 2 );
}

View File

@ -0,0 +1,46 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <QString>
namespace regression
{
class ExponentialRegression;
class LinearRegression;
class LogarithmicRegression;
class PolynomialRegression;
class PowerFitRegression;
} // namespace regression
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
class RiaRegressionTextTools
{
public:
static QString generateRegressionText( const regression::LinearRegression& reg );
static QString generateRegressionText( const regression::PolynomialRegression& reg );
static QString generateRegressionText( const regression::PowerFitRegression& reg );
static QString generateRegressionText( const regression::LogarithmicRegression& reg );
static QString generateRegressionText( const regression::ExponentialRegression& reg );
private:
static QString formatDouble( double v );
}; // namespace RiaRegressionTextTools

View File

@ -132,6 +132,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RimPressureDepthData.h
${CMAKE_CURRENT_LIST_DIR}/RimEclipseResultDefinitionTools.h
${CMAKE_CURRENT_LIST_DIR}/RimResultSelectionUi.h
${CMAKE_CURRENT_LIST_DIR}/RimPlotRectAnnotation.h
)
set(SOURCE_GROUP_SOURCE_FILES
@ -263,6 +264,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimPressureDepthData.cpp
${CMAKE_CURRENT_LIST_DIR}/RimEclipseResultDefinitionTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RimResultSelectionUi.cpp
${CMAKE_CURRENT_LIST_DIR}/RimPlotRectAnnotation.cpp
)
if(RESINSIGHT_USE_QT_CHARTS)

View File

@ -2,6 +2,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlot.h
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlotCollection.h
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlotCurve.h
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlotRegressionCurve.h
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlotDataSet.h
${CMAKE_CURRENT_LIST_DIR}/RimSaturationPressurePlot.h
${CMAKE_CURRENT_LIST_DIR}/RimSaturationPressurePlotCollection.h
@ -11,6 +12,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlot.cpp
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlotCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlotCurve.cpp
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlotRegressionCurve.cpp
${CMAKE_CURRENT_LIST_DIR}/RimGridCrossPlotDataSet.cpp
${CMAKE_CURRENT_LIST_DIR}/RimSaturationPressurePlot.cpp
${CMAKE_CURRENT_LIST_DIR}/RimSaturationPressurePlotCollection.cpp

View File

@ -210,6 +210,7 @@ void RimGridCrossPlot::reattachAllCurves()
for ( auto dataSet : m_crossPlotDataSets )
{
dataSet->detachAllCurves();
dataSet->detachAllRegressionCurves();
if ( dataSet->isChecked() )
{
dataSet->setParentPlotNoReplot( m_plotWidget );
@ -336,6 +337,7 @@ void RimGridCrossPlot::detachAllCurves()
for ( auto dataSet : m_crossPlotDataSets() )
{
dataSet->detachAllCurves();
dataSet->detachAllRegressionCurves();
}
}
@ -1026,6 +1028,7 @@ void RimGridCrossPlot::cleanupBeforeClose()
for ( auto dataSet : m_crossPlotDataSets() )
{
dataSet->detachAllCurves();
dataSet->detachAllRegressionCurves();
}
if ( m_plotWidget )

View File

@ -19,6 +19,7 @@
#include "RimGridCrossPlotDataSet.h"
#include "RiaColorTables.h"
#include "RiaColorTools.h"
#include "RiaLogging.h"
#include "RifTextDataTableFormatter.h"
@ -30,12 +31,11 @@
#include "RigEclipseCaseData.h"
#include "RigEclipseCrossPlotDataExtractor.h"
#include "RigEclipseResultAddress.h"
#include "RigFormationNames.h"
#include "RigMainGrid.h"
#include "RiuDraggableOverlayFrame.h"
#include "RiuGridCrossQwtPlot.h"
#include "RiuGuiTheme.h"
#include "RiuPlotWidget.h"
#include "RiuScalarMapperLegendFrame.h"
@ -48,6 +48,7 @@
#include "RimFormationNames.h"
#include "RimGridCrossPlot.h"
#include "RimGridCrossPlotCurve.h"
#include "RimGridCrossPlotRegressionCurve.h"
#include "RimGridView.h"
#include "RimProject.h"
#include "RimRegularLegendConfig.h"
@ -63,9 +64,6 @@
#include "cafProgressInfo.h"
#include "cafTitledOverlayFrame.h"
#include "cvfScalarMapper.h"
#include "cvfqtUtils.h"
#include "qwt_plot.h"
#include <QString>
@ -129,6 +127,9 @@ RimGridCrossPlotDataSet::RimGridCrossPlotDataSet()
CAF_PDM_InitFieldNoDefault( &m_crossPlotCurves, "CrossPlotCurves", "Curves" );
m_crossPlotCurves.uiCapability()->setUiTreeHidden( true );
CAF_PDM_InitFieldNoDefault( &m_crossPlotRegressionCurves, "CrossPlotRegressionCurves", "Regression Curves" );
// m_crossPlotRegressionCurves.uiCapability()->setUiTreeHidden( true );
CAF_PDM_InitField( &m_useCustomColor, "UseCustomColor", false, "Use Custom Color" );
CAF_PDM_InitField( &m_customColor, "CustomColor", cvf::Color3f( cvf::Color3f::BLACK ), "Custom Color" );
@ -159,43 +160,42 @@ RimGridCrossPlotDataSet::~RimGridCrossPlotDataSet()
void RimGridCrossPlotDataSet::setCellFilterView( RimGridView* cellFilterView )
{
m_cellFilterView = cellFilterView;
auto eclipseView = dynamic_cast<RimEclipseView*>( m_cellFilterView() );
if ( !eclipseView ) return;
if ( eclipseView )
m_groupingProperty->setReservoirView( eclipseView );
RigEclipseResultAddress resAddr = eclipseView->cellResult()->eclipseResultAddress();
if ( resAddr.isValid() )
{
m_groupingProperty->setReservoirView( eclipseView );
RigEclipseResultAddress resAddr = eclipseView->cellResult()->eclipseResultAddress();
if ( resAddr.isValid() )
m_grouping = NO_GROUPING;
RimEclipseCase* eclipseCase = eclipseView->eclipseCase();
if ( eclipseCase )
{
m_grouping = NO_GROUPING;
m_case = eclipseCase;
m_xAxisProperty->setEclipseCase( eclipseCase );
m_yAxisProperty->setEclipseCase( eclipseCase );
m_groupingProperty->setEclipseCase( eclipseCase );
RimEclipseCase* eclipseCase = eclipseView->eclipseCase();
if ( eclipseCase )
if ( eclipseCase->activeFormationNames() )
{
m_case = eclipseCase;
m_xAxisProperty->setEclipseCase( eclipseCase );
m_yAxisProperty->setEclipseCase( eclipseCase );
m_groupingProperty->setEclipseCase( eclipseCase );
if ( eclipseCase->activeFormationNames() )
{
m_grouping = GROUP_BY_FORMATION;
m_groupingProperty->legendConfig()->setColorLegend(
RimRegularLegendConfig::mapToColorLegend( RimRegularLegendConfig::ColorRangesType::CATEGORY ) );
}
m_grouping = GROUP_BY_FORMATION;
m_groupingProperty->legendConfig()->setColorLegend(
RimRegularLegendConfig::mapToColorLegend( RimRegularLegendConfig::ColorRangesType::CATEGORY ) );
}
}
m_xAxisProperty->setResultType( resAddr.resultCatType() );
m_xAxisProperty->setResultVariable( resAddr.resultName() );
m_yAxisProperty->setResultType( RiaDefines::ResultCatType::STATIC_NATIVE );
m_yAxisProperty->setResultVariable( "DEPTH" );
m_timeStep = eclipseView->currentTimeStep();
m_xAxisProperty->setResultType( resAddr.resultCatType() );
m_xAxisProperty->setResultVariable( resAddr.resultName() );
m_yAxisProperty->setResultType( RiaDefines::ResultCatType::STATIC_NATIVE );
m_yAxisProperty->setResultVariable( "DEPTH" );
m_timeStep = eclipseView->currentTimeStep();
auto parentPlot = firstAncestorOrThisOfType<RimGridCrossPlot>();
if ( parentPlot )
{
parentPlot->setYAxisInverted( true );
}
auto parentPlot = firstAncestorOrThisOfType<RimGridCrossPlot>();
if ( parentPlot )
{
parentPlot->setYAxisInverted( true );
}
}
}
@ -217,6 +217,11 @@ void RimGridCrossPlotDataSet::setParentPlotNoReplot( RiuPlotWidget* parent )
{
curve->setParentPlotNoReplot( m_isChecked() ? parent : nullptr );
}
for ( auto& curve : m_crossPlotRegressionCurves() )
{
curve->setParentPlotNoReplot( m_isChecked() ? parent : nullptr );
}
}
//--------------------------------------------------------------------------------------------------
@ -246,7 +251,7 @@ QString RimGridCrossPlotDataSet::infoText() const
QStringList textLines;
textLines += QString( "<b>Case:</b> %1" ).arg( m_case()->caseUserDescription() );
textLines += QString( "<b>Parameters:</b>: %1 x %2" )
textLines += QString( "<b>Parameters:</b> %1 x %2" )
.arg( m_xAxisProperty->resultVariableUiShortName() )
.arg( m_yAxisProperty->resultVariableUiShortName() );
if ( m_timeStep != -1 )
@ -359,6 +364,17 @@ void RimGridCrossPlotDataSet::detachAllCurves()
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotDataSet::detachAllRegressionCurves()
{
for ( auto curve : m_crossPlotRegressionCurves() )
{
curve->detach();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@ -525,6 +541,20 @@ void RimGridCrossPlotDataSet::onLoadDataAndUpdate( bool updateParentPlot )
fillCurveDataInExistingCurves( result );
}
if ( m_crossPlotRegressionCurves.size() != m_groupedResults.size() )
{
destroyRegressionCurves();
}
if ( m_crossPlotRegressionCurves.empty() )
{
createRegressionCurves( result );
}
else
{
fillCurveDataInExistingRegressionCurves( result );
}
updateLegendIcons();
if ( updateParentPlot )
@ -591,6 +621,44 @@ void RimGridCrossPlotDataSet::assignCurveDataGroups( const RigEclipseCrossPlotRe
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Color3f RimGridCrossPlotDataSet::createCurveColor( bool useCustomColor, int colorIndex ) const
{
if ( useCustomColor )
{
return m_customColor();
}
else
{
const caf::ColorTable& colors = RiaColorTables::contrastCategoryPaletteColors();
return colors.cycledColor3f( colorIndex );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Color3f RimGridCrossPlotDataSet::createCurveColor( const std::vector<double>& tickValues, int groupIndex ) const
{
if ( tickValues.empty() )
{
return cvf::Color3f( legendConfig()->scalarMapper()->mapToColor( groupIndex ) );
}
else
{
if ( groupIndex < static_cast<int>( tickValues.size() ) )
{
return cvf::Color3f( legendConfig()->scalarMapper()->mapToColor( tickValues[groupIndex] ) );
}
else
{
return cvf::Color3f( legendConfig()->scalarMapper()->mapToColor( groupIndex ) );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@ -599,16 +667,7 @@ void RimGridCrossPlotDataSet::createCurves( const RigEclipseCrossPlotResult& res
if ( !groupingEnabled() )
{
RimGridCrossPlotCurve* curve = new RimGridCrossPlotCurve();
if ( m_useCustomColor )
{
curve->setColor( m_customColor() );
}
else
{
const caf::ColorTable& colors = RiaColorTables::contrastCategoryPaletteColors();
int colorIndex = indexInPlot();
curve->setColor( colors.cycledColor3f( colorIndex ) );
}
curve->setColor( createCurveColor( m_useCustomColor(), indexInPlot() ) );
curve->setSymbolEdgeColor( curve->color() );
curve->setGroupingInformation( indexInPlot(), 0 );
curve->setSamples( result.xValues, result.yValues );
@ -628,25 +687,12 @@ void RimGridCrossPlotDataSet::createCurves( const RigEclipseCrossPlotResult& res
// NB : Make sure iteration of curve and groups are syncronized with createCurves()
for ( auto it = m_groupedResults.rbegin(); it != m_groupedResults.rend(); ++it )
{
auto [groupIndex, values] = *it;
RimGridCrossPlotCurve* curve = new RimGridCrossPlotCurve();
curve->setGroupingInformation( indexInPlot(), it->first );
if ( groupingByCategoryResult() )
{
curve->setColor( cvf::Color3f( legendConfig()->scalarMapper()->mapToColor( it->first ) ) );
}
else
{
if ( it->first < static_cast<int>( tickValues.size() ) )
{
curve->setColor( cvf::Color3f( legendConfig()->scalarMapper()->mapToColor( tickValues[it->first] ) ) );
}
else
{
curve->setColor( cvf::Color3f( legendConfig()->scalarMapper()->mapToColor( it->first ) ) );
}
}
curve->setGroupingInformation( indexInPlot(), groupIndex );
curve->setColor( createCurveColor( tickValues, groupIndex ) );
curve->setSymbolEdgeColor( curve->color() );
curve->setSamples( it->second.xValues, it->second.yValues );
curve->setSamples( values.xValues, values.yValues );
curve->setShowInLegend( m_crossPlotCurves.empty() );
curve->setLegendEntryText( createAutoName() );
curve->setCurveAutoAppearance();
@ -688,6 +734,88 @@ void RimGridCrossPlotDataSet::fillCurveDataInExistingCurves( const RigEclipseCro
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotDataSet::createRegressionCurves( const RigEclipseCrossPlotResult& result )
{
auto symbolEdgeColor = RiaColorTools::fromQColorTo3f( RiuGuiTheme::getColorByVariableName( "auxilliaryCurveColor" ) );
if ( !groupingEnabled() )
{
RimGridCrossPlotRegressionCurve* curve = new RimGridCrossPlotRegressionCurve();
m_crossPlotRegressionCurves.push_back( curve );
curve->setColor( createCurveColor( m_useCustomColor(), indexInPlot() ) );
curve->setSymbolEdgeColor( symbolEdgeColor );
curve->setGroupingInformation( indexInPlot(), 0 );
curve->setRangeDefaults( result.xValues, result.yValues );
curve->setSamples( result.xValues, result.yValues );
curve->setCurveAutoAppearance();
curve->updateUiIconFromPlotSymbol();
}
else
{
std::vector<double> tickValues;
if ( !groupingByCategoryResult() )
{
legendConfig()->scalarMapper()->majorTickValues( &tickValues );
}
// NB : Make sure iteration of curve and groups are syncronized with createCurves()
for ( auto it = m_groupedResults.rbegin(); it != m_groupedResults.rend(); ++it )
{
auto [groupIndex, values] = *it;
bool isFirst = m_crossPlotRegressionCurves.empty();
RimGridCrossPlotRegressionCurve* curve = new RimGridCrossPlotRegressionCurve();
m_crossPlotRegressionCurves.push_back( curve );
curve->setGroupingInformation( indexInPlot(), groupIndex );
curve->setColor( createCurveColor( tickValues, groupIndex ) );
curve->setSymbolEdgeColor( symbolEdgeColor );
curve->setRangeDefaults( values.xValues, values.yValues );
curve->setSamples( values.xValues, values.yValues );
curve->setShowInLegend( isFirst );
curve->setLegendEntryText( createAutoName() );
curve->setCurveAutoAppearance();
curve->updateUiIconFromPlotSymbol();
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotDataSet::fillCurveDataInExistingRegressionCurves( const RigEclipseCrossPlotResult& result )
{
if ( !groupingEnabled() )
{
CVF_ASSERT( m_crossPlotRegressionCurves.size() == 1u );
RimGridCrossPlotRegressionCurve* curve = m_crossPlotRegressionCurves[0];
curve->setGroupingInformation( indexInPlot(), 0 );
curve->updateCurveVisibility();
curve->setSamples( result.xValues, result.yValues );
curve->updateCurveAppearance();
curve->updateUiIconFromPlotSymbol();
}
else
{
// NB : Make sure iteration of curve and groups are syncronized with fillCurveDataInExistingCurves()
auto curveIt = m_crossPlotRegressionCurves.begin();
auto groupIt = m_groupedResults.rbegin();
for ( ; curveIt != m_crossPlotRegressionCurves.end() && groupIt != m_groupedResults.rend(); ++curveIt, ++groupIt )
{
RimGridCrossPlotRegressionCurve* curve = *curveIt;
curve->setGroupingInformation( indexInPlot(), groupIt->first );
curve->updateCurveVisibility();
curve->setSamples( groupIt->second.xValues, groupIt->second.yValues );
curve->updateCurveAppearance();
curve->updateUiIconFromPlotSymbol();
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@ -697,6 +825,15 @@ void RimGridCrossPlotDataSet::destroyCurves()
m_crossPlotCurves.deleteChildren();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotDataSet::destroyRegressionCurves()
{
detachAllRegressionCurves();
m_crossPlotRegressionCurves.deleteChildren();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@ -882,6 +1019,7 @@ void RimGridCrossPlotDataSet::fieldChangedByUi( const caf::PdmFieldHandle* chang
}
destroyCurves();
destroyRegressionCurves();
loadDataAndUpdate( true );
}
}
@ -893,6 +1031,7 @@ void RimGridCrossPlotDataSet::fieldChangedByUi( const caf::PdmFieldHandle* chang
}
destroyCurves();
destroyRegressionCurves();
loadDataAndUpdate( true );
}
else if ( changedField == &m_grouping )
@ -914,6 +1053,7 @@ void RimGridCrossPlotDataSet::fieldChangedByUi( const caf::PdmFieldHandle* chang
}
destroyCurves();
destroyRegressionCurves();
loadDataAndUpdate( true );
}
else if ( changedField == &m_cellFilterView )
@ -936,12 +1076,17 @@ void RimGridCrossPlotDataSet::childFieldChangedByUi( const caf::PdmFieldHandle*
{
if ( changedChildField == &m_yAxisProperty )
{
if ( m_yAxisProperty->resultVariable() == "DEPTH" )
{
auto plot = firstAncestorOrThisOfTypeAsserted<RimGridCrossPlot>();
plot->setYAxisInverted( true );
triggerPlotNameUpdateAndReplot();
}
bool useInvertedYAxis = m_yAxisProperty->resultVariable() == "DEPTH";
auto plot = firstAncestorOrThisOfTypeAsserted<RimGridCrossPlot>();
plot->setYAxisInverted( useInvertedYAxis );
triggerPlotNameUpdateAndReplot();
}
if ( changedChildField == &m_xAxisProperty || changedChildField == &m_yAxisProperty )
{
destroyCurves();
destroyRegressionCurves();
loadDataAndUpdate( true );
}
else if ( changedChildField == &m_crossPlotCurves )
{
@ -1125,6 +1270,12 @@ void RimGridCrossPlotDataSet::swapAxisProperties( bool updatePlot )
m_xAxisProperty = yAxisProperties;
updateConnectedEditors();
for ( auto curve : m_crossPlotRegressionCurves )
{
curve->swapAxis();
}
loadDataAndUpdate( updatePlot );
}
@ -1288,6 +1439,32 @@ void RimGridCrossPlotDataSet::updateCurveNames( size_t dataSetIndex, size_t data
}
curve->updateCurveNameAndUpdatePlotLegendAndTitle();
}
for ( size_t i = 0; i < m_crossPlotRegressionCurves.size(); ++i )
{
QString dataSetName = createAutoName();
if ( dataSetName.isEmpty() )
{
if ( dataSetCount > 1u )
dataSetName = QString( "DataSet #%1" ).arg( dataSetIndex + 1 );
else
dataSetName = "Data Set";
}
auto curve = m_crossPlotRegressionCurves[i];
if ( groupingEnabled() )
{
QString curveGroupName = createGroupName( curve->groupIndex() );
curve->setCustomName( curveGroupName + " " + curve->getRegressionTypeString() );
curve->setLegendEntryText( dataSetName );
}
else
{
curve->setCustomName( dataSetName + " " + curve->getRegressionTypeString() );
}
curve->updateCurveNameAndUpdatePlotLegendAndTitle();
}
}
//--------------------------------------------------------------------------------------------------
@ -1313,29 +1490,26 @@ void RimGridCrossPlotDataSet::performAutoNameUpdate()
void RimGridCrossPlotDataSet::setDefaults()
{
RimProject* project = RimProject::current();
if ( project )
if ( project && !project->eclipseCases().empty() )
{
if ( !project->eclipseCases().empty() )
RimEclipseCase* eclipseCase = project->eclipseCases().front();
m_case = eclipseCase;
m_xAxisProperty->setEclipseCase( eclipseCase );
m_yAxisProperty->setEclipseCase( eclipseCase );
m_groupingProperty->setEclipseCase( eclipseCase );
m_xAxisProperty->setResultType( RiaDefines::ResultCatType::STATIC_NATIVE );
m_xAxisProperty->setResultVariable( "PORO" );
m_yAxisProperty->setResultType( RiaDefines::ResultCatType::STATIC_NATIVE );
m_yAxisProperty->setResultVariable( "PERMX" );
m_grouping = NO_GROUPING;
if ( eclipseCase->activeFormationNames() )
{
RimEclipseCase* eclipseCase = project->eclipseCases().front();
m_case = eclipseCase;
m_xAxisProperty->setEclipseCase( eclipseCase );
m_yAxisProperty->setEclipseCase( eclipseCase );
m_groupingProperty->setEclipseCase( eclipseCase );
m_xAxisProperty->setResultType( RiaDefines::ResultCatType::STATIC_NATIVE );
m_xAxisProperty->setResultVariable( "PORO" );
m_yAxisProperty->setResultType( RiaDefines::ResultCatType::STATIC_NATIVE );
m_yAxisProperty->setResultVariable( "PERMX" );
m_grouping = NO_GROUPING;
if ( eclipseCase->activeFormationNames() )
{
m_grouping = GROUP_BY_FORMATION;
m_groupingProperty->legendConfig()->setColorLegend(
RimRegularLegendConfig::mapToColorLegend( RimRegularLegendConfig::ColorRangesType::CATEGORY ) );
}
m_grouping = GROUP_BY_FORMATION;
m_groupingProperty->legendConfig()->setColorLegend(
RimRegularLegendConfig::mapToColorLegend( RimRegularLegendConfig::ColorRangesType::CATEGORY ) );
}
}
}
@ -1362,6 +1536,8 @@ void RimGridCrossPlotDataSet::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTr
uiTreeOrdering.add( curve );
}
uiTreeOrdering.add( &m_crossPlotRegressionCurves );
uiTreeOrdering.add( &m_plotCellFilterCollection );
uiTreeOrdering.skipRemainingChildren( true );

View File

@ -42,6 +42,7 @@
class RifTextDataTableFormatter;
class RimCase;
class RimGridCrossPlotCurve;
class RimGridCrossPlotRegressionCurve;
class RimGridView;
class RimEclipseCase;
class RimEclipseResultCase;
@ -110,6 +111,7 @@ public:
QString groupTitle() const;
QString groupParameter() const;
void detachAllCurves();
void detachAllRegressionCurves();
void cellFilterViewUpdated();
RimRegularLegendConfig* legendConfig() const;
@ -138,6 +140,8 @@ public:
void setCustomColor( const cvf::Color3f color );
void destroyCurves();
void destroyRegressionCurves();
size_t visibleCurveCount() const;
size_t sampleCount() const;
@ -149,6 +153,8 @@ protected:
void createCurves( const RigEclipseCrossPlotResult& result );
void fillCurveDataInExistingCurves( const RigEclipseCrossPlotResult& result );
QString createGroupName( size_t curveIndex ) const;
void createRegressionCurves( const RigEclipseCrossPlotResult& result );
void fillCurveDataInExistingRegressionCurves( const RigEclipseCrossPlotResult& result );
std::map<int, cvf::UByteArray> calculateCellVisibility( RimEclipseCase* eclipseCase ) const;
@ -168,6 +174,9 @@ protected:
bool hasMultipleTimeSteps() const;
void filterInvalidCurveValues( RigEclipseCrossPlotResult* result );
cvf::Color3f createCurveColor( bool useCustomColor, int colorIndex ) const;
cvf::Color3f createCurveColor( const std::vector<double>& tickValues, int groupIndex ) const;
private:
caf::PdmPtrField<RimCase*> m_case;
caf::PdmField<int> m_timeStep;
@ -179,7 +188,8 @@ private:
caf::PdmChildField<RimGridCrossPlotDataSetNameConfig*> m_nameConfig;
caf::PdmChildArrayField<RimGridCrossPlotCurve*> m_crossPlotCurves;
caf::PdmChildArrayField<RimGridCrossPlotCurve*> m_crossPlotCurves;
caf::PdmChildArrayField<RimGridCrossPlotRegressionCurve*> m_crossPlotRegressionCurves;
std::map<int, RigEclipseCrossPlotResult> m_groupedResults;

View File

@ -0,0 +1,546 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimGridCrossPlotRegressionCurve.h"
#include "RiaNumericalTools.h"
#include "RiaRegressionTextTools.h"
#include "RimGridCrossPlot.h"
#include "RimGridCrossPlotDataSet.h"
#include "RimPlotRectAnnotation.h"
#include "RimPlotRectAnnotation.h"
#include "RiuPlotCurve.h"
#include "RiuPlotWidget.h"
#include "ExponentialRegression.hpp"
#include "LinearRegression.hpp"
#include "LogarithmicRegression.hpp"
#include "PolynomialRegression.hpp"
#include "PowerFitRegression.hpp"
#include "cafPdmUiDoubleSliderEditor.h"
#include "cafPdmUiDoubleValueEditor.h"
#include "cafPdmUiLineEditor.h"
#include "cafPdmUiTextEditor.h"
#include "cvfMath.h"
CAF_PDM_SOURCE_INIT( RimGridCrossPlotRegressionCurve, "GridCrossPlotRegressionCurve" );
namespace caf
{
template <>
void caf::AppEnum<RimGridCrossPlotRegressionCurve::RegressionType>::setUp()
{
addItem( RimGridCrossPlotRegressionCurve::RegressionType::LINEAR, "LINEAR", "Linear" );
addItem( RimGridCrossPlotRegressionCurve::RegressionType::POLYNOMIAL, "POLYNOMIAL", "Polynomial" );
addItem( RimGridCrossPlotRegressionCurve::RegressionType::POWER_FIT, "POWER_FIT", "Power Fit" );
addItem( RimGridCrossPlotRegressionCurve::RegressionType::EXPONENTIAL, "EXPONENTIAL", "Exponential" );
addItem( RimGridCrossPlotRegressionCurve::RegressionType::LOGARITHMIC, "LOGARITHMIC", "Logarithmic" );
setDefault( RimGridCrossPlotRegressionCurve::RegressionType::LINEAR );
}
}; // namespace caf
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimGridCrossPlotRegressionCurve::RimGridCrossPlotRegressionCurve()
: m_dataSetIndex( 0 )
, m_groupIndex( 0 )
{
CAF_PDM_InitObject( "Cross Plot Regression Curve", ":/WellLogCurve16x16.png" );
CAF_PDM_InitFieldNoDefault( &m_regressionType, "RegressionType", "Type" );
CAF_PDM_InitFieldNoDefault( &m_minExtrapolationRangeX, "MinExtrapolationRangeX", "Min" );
m_minExtrapolationRangeX.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_maxExtrapolationRangeX, "MaxExtrapolationRangeX", "Max" );
m_maxExtrapolationRangeX.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_polynomialDegree, "PolynomialDegree", 3, "Degree" );
CAF_PDM_InitFieldNoDefault( &m_minRangeX, "MinRangeX", "Min X" );
m_minRangeX.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_maxRangeX, "MaxRangeX", "Max X" );
m_maxRangeX.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_minRangeY, "MinRangeY", "Min Y" );
m_minRangeY.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_maxRangeY, "MaxRangeY", "Max Y" );
m_maxRangeY.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_showDataSelectionInPlot, "ShowDataSelectionInPlot", false, "Show In Plot" );
CAF_PDM_InitFieldNoDefault( &m_expressionText, "ExpressionText", "Expression" );
m_expressionText.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() );
m_expressionText.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN );
m_expressionText.uiCapability()->setUiReadOnly( true );
m_expressionText.xmlCapability()->disableIO();
setLineStyle( RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_SOLID );
setSymbol( RiuPlotCurveSymbol::SYMBOL_RECT );
setSymbolSize( 6 );
setZOrder( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_REGRESSION_CURVE ) );
m_dataRangeX = { cvf::UNDEFINED_DOUBLE, cvf::UNDEFINED_DOUBLE };
m_dataRangeY = { cvf::UNDEFINED_DOUBLE, cvf::UNDEFINED_DOUBLE };
auto rectAnnotation = new RimPlotRectAnnotation;
rectAnnotation->setName( "Data Selection" );
m_rectAnnotations.push_back( rectAnnotation );
m_rectAnnotations.uiCapability()->setUiTreeHidden( true );
m_rectAnnotations.uiCapability()->setUiTreeChildrenHidden( true );
setCheckState( false );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::setGroupingInformation( int dataSetIndex, int groupIndex )
{
m_dataSetIndex = dataSetIndex;
m_groupIndex = groupIndex;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::setSamples( const std::vector<double>& xValues, const std::vector<double>& yValues )
{
CVF_ASSERT( xValues.size() == yValues.size() );
if ( xValues.empty() || yValues.empty() || !m_plotCurve ) return;
auto [minX, maxX] = minmax_element( xValues.begin(), xValues.end() );
auto [minY, maxY] = minmax_element( yValues.begin(), yValues.end() );
m_dataRangeX = { *minX, *maxX };
m_dataRangeY = { *minY, *maxY };
auto filterValues = []( const std::vector<double>& x, const std::vector<double>& y, double minX, double maxX, double minY, double maxY )
{
std::vector<double> filteredX;
std::vector<double> filteredY;
for ( size_t i = 0; i < x.size(); i++ )
{
if ( x[i] >= minX && x[i] <= maxX && y[i] >= minY && y[i] <= maxY )
{
filteredX.push_back( x[i] );
filteredY.push_back( y[i] );
}
}
return std::make_pair( filteredX, filteredY );
};
auto [filteredX, filteredY] = filterValues( xValues, yValues, m_minRangeX, m_maxRangeX, m_minRangeY, m_maxRangeY );
if ( filteredX.empty() || filteredX.size() != filteredY.size() ) return;
auto subsampleValues = []( double min, double max, int numSamples )
{
double step = ( max - min ) / numSamples;
std::vector<double> subSampledRange( numSamples );
for ( int i = 0; i < numSamples; i++ )
subSampledRange[i] = min + step * i;
subSampledRange.push_back( max );
return subSampledRange;
};
std::vector<double> subsampledXValues = subsampleValues( m_minExtrapolationRangeX, m_maxExtrapolationRangeX, 50 );
auto [outputXValues, outputYValues, regressionText] = calculateRegression( m_regressionType(), filteredX, filteredY, subsampledXValues );
m_expressionText = regressionText;
bool useLogarithmicScale = false;
m_plotCurve->setSamplesFromXValuesAndYValues( outputXValues, outputYValues, useLogarithmicScale );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::setRangeDefaults( const std::vector<double>& xValues, const std::vector<double>& yValues )
{
CVF_ASSERT( xValues.size() == yValues.size() );
if ( xValues.empty() || yValues.empty() ) return;
auto [minX, maxX] = minmax_element( xValues.begin(), xValues.end() );
auto [minY, maxY] = minmax_element( yValues.begin(), yValues.end() );
m_minRangeX = *minX;
m_maxRangeX = *maxX;
m_minRangeY = *minY;
m_maxRangeY = *maxY;
m_maxExtrapolationRangeX = *maxX;
m_minExtrapolationRangeX = *minX;
m_dataRangeX = { *minX, *maxX };
m_dataRangeY = { *minY, *maxY };
updateRectAnnotation();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::swapAxis()
{
std::swap( m_minRangeX, m_minRangeY );
std::swap( m_maxRangeX, m_maxRangeY );
m_maxExtrapolationRangeX = m_maxRangeX;
m_minExtrapolationRangeX = m_minRangeX;
std::swap( m_dataRangeX, m_dataRangeY );
updateRectAnnotation();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::setCurveAutoAppearance()
{
updateCurveAppearance();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RimGridCrossPlotRegressionCurve::groupIndex() const
{
return m_groupIndex;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RimGridCrossPlotRegressionCurve::sampleCount() const
{
return m_plotCurve ? m_plotCurve->numSamples() : 0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::determineLegendIcon()
{
if ( !m_plotCurve ) return;
auto plot = firstAncestorOrThisOfTypeAsserted<RimGridCrossPlot>();
int fontSize = plot->legendFontSize();
m_plotCurve->setLegendIconSize( QSize( fontSize, fontSize ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::setBlackAndWhiteLegendIcons( bool blackAndWhite )
{
if ( m_plotCurve )
{
m_plotCurve->setBlackAndWhiteLegendIcon( blackAndWhite );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::updateZoomInParentPlot()
{
auto plot = firstAncestorOrThisOfTypeAsserted<RimGridCrossPlot>();
plot->calculateZoomRangeAndUpdateQwt();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimGridCrossPlotRegressionCurve::createCurveAutoName()
{
return m_curveName;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimGridCrossPlotRegressionCurve::getRegressionTypeString() const
{
return m_regressionType().uiText() + " Regression";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::onLoadDataAndUpdate( bool updateParentPlot )
{
if ( updateParentPlot )
{
m_parentPlot->replot();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
caf::PdmUiGroup* regressionCurveGroup = uiOrdering.addNewGroup( "Regression Analysis" );
regressionCurveGroup->add( &m_regressionType );
if ( m_regressionType == RegressionType::POLYNOMIAL )
{
regressionCurveGroup->add( &m_polynomialDegree );
}
regressionCurveGroup->add( &m_expressionText );
caf::PdmUiGroup* dataSelectionGroup = uiOrdering.addNewGroup( "Data Selection" );
dataSelectionGroup->add( &m_minRangeX );
dataSelectionGroup->add( &m_maxRangeX );
dataSelectionGroup->add( &m_minRangeY );
dataSelectionGroup->add( &m_maxRangeY );
dataSelectionGroup->add( &m_showDataSelectionInPlot );
caf::PdmUiGroup* forecastingGroup = uiOrdering.addNewGroup( "Extrapolation" );
forecastingGroup->add( &m_minExtrapolationRangeX );
forecastingGroup->add( &m_maxExtrapolationRangeX );
caf::PdmUiGroup* appearanceGroup = uiOrdering.addNewGroup( "Appearance" );
RimPlotCurve::appearanceUiOrdering( *appearanceGroup );
uiOrdering.skipRemainingFields( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::defineObjectEditorAttribute( QString uiConfigName, caf::PdmUiEditorAttribute* attribute )
{
// Implement an empty method to avoid the base class implementation in RimPlotCurve
// The color tag is not used for Grid Cross Plot Curves
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::defineEditorAttribute( const caf::PdmFieldHandle* field,
QString uiConfigName,
caf::PdmUiEditorAttribute* attribute )
{
if ( field == &m_polynomialDegree )
{
if ( auto* lineEditorAttr = dynamic_cast<caf::PdmUiLineEditorAttribute*>( attribute ) )
{
// Polynomial degree should be a positive number.
lineEditorAttr->validator = new QIntValidator( 1, 50, nullptr );
}
}
else if ( field == &m_minRangeX || field == &m_maxRangeX )
{
if ( auto* myAttr = dynamic_cast<caf::PdmUiDoubleSliderEditorAttribute*>( attribute ) )
{
auto [min, max] = m_dataRangeX;
myAttr->m_minimum = RiaNumericalTools::roundToNumSignificantDigits( min, 2 );
myAttr->m_maximum = RiaNumericalTools::roundToNumSignificantDigits( max, 2 );
myAttr->m_decimals = 3;
}
}
else if ( field == &m_minRangeY || field == &m_maxRangeY )
{
if ( auto* myAttr = dynamic_cast<caf::PdmUiDoubleSliderEditorAttribute*>( attribute ) )
{
auto [min, max] = m_dataRangeY;
myAttr->m_minimum = RiaNumericalTools::roundToNumSignificantDigits( min, 2 );
myAttr->m_maximum = RiaNumericalTools::roundToNumSignificantDigits( max, 2 );
myAttr->m_decimals = 3;
}
}
else if ( field == &m_minExtrapolationRangeX || field == &m_maxExtrapolationRangeX )
{
auto doubleAttr = dynamic_cast<caf::PdmUiDoubleValueEditorAttribute*>( attribute );
if ( doubleAttr )
{
doubleAttr->m_decimals = 2;
doubleAttr->m_numberFormat = caf::PdmUiDoubleValueEditorAttribute::NumberFormat::FIXED;
}
}
else if ( field == &m_expressionText )
{
auto myAttr = dynamic_cast<caf::PdmUiTextEditorAttribute*>( attribute );
if ( myAttr )
{
myAttr->wrapMode = caf::PdmUiTextEditorAttribute::NoWrap;
myAttr->textMode = caf::PdmUiTextEditorAttribute::HTML;
QFont font;
auto pointSize = font.pointSize();
font.setPointSize( pointSize + 2 );
myAttr->font = font;
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
if ( &m_showCurve == changedField )
{
// RimPlotCurve::fieldChangedByUi always replot, and this is usually unnecessary except for visibility changes
RimPlotCurve::fieldChangedByUi( changedField, oldValue, newValue );
}
auto enforceRange = []( const caf::PdmFieldHandle* changedField, caf::PdmField<double>& minRange, caf::PdmField<double>& maxRange )
{
if ( &minRange == changedField && minRange > maxRange ) maxRange = minRange;
if ( &maxRange == changedField && maxRange < minRange ) minRange = maxRange;
};
enforceRange( changedField, m_minRangeX, m_maxRangeX );
enforceRange( changedField, m_minRangeY, m_maxRangeY );
if ( &m_minRangeX == changedField || &m_maxRangeX == changedField || &m_minRangeY == changedField || &m_maxRangeY == changedField ||
&m_showDataSelectionInPlot == changedField )
{
updateRectAnnotation();
}
if ( &m_minRangeX == changedField || &m_maxRangeX == changedField || &m_minRangeY == changedField || &m_maxRangeY == changedField ||
&m_minExtrapolationRangeX == changedField || &m_maxExtrapolationRangeX == changedField || &m_regressionType == changedField ||
&m_polynomialDegree == changedField || &m_showDataSelectionInPlot == changedField )
{
auto dataSet = firstAncestorOrThisOfTypeAsserted<RimGridCrossPlotDataSet>();
dataSet->loadDataAndUpdate( true );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::tuple<std::vector<double>, std::vector<double>, QString>
RimGridCrossPlotRegressionCurve::calculateRegression( RimGridCrossPlotRegressionCurve::RegressionType regressionType,
const std::vector<double>& xValues,
const std::vector<double>& yValues,
const std::vector<double>& outputXValues ) const
{
if ( regressionType == RegressionType::LINEAR )
{
regression::LinearRegression linearRegression;
linearRegression.fit( xValues, yValues );
std::vector<double> predictedValues = linearRegression.predict( outputXValues );
return { outputXValues, predictedValues, RiaRegressionTextTools::generateRegressionText( linearRegression ) };
}
else if ( m_regressionType == RegressionType::POLYNOMIAL )
{
regression::PolynomialRegression polynomialRegression;
polynomialRegression.fit( xValues, yValues, m_polynomialDegree );
std::vector<double> predictedValues = polynomialRegression.predict( outputXValues );
return { outputXValues, predictedValues, RiaRegressionTextTools::generateRegressionText( polynomialRegression ) };
}
else if ( m_regressionType == RegressionType::POWER_FIT )
{
auto [filteredTimeSteps, filteredValues] = getPositiveValues( xValues, yValues );
if ( filteredTimeSteps.empty() || filteredValues.empty() ) return {};
regression::PowerFitRegression powerFitRegression;
powerFitRegression.fit( filteredTimeSteps, filteredValues );
std::vector<double> predictedValues = powerFitRegression.predict( outputXValues );
return { outputXValues, predictedValues, RiaRegressionTextTools::generateRegressionText( powerFitRegression ) };
}
else if ( m_regressionType == RegressionType::EXPONENTIAL )
{
auto [filteredTimeSteps, filteredValues] = getPositiveValues( xValues, yValues );
if ( filteredTimeSteps.empty() || filteredValues.empty() ) return {};
regression::ExponentialRegression exponentialRegression;
exponentialRegression.fit( filteredTimeSteps, filteredValues );
std::vector<double> predictedValues = exponentialRegression.predict( outputXValues );
return { outputXValues, predictedValues, RiaRegressionTextTools::generateRegressionText( exponentialRegression ) };
}
else if ( m_regressionType == RegressionType::LOGARITHMIC )
{
auto [filteredTimeSteps, filteredValues] = getPositiveValues( xValues, yValues );
if ( filteredTimeSteps.empty() || filteredValues.empty() ) return {};
regression::LogarithmicRegression logarithmicRegression;
logarithmicRegression.fit( filteredTimeSteps, filteredValues );
std::vector<double> predictedValues = logarithmicRegression.predict( outputXValues );
return { outputXValues, predictedValues, RiaRegressionTextTools::generateRegressionText( logarithmicRegression ) };
}
return { {}, {}, "" };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<std::vector<double>, std::vector<double>> RimGridCrossPlotRegressionCurve::getPositiveValues( const std::vector<double>& xValues,
const std::vector<double>& yValues )
{
std::vector<double> filteredXValues;
std::vector<double> filteredYValues;
for ( size_t i = 0; i < xValues.size(); i++ )
{
if ( xValues[i] > 0.0 && yValues[i] > 0.0 )
{
filteredXValues.push_back( xValues[i] );
filteredYValues.push_back( yValues[i] );
}
}
return std::make_pair( filteredXValues, filteredYValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGridCrossPlotRegressionCurve::updateRectAnnotation()
{
if ( !m_rectAnnotations.empty() )
{
RimPlotRectAnnotation* annotation = m_rectAnnotations[0];
annotation->setRangeX( m_minRangeX, m_maxRangeX );
annotation->setRangeY( m_minRangeY, m_maxRangeY );
annotation->setColor( m_curveAppearance->color() );
annotation->setCheckState( m_showDataSelectionInPlot() );
auto dataSet = firstAncestorOrThisOfType<RimGridCrossPlotDataSet>();
if ( dataSet )
{
QString textLines;
textLines += QString( "<b>Case:</b> %1<br>" ).arg( dataSet->caseNameString() );
textLines += QString( "<b>%1:</b> %2 - %3<br>" ).arg( dataSet->xAxisName() ).arg( m_minRangeX ).arg( m_maxRangeX );
textLines += QString( "<b>%1:</b> %2 - %3<br>" ).arg( dataSet->yAxisName() ).arg( m_minRangeY ).arg( m_maxRangeY );
annotation->setText( textLines );
}
}
}

View File

@ -0,0 +1,93 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimPlotCurve.h"
//==================================================================================================
///
///
//==================================================================================================
class RimGridCrossPlotRegressionCurve : public RimPlotCurve
{
CAF_PDM_HEADER_INIT;
public:
enum class RegressionType
{
LINEAR,
POLYNOMIAL,
POWER_FIT,
EXPONENTIAL,
LOGARITHMIC,
LOGISTIC
};
RimGridCrossPlotRegressionCurve();
~RimGridCrossPlotRegressionCurve() override = default;
void setGroupingInformation( int dataSetIndex, int groupIndex );
void setSamples( const std::vector<double>& xValues, const std::vector<double>& yValues );
void setRangeDefaults( const std::vector<double>& xValues, const std::vector<double>& yValues );
void setCurveAutoAppearance();
int groupIndex() const;
size_t sampleCount() const;
void determineLegendIcon();
void setBlackAndWhiteLegendIcons( bool blackAndWhite );
QString getRegressionTypeString() const;
void swapAxis();
protected:
void updateZoomInParentPlot() override;
QString createCurveAutoName() override;
void onLoadDataAndUpdate( bool updateParentPlot ) override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
void defineObjectEditorAttribute( QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override;
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override;
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void updateRectAnnotation();
std::tuple<std::vector<double>, std::vector<double>, QString>
calculateRegression( RimGridCrossPlotRegressionCurve::RegressionType regressionType,
const std::vector<double>& xValues,
const std::vector<double>& yValues,
const std::vector<double>& outputXValues ) const;
static std::pair<std::vector<double>, std::vector<double>> getPositiveValues( const std::vector<double>& xValues,
const std::vector<double>& yValues );
private:
caf::PdmField<caf::AppEnum<RegressionType>> m_regressionType;
caf::PdmField<double> m_minRangeX;
caf::PdmField<double> m_maxRangeX;
caf::PdmField<double> m_minRangeY;
caf::PdmField<double> m_maxRangeY;
caf::PdmField<bool> m_showDataSelectionInPlot;
caf::PdmField<int> m_polynomialDegree;
caf::PdmField<QString> m_expressionText;
caf::PdmField<double> m_minExtrapolationRangeX;
caf::PdmField<double> m_maxExtrapolationRangeX;
std::pair<double, double> m_dataRangeX;
std::pair<double, double> m_dataRangeY;
int m_dataSetIndex;
int m_groupIndex;
};

View File

@ -26,6 +26,7 @@
#include "RimEnsembleCurveSet.h"
#include "RimEnsembleCurveSetCollection.h"
#include "RimNameConfig.h"
#include "RimPlotRectAnnotation.h"
#include "RimProject.h"
#include "RimSummaryCrossPlot.h"
#include "RimSummaryCurve.h"
@ -135,6 +136,8 @@ RimPlotCurve::RimPlotCurve()
m_additionalDataSources.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() );
m_additionalDataSources.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN );
CAF_PDM_InitFieldNoDefault( &m_rectAnnotations, "RectAnnotation", "Plot Rect Annotations" );
m_plotCurve = nullptr;
m_parentPlot = nullptr;
}
@ -1137,6 +1140,14 @@ std::vector<RimPlotCurve*> RimPlotCurve::additionalDataSources() const
return m_additionalDataSources.ptrReferencedObjectsByType();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimPlotRectAnnotation*> RimPlotCurve::rectAnnotations() const
{
return m_rectAnnotations.childrenByType();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------

View File

@ -28,6 +28,7 @@
#include "RiuQwtPlotCurveDefines.h"
#include "RiuQwtSymbol.h"
#include "cafPdmChildArrayField.h"
#include "cafPdmChildField.h"
#include "cafPdmField.h"
#include "cafPdmFieldCvfColor.h"
@ -39,6 +40,7 @@
class RiuPlotCurve;
class RiuPlotWidget;
class RimPlotRectAnnotation;
//==================================================================================================
///
@ -139,6 +141,8 @@ public:
bool isSameCurve( const RiuPlotCurve* plotCurve ) const;
void deletePlotCurve();
std::vector<RimPlotRectAnnotation*> rectAnnotations() const;
protected:
virtual QString createCurveAutoName() = 0;
@ -208,7 +212,8 @@ protected:
caf::PdmChildField<RimPlotCurveAppearance*> m_curveAppearance;
caf::PdmPtrArrayField<RimPlotCurve*> m_additionalDataSources;
caf::PdmPtrArrayField<RimPlotCurve*> m_additionalDataSources;
caf::PdmChildArrayField<RimPlotRectAnnotation*> m_rectAnnotations;
QPointer<RiuPlotWidget> m_parentPlot;
RiuPlotCurve* m_plotCurve;

View File

@ -0,0 +1,129 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimPlotRectAnnotation.h"
#include "RimPlot.h"
#include "RimTools.h"
#include "cafPdmUiDoubleSliderEditor.h"
#include <cmath>
CAF_PDM_SOURCE_INIT( RimPlotRectAnnotation, "RimPlotRectAnnotation" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimPlotRectAnnotation::RimPlotRectAnnotation()
{
CAF_PDM_InitObject( "Plot Rect Annotation", ":/LeftAxis16x16.png" );
CAF_PDM_InitFieldNoDefault( &m_minX, "MinX", "Min X" );
CAF_PDM_InitFieldNoDefault( &m_maxX, "MaxX", "Max X" );
CAF_PDM_InitFieldNoDefault( &m_minY, "MinY", "Min Y" );
CAF_PDM_InitFieldNoDefault( &m_maxY, "MaxY", "Max Y" );
CAF_PDM_InitField( &m_color, "Color", cvf::Color3f( cvf::Color3f::LIGHT_GRAY ), "Color" );
CAF_PDM_InitField( &m_transparency, "Transparency", 0.1, "Transparency" );
m_transparency.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_text, "Text", "Text" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimPlotRectAnnotation::setRangeX( double minX, double maxX )
{
m_minX = minX;
m_maxX = maxX;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimPlotRectAnnotation::setRangeY( double minY, double maxY )
{
m_minY = minY;
m_maxY = maxY;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<double, double> RimPlotRectAnnotation::rangeX() const
{
return { m_minX, m_maxX };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<double, double> RimPlotRectAnnotation::rangeY() const
{
return { m_minY, m_maxY };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimPlotRectAnnotation::setColor( const cvf::Color3f& color )
{
m_color = color;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Color3f RimPlotRectAnnotation::color() const
{
return m_color;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimPlotRectAnnotation::setTransparency( double transparency )
{
m_transparency = transparency;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimPlotRectAnnotation::transparency() const
{
return m_transparency;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimPlotRectAnnotation::setText( const QString& text )
{
m_text = text;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimPlotRectAnnotation::text() const
{
return m_text;
}

View File

@ -0,0 +1,62 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimCheckableNamedObject.h"
#include "cafPdmField.h"
#include "cafPdmFieldCvfColor.h"
#include <QString>
//==================================================================================================
///
///
//==================================================================================================
class RimPlotRectAnnotation : public RimCheckableNamedObject
{
CAF_PDM_HEADER_INIT;
public:
RimPlotRectAnnotation();
void setRangeX( double minX, double maxX );
void setRangeY( double minY, double maxY );
std::pair<double, double> rangeX() const;
std::pair<double, double> rangeY() const;
void setColor( const cvf::Color3f& color );
cvf::Color3f color() const;
void setText( const QString& text );
QString text() const;
void setTransparency( double transparency );
double transparency() const;
protected:
caf::PdmField<double> m_minX;
caf::PdmField<double> m_maxX;
caf::PdmField<double> m_minY;
caf::PdmField<double> m_maxY;
caf::PdmField<cvf::Color3f> m_color;
caf::PdmField<double> m_transparency;
caf::PdmField<QString> m_text;
};

View File

@ -19,6 +19,7 @@
#include "RimSummaryRegressionAnalysisCurve.h"
#include "RiaQDateTimeTools.h"
#include "RiaRegressionTextTools.h"
#include "RiaTimeTTools.h"
#include "RimSummaryPlot.h"
@ -366,22 +367,12 @@ QString RimSummaryRegressionAnalysisCurve::curveExportDescription( const RifEcli
return RimSummaryCurve::curveExportDescription() + "." + m_regressionType().uiText() + "_Regression";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimSummaryRegressionAnalysisCurve::formatDouble( double v )
{
return QString::number( v, 'g', 2 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::LinearRegression& reg )
{
QString sign = reg.intercept() < 0.0 ? "-" : "+";
return QString( "y = %1x %2 %3" ).arg( formatDouble( reg.slope() ) ).arg( sign ).arg( formatDouble( std::fabs( reg.intercept() ) ) ) +
QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() ) + getXAxisUnitText();
return RiaRegressionTextTools::generateRegressionText( reg ) + getXAxisUnitText();
}
//--------------------------------------------------------------------------------------------------
@ -389,40 +380,7 @@ QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regress
//--------------------------------------------------------------------------------------------------
QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::PolynomialRegression& reg )
{
QString str = "y = ";
bool isFirst = true;
std::vector<double> coeffs = reg.coeffisients();
QStringList parts;
for ( size_t i = 0; i < coeffs.size(); i++ )
{
double coeff = coeffs[i];
// Skip zero coeffs
if ( coeff != 0.0 )
{
if ( coeff < 0.0 )
parts.append( "-" );
else if ( !isFirst )
parts.append( "+" );
if ( i == 0 )
{
parts.append( QString( "%1" ).arg( formatDouble( std::fabs( coeff ) ) ) );
}
else if ( i == 1 )
{
parts.append( QString( "%1x" ).arg( formatDouble( std::fabs( coeff ) ) ) );
}
else
{
parts.append( QString( " %1x<sup>%2</sup>" ).arg( formatDouble( std::fabs( coeff ) ) ).arg( i ) );
}
isFirst = false;
}
}
return str + parts.join( " " ) + QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() ) + getXAxisUnitText();
return RiaRegressionTextTools::generateRegressionText( reg ) + getXAxisUnitText();
}
//--------------------------------------------------------------------------------------------------
@ -430,8 +388,7 @@ QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regress
//--------------------------------------------------------------------------------------------------
QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::PowerFitRegression& reg )
{
return QString( "y = %1 + x<sup>%2</sup>" ).arg( formatDouble( reg.scale() ) ).arg( formatDouble( reg.exponent() ) ) +
QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() ) + getXAxisUnitText();
return RiaRegressionTextTools::generateRegressionText( reg ) + getXAxisUnitText();
}
//--------------------------------------------------------------------------------------------------
@ -439,8 +396,7 @@ QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regress
//--------------------------------------------------------------------------------------------------
QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::ExponentialRegression& reg )
{
return QString( "y = %1 * e<sup>%2x</sup>" ).arg( formatDouble( reg.a() ) ).arg( formatDouble( reg.b() ) ) +
QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() ) + getXAxisUnitText();
return RiaRegressionTextTools::generateRegressionText( reg ) + getXAxisUnitText();
}
//--------------------------------------------------------------------------------------------------
@ -448,8 +404,7 @@ QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regress
//--------------------------------------------------------------------------------------------------
QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::LogarithmicRegression& reg )
{
return QString( "y = %1 + %2 * ln(x)" ).arg( formatDouble( reg.a() ) ).arg( formatDouble( reg.b() ) ) +
QString( "<br>R<sup>2</sup> = %1" ).arg( reg.r2() ) + getXAxisUnitText();
return RiaRegressionTextTools::generateRegressionText( reg ) + getXAxisUnitText();
}
//--------------------------------------------------------------------------------------------------

View File

@ -110,7 +110,6 @@ private:
static QString generateRegressionText( const regression::LogarithmicRegression& reg );
static QString generateRegressionText( const regression::ExponentialRegression& reg );
static QString formatDouble( double v );
static QString getXAxisUnitText();
static void appendTimeSteps( std::vector<time_t>& destinationTimeSteps, const std::set<QDateTime>& sourceTimeSteps );

View File

@ -108,6 +108,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RiuQwtDateScaleWrapper.h
${CMAKE_CURRENT_LIST_DIR}/RiuMatrixPlotWidget.h
${CMAKE_CURRENT_LIST_DIR}/RiuMenuBarBuildTools.h
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotRectAnnotation.h
)
set(SOURCE_GROUP_SOURCE_FILES
@ -217,6 +218,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiuQwtDateScaleWrapper.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuMatrixPlotWidget.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuMenuBarBuildTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotRectAnnotation.cpp
)
if(RESINSIGHT_USE_QT_CHARTS)

View File

@ -19,9 +19,14 @@
#include "RiuQwtPlotCurve.h"
#include "RiaColorTools.h"
#include "RiaCurveDataTools.h"
#include "RiaImageTools.h"
#include "RimPlotCurve.h"
#include "RimPlotRectAnnotation.h"
#include "RiuQwtPlotRectAnnotation.h"
#include "RiuQwtPlotTools.h"
#include "RiuQwtPlotWidget.h"
#include "RiuQwtSymbol.h"
@ -32,9 +37,11 @@
#include "qwt_painter.h"
#include "qwt_plot_curve.h"
#include "qwt_plot_intervalcurve.h"
#include "qwt_plot_marker.h"
#include "qwt_point_mapper.h"
#include "qwt_scale_map.h"
#include "qwt_symbol.h"
#include "qwt_text.h"
#include "qwt_weeding_curve_fitter.h"
#include <cmath>
@ -59,6 +66,8 @@ RiuQwtPlotCurve::RiuQwtPlotCurve( RimPlotCurve* ownerRimCurve, const QString& ti
m_qwtCurveErrorBars->setSymbol( new QwtIntervalSymbol( QwtIntervalSymbol::Bar ) );
m_qwtCurveErrorBars->setItemAttribute( QwtPlotItem::Legend, false );
m_qwtCurveErrorBars->setZ( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_ERROR_BARS ) );
m_qwtPlotRectAnnotation = new RiuQwtPlotRectAnnotation;
}
//--------------------------------------------------------------------------------------------------
@ -70,6 +79,9 @@ RiuQwtPlotCurve::~RiuQwtPlotCurve()
delete m_qwtCurveErrorBars;
m_qwtCurveErrorBars = nullptr;
delete m_qwtPlotRectAnnotation;
m_qwtPlotRectAnnotation = nullptr;
}
//--------------------------------------------------------------------------------------------------
@ -322,6 +334,30 @@ void RiuQwtPlotCurve::attachToPlot( RiuPlotWidget* plotWidget )
{
m_qwtCurveErrorBars->attach( m_plotWidget->qwtPlot() );
}
if ( m_ownerRimCurve )
{
auto rectAnnotations = m_ownerRimCurve->rectAnnotations();
if ( !rectAnnotations.empty() )
{
auto [minX, maxX] = rectAnnotations[0]->rangeX();
auto [minY, maxY] = rectAnnotations[0]->rangeY();
m_qwtPlotRectAnnotation->setInterval( minX, maxX, minY, maxY );
QColor brushColor( RiaColorTools::toQColor( rectAnnotations[0]->color() ) );
brushColor.setAlphaF( rectAnnotations[0]->transparency() );
QBrush brush( brushColor );
m_qwtPlotRectAnnotation->setBrush( brush );
QColor penColor( RiaColorTools::toQColor( rectAnnotations[0]->color() ) );
QPen pen( penColor );
m_qwtPlotRectAnnotation->setPen( pen );
m_qwtPlotRectAnnotation->setText( rectAnnotations[0]->text() );
m_qwtPlotRectAnnotation->attach( m_plotWidget->qwtPlot() );
m_qwtPlotRectAnnotation->setVisible( rectAnnotations[0]->isChecked() );
}
}
}
//--------------------------------------------------------------------------------------------------
@ -331,6 +367,7 @@ void RiuQwtPlotCurve::detach()
{
QwtPlotCurve::detach();
m_qwtCurveErrorBars->detach();
m_qwtPlotRectAnnotation->detach();
}
//--------------------------------------------------------------------------------------------------

View File

@ -26,6 +26,7 @@
class QwtPlotIntervalCurve;
class RiuQwtPlotWidget;
class RiuQwtPlotRectAnnotation;
//==================================================================================================
//
@ -99,5 +100,7 @@ protected:
QwtPlotIntervalCurve* m_qwtCurveErrorBars;
bool m_showErrorBars;
RiuQwtPlotRectAnnotation* m_qwtPlotRectAnnotation;
QPointer<RiuQwtPlotWidget> m_plotWidget;
};

View File

@ -67,6 +67,13 @@ int RiuQwtPlotCurveDefines::zDepthForIndex( ZIndex index )
case RiuQwtPlotCurveDefines::ZIndex::Z_SINGLE_CURVE_OBSERVED:
return 500;
break;
case RiuQwtPlotCurveDefines::ZIndex::Z_PLOT_RECT_ANNOTATION:
return 600;
break;
case RiuQwtPlotCurveDefines::ZIndex::Z_REGRESSION_CURVE:
return 700;
break;
default:
break;
}

View File

@ -45,7 +45,9 @@ enum class ZIndex
Z_ENSEMBLE_STAT_CURVE,
Z_SINGLE_CURVE_NON_OBSERVED,
Z_ERROR_BARS,
Z_SINGLE_CURVE_OBSERVED
Z_SINGLE_CURVE_OBSERVED,
Z_PLOT_RECT_ANNOTATION,
Z_REGRESSION_CURVE
};
int zDepthForIndex( ZIndex index );

View File

@ -0,0 +1,214 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiuQwtPlotRectAnnotation.h"
#include "RiuGuiTheme.h"
#include "RiuQwtPlotCurveDefines.h"
#include "qwt_painter.h"
#include "qwt_plot_marker.h"
#include "qwt_scale_map.h"
#include "qwt_symbol.h"
#include "qwt_text.h"
#include <QPainter>
#include <QRect>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuQwtPlotRectAnnotation::RiuQwtPlotRectAnnotation()
: QwtPlotItem( QwtText( "PlotRectAnnotation" ) )
{
setItemAttribute( QwtPlotItem::AutoScale, false );
setItemAttribute( QwtPlotItem::Legend, false );
QColor c( Qt::darkGray );
c.setAlpha( 50 );
m_brush = QBrush( c );
m_pen = QPen();
m_pen.setColor( Qt::black );
m_pen.setWidth( 2 );
setZ( RiuQwtPlotCurveDefines::zDepthForIndex( RiuQwtPlotCurveDefines::ZIndex::Z_PLOT_RECT_ANNOTATION ) );
m_textLabel = std::make_unique<QwtPlotMarker>();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQwtPlotRectAnnotation::setPen( const QPen& pen )
{
m_pen = pen;
itemChanged();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const QPen& RiuQwtPlotRectAnnotation::pen() const
{
return m_pen;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQwtPlotRectAnnotation::setBrush( const QBrush& brush )
{
m_brush = brush;
itemChanged();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const QBrush& RiuQwtPlotRectAnnotation::brush() const
{
return m_brush;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQwtPlotRectAnnotation::setInterval( double minX, double maxX, double minY, double maxY )
{
m_intervalX.setMinValue( minX );
m_intervalX.setMaxValue( maxX );
m_intervalY.setMinValue( minY );
m_intervalY.setMaxValue( maxY );
itemChanged();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQwtPlotRectAnnotation::draw( QPainter* painter, const QwtScaleMap& xMap, const QwtScaleMap& yMap, const QRectF& canvasRect ) const
{
if ( !m_intervalX.isValid() || !m_intervalY.isValid() ) return;
QPen pen = m_pen;
pen.setCapStyle( Qt::FlatCap );
double x1 = xMap.transform( m_intervalX.minValue() );
double x2 = xMap.transform( m_intervalX.maxValue() );
double y1 = yMap.transform( m_intervalY.minValue() );
double y2 = yMap.transform( m_intervalY.maxValue() );
const bool doAlign = QwtPainter::roundingAlignment( painter );
if ( doAlign )
{
x1 = qRound( x1 );
x2 = qRound( x2 );
y1 = qRound( y1 );
y2 = qRound( y2 );
}
QRectF r( x1, y1, x2 - x1, y2 - y1 );
r = r.normalized();
if ( m_brush.style() != Qt::NoBrush && x1 != x2 && y1 != y2 )
{
QwtPainter::fillRect( painter, r, m_brush );
}
if ( m_pen.style() != Qt::NoPen )
{
painter->setPen( m_pen );
QwtPainter::drawRect( painter, r );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QRectF RiuQwtPlotRectAnnotation::boundingRect() const
{
QRectF br = QwtPlotItem::boundingRect();
if ( m_intervalX.isValid() && m_intervalY.isValid() )
{
br.setTop( m_intervalY.minValue() );
br.setBottom( m_intervalY.maxValue() );
br.setLeft( m_intervalX.minValue() );
br.setRight( m_intervalX.maxValue() );
}
return br;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiuQwtPlotRectAnnotation::rtti() const
{
return 5001;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQwtPlotRectAnnotation::setText( const QString& text )
{
QwtText label( text );
auto textColor = RiuGuiTheme::getColorByVariableName( "textColor" );
label.setColor( textColor );
label.setRenderFlags( Qt::AlignLeft );
m_textLabel->setLabel( label );
m_textLabel->setLabelAlignment( Qt::AlignRight | Qt::AlignBottom );
m_textLabel->setLineStyle( QwtPlotMarker::NoLine );
m_textLabel->setSymbol( new QwtSymbol( QwtSymbol::NoSymbol ) );
m_textLabel->setValue( m_intervalX.minValue(), m_intervalY.maxValue() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQwtPlotRectAnnotation::attach( QwtPlot* plot )
{
QwtPlotItem::attach( plot );
m_textLabel->attach( plot );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQwtPlotRectAnnotation::detach()
{
QwtPlotItem::detach();
m_textLabel->detach();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQwtPlotRectAnnotation::setVisible( bool isVisible )
{
QwtPlotItem::setVisible( isVisible );
m_textLabel->setVisible( isVisible );
}

View File

@ -0,0 +1,69 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "qwt_interval.h"
#include "qwt_plot_item.h"
#include <QBrush>
#include <QPen>
#include <memory>
class QRectF;
class QPainter;
class QwtPlotMarker;
//==================================================================================================
/// Rectangular plot item for annotation areas of a plot.
///
//==================================================================================================
class RiuQwtPlotRectAnnotation : public QwtPlotItem
{
public:
RiuQwtPlotRectAnnotation();
~RiuQwtPlotRectAnnotation() override = default;
void setInterval( double minX, double maxX, double minY, double maxY );
void setPen( const QPen& );
const QPen& pen() const;
void setBrush( const QBrush& );
const QBrush& brush() const;
void setText( const QString& text );
int rtti() const override;
void draw( QPainter* painter, const QwtScaleMap& xMap, const QwtScaleMap& yMap, const QRectF& canvasRect ) const override;
QRectF boundingRect() const override;
void attach( QwtPlot* plot );
void detach();
void setVisible( bool isVisible ) override;
protected:
QwtInterval m_intervalX;
QwtInterval m_intervalY;
QPen m_pen;
QBrush m_brush;
QString m_text;
std::unique_ptr<QwtPlotMarker> m_textLabel;
};