mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 22:50:25 -05:00
#14880: RFT correlation plot formation workflow
* Add Add Well Picks button to well log formation settings * Expose formation zone colors and shading alpha from RFT track * Average observed pressures per formation with full min/max range * Show formation-colored observed pressure and depth filter in RFT correlation plots * Default new RFT correlation plots to formation filter and ensemble parameter coloring * Move Qwt code out of RFT correlation Rim classes * Align RFT correlation depth filter bars and click zones with displayed track formations
This commit is contained in:
1 parent
4126d25ea0
commit
5812826db6
22 files changed
+1179
-122
No files matched your search
@@ -37,6 +37,18 @@ CAF_CMD_SOURCE_INIT( RicImportWellFormationsFeature, "RicImportWellFormationsFea
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RicImportWellFormationsFeature::onActionTriggered( bool isChecked )
|
||||
{
|
||||
std::vector<RimWellFormationsFile*> importedFiles = importFilesWithDialog();
|
||||
if ( !importedFiles.empty() )
|
||||
{
|
||||
Riu3DMainWindowTools::selectAsCurrentItem( importedFiles.back() );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<RimWellFormationsFile*> RicImportWellFormationsFeature::importFilesWithDialog()
|
||||
{
|
||||
RiaApplication* app = RiaApplication::instance();
|
||||
QString defaultDir = app->lastUsedDialogDirectory( "WELLPATHFORMATIONS_DIR" );
|
||||
@@ -45,24 +57,21 @@ void RicImportWellFormationsFeature::onActionTriggered( bool isChecked )
|
||||
defaultDir,
|
||||
"Well Formations (*.csv);;All Files (*.*)" );
|
||||
|
||||
if ( filePaths.empty() ) return;
|
||||
if ( filePaths.empty() ) return {};
|
||||
|
||||
// Remember the path for next time
|
||||
app->setLastUsedDialogDirectory( "WELLPATHFORMATIONS_DIR", QFileInfo( filePaths.last() ).absolutePath() );
|
||||
|
||||
RimProject* project = RimProject::current();
|
||||
if ( !project || !project->activeOilField() ) return;
|
||||
if ( !project || !project->activeOilField() ) return {};
|
||||
|
||||
RimWellFormationsCollection* collection = project->activeOilField()->wellFormationsCollection();
|
||||
if ( !collection ) return;
|
||||
if ( !collection ) return {};
|
||||
|
||||
std::vector<RimWellFormationsFile*> importedFiles = collection->importFiles( filePaths );
|
||||
collection->updateConnectedEditors();
|
||||
|
||||
if ( !importedFiles.empty() )
|
||||
{
|
||||
Riu3DMainWindowTools::selectAsCurrentItem( importedFiles.back() );
|
||||
}
|
||||
return importedFiles;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
|
||||
#include "cafCmdFeature.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
class RimWellFormationsFile;
|
||||
|
||||
//==================================================================================================
|
||||
/// Imports one or more well formations files (e.g. FMU formations.csv) into the project-level
|
||||
/// RimWellFormationsCollection, without linking the resulting entries to any well path or file set.
|
||||
@@ -28,6 +32,10 @@ class RicImportWellFormationsFeature : public caf::CmdFeature
|
||||
{
|
||||
CAF_CMD_HEADER_INIT;
|
||||
|
||||
public:
|
||||
// Asks the user for files and imports them; returns the imported files without changing selection
|
||||
static std::vector<RimWellFormationsFile*> importFilesWithDialog();
|
||||
|
||||
protected:
|
||||
void onActionTriggered( bool isChecked ) override;
|
||||
void setupActionLook( QAction* actionToSetup ) override;
|
||||
|
||||
@@ -150,6 +150,30 @@ void RimCurveSetAppearance::setEnsembleParameter( const QString& ensembleParamet
|
||||
m_ensembleParameter = ensembleParameter;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimCurveSetAppearance::setColorByEnsembleParameter( const QString& ensembleParameter )
|
||||
{
|
||||
m_colorMode = RimEnsembleCurveSetColorManager::ColorMode::BY_ENSEMBLE_PARAM;
|
||||
m_ensembleParameter = ensembleParameter;
|
||||
updateEnsembleParameterLegend( m_ensembleLegendConfig );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Changes the ensemble parameter only if curves are colored by ensemble parameter. Returns true
|
||||
/// if the parameter was changed.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimCurveSetAppearance::syncEnsembleParameter( const QString& ensembleParameter )
|
||||
{
|
||||
if ( m_colorMode() != RimEnsembleCurveSetColorManager::ColorMode::BY_ENSEMBLE_PARAM || m_ensembleParameter() == ensembleParameter )
|
||||
return false;
|
||||
|
||||
m_ensembleParameter = ensembleParameter;
|
||||
updateEnsembleParameterLegend( m_ensembleLegendConfig );
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -61,6 +61,8 @@ public:
|
||||
|
||||
void setColorMode( ColorModeEnum colorMode );
|
||||
void setEnsembleParameter( const QString& ensembleParameter );
|
||||
bool syncEnsembleParameter( const QString& ensembleParameter );
|
||||
void setColorByEnsembleParameter( const QString& ensembleParameter );
|
||||
RimRegularLegendConfig* legendConfig() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -20,15 +20,20 @@
|
||||
|
||||
#include "Summary/RiaSummaryCurveDefinition.h"
|
||||
|
||||
#include "Formations/RimWellFormationsCollection.h"
|
||||
#include "Formations/RimWellFormationsFile.h"
|
||||
#include "RimCorrelationMatrixPlot.h"
|
||||
#include "RimCorrelationPlot.h"
|
||||
#include "RimCorrelationReportPlot.h"
|
||||
#include "RimOilField.h"
|
||||
#include "RimParameterResultCrossPlot.h"
|
||||
#include "RimParameterRftCrossPlot.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimRftCorrelationReportPlot.h"
|
||||
#include "RimRftCrossPlotTools.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
#include "RimSummaryEnsembleTools.h"
|
||||
#include "RimWellRftEnsembleCurveSet.h"
|
||||
#include "RimWellRftPlot.h"
|
||||
|
||||
CAF_PDM_SOURCE_INIT( RimCorrelationPlotCollection, "CorrelationPlotCollection" );
|
||||
@@ -223,12 +228,32 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR
|
||||
if ( param.isNumeric() )
|
||||
{
|
||||
report->crossPlot()->setEnsembleParameter( param.name );
|
||||
|
||||
if ( auto* curveSet = report->wellRftPlot()->findEnsembleCurveSet( ensemble ) )
|
||||
{
|
||||
curveSet->setColorByEnsembleParameter( param.name );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
report->crossPlot()->setWellName( source->simWellOrWellPathName() );
|
||||
|
||||
// Default to filtering by formation when a formations file has data for the well
|
||||
if ( auto* project = RimProject::current();
|
||||
project && project->activeOilField() && project->activeOilField()->wellFormationsCollection() )
|
||||
{
|
||||
for ( RimWellFormationsFile* file : project->activeOilField()->wellFormationsCollection()->wellFormationsFiles() )
|
||||
{
|
||||
if ( file->formationsForWell( source->simWellOrWellPathName() ) )
|
||||
{
|
||||
report->crossPlot()->setWellFormations( file );
|
||||
report->crossPlot()->setFilterMode( RimRftCrossPlotTools::DepthFilterMode::ZONES );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto timeSteps = source->selectedTimeSteps();
|
||||
if ( !timeSteps.empty() ) report->crossPlot()->setTimeStep( timeSteps.front() );
|
||||
}
|
||||
|
||||
+116
-81
@@ -33,6 +33,7 @@
|
||||
#include "RimEclipseResultCase.h"
|
||||
#include "RimOilField.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimRftCorrelationReportPlot.h"
|
||||
#include "RimRftCrossPlotTools.h"
|
||||
#include "RimSummaryCase.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
@@ -40,24 +41,13 @@
|
||||
|
||||
#include "RiuContextMenuLauncher.h"
|
||||
#include "RiuDockWidgetTools.h"
|
||||
#include "RiuPlotCurve.h"
|
||||
#include "RiuQwtCurveSelectorFilter.h"
|
||||
#include "RiuQwtPlotCurve.h"
|
||||
#include "RiuQwtPlotWidget.h"
|
||||
#include "RiuQwtSymbol.h"
|
||||
#include "RiuRftCorrelationPlotTools.h"
|
||||
|
||||
#include "cafPdmPointer.h"
|
||||
#include "cafPdmUiComboBoxEditor.h"
|
||||
#include "cafPdmUiTreeSelectionEditor.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_map.h"
|
||||
#include "qwt_text.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPaintDevice>
|
||||
|
||||
@@ -183,6 +173,30 @@ void RimParameterRftCrossPlot::setWellFormations( RimWellFormationsFile* wellFor
|
||||
m_wellFormations = wellFormationsFile;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimParameterRftCrossPlot::setFilterMode( RimRftCrossPlotTools::DepthFilterMode filterMode )
|
||||
{
|
||||
m_filterMode = filterMode;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimParameterRftCrossPlot::setSelectedZones( const std::vector<QString>& zones )
|
||||
{
|
||||
m_selectedZones = zones;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimParameterRftCrossPlot::setZoneColors( const std::map<QString, QColor>& zoneColors )
|
||||
{
|
||||
m_zoneColors = zoneColors;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -423,7 +437,7 @@ QString RimParameterRftCrossPlot::asciiDataForPlotExport() const
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimParameterRftCrossPlot::detachAllCurves()
|
||||
{
|
||||
if ( m_plotWidget ) m_plotWidget->qwtPlot()->detachItems();
|
||||
if ( m_plotWidget ) RiuRftCorrelationPlotTools::detachAllItems( m_plotWidget );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -458,54 +472,6 @@ void RimParameterRftCrossPlot::doRenderWindowContent( QPaintDevice* paintDevice
|
||||
if ( m_plotWidget ) m_plotWidget->render( paintDevice );
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class CurveTracker : public QwtPlotPicker
|
||||
{
|
||||
public:
|
||||
CurveTracker( QwtPlot* plot )
|
||||
: QwtPlotPicker( plot->canvas() )
|
||||
{
|
||||
setStateMachine( new QwtPickerTrackerMachine() );
|
||||
setRubberBand( QwtPicker::NoRubberBand );
|
||||
setTrackerMode( QwtPicker::AlwaysOn );
|
||||
}
|
||||
|
||||
protected:
|
||||
QwtText trackerText( const QPoint& pos ) const override
|
||||
{
|
||||
double minDistance = std::numeric_limits<double>::max();
|
||||
QString closestCurveLabel;
|
||||
|
||||
for ( QwtPlotItem* item : plot()->itemList() )
|
||||
{
|
||||
if ( item->rtti() == QwtPlotItem::Rtti_PlotCurve )
|
||||
{
|
||||
auto curve = static_cast<QwtPlotCurve*>( item );
|
||||
double distance = std::numeric_limits<double>::max();
|
||||
curve->closestPoint( pos, &distance );
|
||||
|
||||
if ( distance < minDistance )
|
||||
{
|
||||
minDistance = distance;
|
||||
closestCurveLabel = curve->title().text();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( minDistance < 20.0 )
|
||||
{
|
||||
QwtText text( closestCurveLabel );
|
||||
text.setBackgroundBrush( QBrush( Qt::white ) );
|
||||
text.setColor( Qt::black );
|
||||
return text;
|
||||
}
|
||||
return QwtText();
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -520,12 +486,12 @@ RiuPlotWidget* RimParameterRftCrossPlot::doCreatePlotViewWidget( QWidget* parent
|
||||
|
||||
if ( m_plotWidget )
|
||||
{
|
||||
new CurveTracker( m_plotWidget->qwtPlot() );
|
||||
RiuRftCorrelationPlotTools::installCurveTracker( m_plotWidget );
|
||||
|
||||
caf::PdmPointer<RimParameterRftCrossPlot> self( this );
|
||||
new RiuQwtCurveSelectorFilter( m_plotWidget->qwtPlot(),
|
||||
[self]( const QPoint& pos ) -> const caf::PdmUiItem*
|
||||
{ return self ? self->findClosestCase( pos ) : nullptr; } );
|
||||
RiuRftCorrelationPlotTools::installCurveSelector( m_plotWidget,
|
||||
[self]( const QPoint& pos ) -> const caf::PdmUiItem*
|
||||
{ return self ? self->findClosestCase( pos ) : nullptr; } );
|
||||
}
|
||||
|
||||
return m_plotWidget;
|
||||
@@ -549,15 +515,17 @@ void RimParameterRftCrossPlot::onLoadDataAndUpdate()
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
/// Adds the data source, parameter and depth filter groups; used by the parent report plot, not shown
|
||||
/// in the cross plot's own editor.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimParameterRftCrossPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
||||
void RimParameterRftCrossPlot::appendDataAndFilterUiOrdering( caf::PdmUiOrdering& uiOrdering )
|
||||
{
|
||||
auto* dataGroup = uiOrdering.addNewGroup( "Data Source" );
|
||||
dataGroup->add( &m_ensemble );
|
||||
dataGroup->add( &m_wellName );
|
||||
dataGroup->add( &m_selectedTimeStep );
|
||||
dataGroup->add( &m_eclipseCase );
|
||||
dataGroup->add( &m_ensembleParameter );
|
||||
|
||||
auto* depthGroup =
|
||||
uiOrdering.addNewGroup( QString( "Depth Range (%1)" ).arg( RimRftCrossPlotTools::depthTypeAbbreviation( m_depthType() ) ) );
|
||||
@@ -575,9 +543,14 @@ void RimParameterRftCrossPlot::defineUiOrdering( QString uiConfigName, caf::PdmU
|
||||
m_selectedZones.uiCapability()->setUiHidden( !useZones || !m_wellFormations() );
|
||||
|
||||
auto* crossPlotGroup = uiOrdering.addNewGroup( "Cross Plot Parameter" );
|
||||
crossPlotGroup->add( &m_ensembleParameter );
|
||||
crossPlotGroup->add( &m_samplingMode );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimParameterRftCrossPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
||||
{
|
||||
auto* plotGroup = uiOrdering.addNewGroup( "Plot Settings" );
|
||||
plotGroup->setCollapsedByDefault();
|
||||
plotGroup->add( &m_useAutoPlotTitle );
|
||||
@@ -622,7 +595,15 @@ void RimParameterRftCrossPlot::fieldChangedByUi( const caf::PdmFieldHandle* chan
|
||||
}
|
||||
|
||||
RimPlot::fieldChangedByUi( changedField, oldValue, newValue );
|
||||
loadDataAndUpdate();
|
||||
|
||||
// The parent report plot syncs the other sub plots and the track zone highlight from this plot
|
||||
if ( auto* reportPlot = firstAncestorOrThisOfType<RimRftCorrelationReportPlot>() )
|
||||
{
|
||||
if ( changedField == &m_ensembleParameter ) reportPlot->syncEnsembleParameterToRftCurves();
|
||||
reportPlot->loadDataAndUpdate();
|
||||
}
|
||||
else
|
||||
loadDataAndUpdate();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -690,7 +671,7 @@ QList<caf::PdmOptionItemInfo> RimParameterRftCrossPlot::calculateValueOptions( c
|
||||
if ( project && project->activeOilField() && project->activeOilField()->wellFormationsCollection() )
|
||||
{
|
||||
for ( RimWellFormationsFile* file : project->activeOilField()->wellFormationsCollection()->wellFormationsFiles() )
|
||||
options.push_back( caf::PdmOptionItemInfo( file->shortName(), file ) );
|
||||
options.push_back( caf::PdmOptionItemInfo( file->shortName(), file, false, file->uiCapability()->uiIconProvider() ) );
|
||||
}
|
||||
}
|
||||
else if ( fieldNeedingOptions == &m_filterMode )
|
||||
@@ -807,18 +788,65 @@ void RimParameterRftCrossPlot::createPoints()
|
||||
{
|
||||
const auto& [xValues, yValues] = pointsPerCase[summaryCase];
|
||||
|
||||
auto* plotCurve = new RiuQwtPlotCurve;
|
||||
plotCurve->setSamplesValues( xValues, yValues );
|
||||
plotCurve->setStyle( QwtPlotCurve::NoCurve );
|
||||
RiuRftCorrelationPlotTools::PointSeries series;
|
||||
series.x = xValues;
|
||||
series.y = yValues;
|
||||
series.title = summaryCase->displayCaseName();
|
||||
series.color = colorTable.cycledQColor( idx++ );
|
||||
series.isSelected = selectedSummaryCases.contains( summaryCase );
|
||||
RiuRftCorrelationPlotTools::attachPointSeries( m_plotWidget, series );
|
||||
}
|
||||
|
||||
const bool isSelected = selectedSummaryCases.contains( summaryCase );
|
||||
auto* symbol = new RiuQwtSymbol( isSelected ? RiuPlotCurveSymbol::SYMBOL_XCROSS : RiuPlotCurveSymbol::SYMBOL_ELLIPSE );
|
||||
symbol->setSize( 8, 8 );
|
||||
symbol->setColor( colorTable.cycledQColor( idx++ ) );
|
||||
plotCurve->setSymbol( symbol );
|
||||
attachObservedPressure();
|
||||
}
|
||||
|
||||
plotCurve->setTitle( summaryCase->displayCaseName() );
|
||||
plotCurve->attach( m_plotWidget->qwtPlot() );
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Draws every observed pressure within the selected depth intervals as a solid horizontal line,
|
||||
/// with dashed lines and a shaded band at +/- the observed error. Only the first line is labelled.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimParameterRftCrossPlot::attachObservedPressure()
|
||||
{
|
||||
const auto observedPressures =
|
||||
RimRftCrossPlotTools::computeObservedPressures( m_wellName(),
|
||||
m_selectedTimeStep(),
|
||||
depthIntervals(),
|
||||
m_depthType(),
|
||||
RimRftCrossPlotTools::buildAllZoneIntervals( m_wellFormations(),
|
||||
m_wellName(),
|
||||
m_depthType() ) );
|
||||
if ( observedPressures.empty() ) return;
|
||||
|
||||
auto attachLine = [this]( double value, Qt::PenStyle style, const QString& label, const QColor& color )
|
||||
{ RiuRftCorrelationPlotTools::attachHorizontalLine( m_plotWidget, value, style, label, color ); };
|
||||
|
||||
bool isFirst = true;
|
||||
for ( const auto& observed : observedPressures )
|
||||
{
|
||||
// Use the formation color when known; otherwise fall back to black lines and no shaded band
|
||||
std::optional<QColor> zoneColor;
|
||||
if ( auto it = m_zoneColors.find( observed.zoneName ); it != m_zoneColors.end() && it->second.isValid() ) zoneColor = it->second;
|
||||
QColor lineColor = zoneColor ? *zoneColor : QColor( Qt::black );
|
||||
lineColor.setAlpha( 255 );
|
||||
|
||||
QString label;
|
||||
if ( observed.count > 1 )
|
||||
label = QString( "Observed Pressure (average of %1 observations)" ).arg( observed.count );
|
||||
else if ( isFirst )
|
||||
label = "Observed Pressure";
|
||||
|
||||
attachLine( observed.pressure, Qt::SolidLine, label, lineColor );
|
||||
isFirst = false;
|
||||
|
||||
if ( observed.rangeMax <= observed.rangeMin ) continue;
|
||||
|
||||
attachLine( observed.rangeMin, Qt::DashLine, "", lineColor );
|
||||
attachLine( observed.rangeMax, Qt::DashLine, "", lineColor );
|
||||
|
||||
if ( !zoneColor ) continue;
|
||||
|
||||
const QColor shadingColor = *zoneColor; // alpha matches the track's formation shading
|
||||
|
||||
RiuRftCorrelationPlotTools::attachHorizontalBand( m_plotWidget, observed.rangeMin, observed.rangeMax, shadingColor );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -867,6 +895,13 @@ void RimParameterRftCrossPlot::updateValueRanges()
|
||||
yMax = std::max( yMax, pressureValue );
|
||||
}
|
||||
|
||||
for ( const auto& observed :
|
||||
RimRftCrossPlotTools::computeObservedPressures( m_wellName(), m_selectedTimeStep(), depthIntervals(), m_depthType() ) )
|
||||
{
|
||||
yMin = std::min( yMin, observed.rangeMin );
|
||||
yMax = std::max( yMax, observed.rangeMax );
|
||||
}
|
||||
|
||||
if ( xMin == std::numeric_limits<double>::infinity() )
|
||||
{
|
||||
m_xValueRange = std::nullopt;
|
||||
@@ -893,7 +928,7 @@ RimSummaryCase* RimParameterRftCrossPlot::findClosestCase( const QPoint& canvasP
|
||||
for ( const auto& d : caseData )
|
||||
points.push_back( { d.parameterValue, d.pressureValue } );
|
||||
|
||||
int idx = RiuQwtCurveSelectorFilter::closestPointIndex( m_plotWidget->qwtPlot(), canvasPos, points );
|
||||
int idx = RiuRftCorrelationPlotTools::closestPointIndex( m_plotWidget, canvasPos, points );
|
||||
return idx >= 0 ? caseData[idx].summaryCase : nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,11 @@
|
||||
#include "cafPdmField.h"
|
||||
#include "cafPdmPtrField.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QDateTime>
|
||||
#include <QPointer>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -77,6 +79,10 @@ public:
|
||||
void setDepthType( RiaDefines::DepthType depthType );
|
||||
void setEnsembleParameter( const QString& paramName );
|
||||
void setWellFormations( RimWellFormationsFile* wellFormationsFile );
|
||||
void setFilterMode( RimRftCrossPlotTools::DepthFilterMode filterMode );
|
||||
void setSelectedZones( const std::vector<QString>& zones );
|
||||
void setZoneColors( const std::map<QString, QColor>& zoneColors );
|
||||
void appendDataAndFilterUiOrdering( caf::PdmUiOrdering& uiOrdering );
|
||||
|
||||
QString ensembleParameter() const;
|
||||
QString wellName() const;
|
||||
@@ -120,6 +126,7 @@ private:
|
||||
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
|
||||
|
||||
void createPoints();
|
||||
void attachObservedPressure();
|
||||
void updatePlotTitle();
|
||||
void updateValueRanges();
|
||||
void cleanupBeforeClose();
|
||||
@@ -137,6 +144,7 @@ private:
|
||||
caf::PdmField<double> m_depthRangeMax;
|
||||
caf::PdmPtrField<RimWellFormationsFile*> m_wellFormations;
|
||||
caf::PdmField<std::vector<QString>> m_selectedZones;
|
||||
std::map<QString, QColor> m_zoneColors;
|
||||
caf::PdmField<caf::AppEnum<RiaDefines::DepthType>> m_depthType;
|
||||
caf::PdmField<SamplingModeEnum> m_samplingMode;
|
||||
caf::PdmField<QString> m_ensembleParameter;
|
||||
|
||||
+170
-1
@@ -18,16 +18,24 @@
|
||||
|
||||
#include "RimRftCorrelationReportPlot.h"
|
||||
|
||||
#include "RimColorLegend.h"
|
||||
#include "RimColorLegendItem.h"
|
||||
#include "RimDepthTrackPlot.h"
|
||||
#include "RimParameterRftCrossPlot.h"
|
||||
#include "RimRftCrossPlotTools.h"
|
||||
#include "RimRftTornadoPlot.h"
|
||||
#include "RimWellLogTrack.h"
|
||||
#include "RimWellRftEnsembleCurveSet.h"
|
||||
#include "RimWellRftPlot.h"
|
||||
|
||||
#include "Formations/RimWellFormationsFile.h"
|
||||
|
||||
#include "RiaWellLogUnitTools.h"
|
||||
|
||||
#include "RiuInterfaceToViewWindow.h"
|
||||
#include "RiuPlotWidget.h"
|
||||
#include "RiuQwtPlotWidget.h"
|
||||
#include "RiuRftCorrelationPlotTools.h"
|
||||
|
||||
#include "DockAreaTitleBar.h"
|
||||
#include "DockAreaWidget.h"
|
||||
@@ -35,14 +43,24 @@
|
||||
#include "DockWidget.h"
|
||||
|
||||
#include "cafPdmOptionItemInfo.h"
|
||||
#include "cafPdmPointer.h"
|
||||
#include "cafPdmUiCheckBoxEditor.h"
|
||||
#include "cafPdmUiTreeOrdering.h"
|
||||
#include "cafSelectionManager.h"
|
||||
|
||||
#include <QContextMenuEvent>
|
||||
#include <QFrame>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPointer>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
static const char* RFT_DOCK_LAYOUT_REGISTRY_KEY = "RftCorrelationReportPlot/defaultDockLayout";
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -253,6 +271,8 @@ void RimRftCorrelationReportPlot::recreatePlotWidgets()
|
||||
if ( auto* w = m_tornadoPlot->viewer() ) w->installEventFilter( m_contextMenuFilter );
|
||||
if ( auto* w = m_parameterRftCrossPlot->viewer() ) w->installEventFilter( m_contextMenuFilter );
|
||||
|
||||
installTrackClickFilters();
|
||||
|
||||
auto makeDockWidget = [&]( const QString& title, RimPlotWindow* plot, QWidget* widget ) -> ads::CDockWidget*
|
||||
{
|
||||
auto* dock = new ads::CDockWidget( title, m_dockManager );
|
||||
@@ -402,6 +422,9 @@ void RimRftCorrelationReportPlot::onLoadDataAndUpdate()
|
||||
if ( m_showWindow )
|
||||
{
|
||||
m_wellRftPlot->loadDataAndUpdate();
|
||||
installTrackClickFilters();
|
||||
updateSelectedZoneHighlight();
|
||||
syncZoneColorsToCrossPlot();
|
||||
syncTornadoInputsFromCrossPlot();
|
||||
m_tornadoPlot->loadDataAndUpdate();
|
||||
m_parameterRftCrossPlot->loadDataAndUpdate();
|
||||
@@ -418,7 +441,7 @@ void RimRftCorrelationReportPlot::defineUiOrdering( QString uiConfigName, caf::P
|
||||
uiOrdering.add( &m_depthType );
|
||||
|
||||
// Delegate cross-plot settings (ensemble, well, depth range, parameter) to the cross plot
|
||||
m_parameterRftCrossPlot->uiOrdering( uiConfigName, uiOrdering );
|
||||
m_parameterRftCrossPlot->appendDataAndFilterUiOrdering( uiOrdering );
|
||||
|
||||
auto* layoutGroup = uiOrdering.addNewGroup( "Dock Layout" );
|
||||
layoutGroup->setCollapsedByDefault();
|
||||
@@ -519,10 +542,27 @@ void RimRftCorrelationReportPlot::onTornadoParameterSelected( const QString& par
|
||||
{
|
||||
m_parameterRftCrossPlot->setEnsembleParameter( paramName );
|
||||
m_parameterRftCrossPlot->loadDataAndUpdate();
|
||||
|
||||
syncEnsembleParameterToRftCurves();
|
||||
}
|
||||
updateConnectedEditors();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Makes RFT curves colored by ensemble parameter use the cross plot's ensemble parameter.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimRftCorrelationReportPlot::syncEnsembleParameterToRftCurves()
|
||||
{
|
||||
if ( !m_wellRftPlot() || !m_parameterRftCrossPlot() ) return;
|
||||
|
||||
auto* curveSet = m_wellRftPlot->findEnsembleCurveSet( m_parameterRftCrossPlot->ensemble() );
|
||||
if ( curveSet && curveSet->syncEnsembleParameter( m_parameterRftCrossPlot->ensembleParameter() ) )
|
||||
{
|
||||
m_wellRftPlot->rebuildCurves();
|
||||
curveSet->updateConnectedEditors();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -543,6 +583,135 @@ void RimRftCorrelationReportPlot::syncTornadoInputsFromCrossPlot()
|
||||
m_tornadoPlot->setSelectedZones( m_parameterRftCrossPlot->selectedZones() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Makes a click on a formation in the RFT plot tracks select that formation as the zone filter
|
||||
/// used by the cross plot and tornado plot.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimRftCorrelationReportPlot::installTrackClickFilters()
|
||||
{
|
||||
if ( !m_wellRftPlot() ) return;
|
||||
|
||||
if ( m_trackClickFilter ) m_trackClickFilter->deleteLater();
|
||||
m_trackClickFilter = new QObject( this );
|
||||
|
||||
const auto orientation = m_wellRftPlot->depthOrientation();
|
||||
const bool isVertical = orientation == RiaDefines::Orientation::VERTICAL;
|
||||
|
||||
for ( size_t i = 0; i < m_wellRftPlot->plotCount(); ++i )
|
||||
{
|
||||
auto* track = dynamic_cast<RimWellLogTrack*>( m_wellRftPlot->plotByIndex( i ) );
|
||||
if ( !track ) continue;
|
||||
|
||||
RiuQwtPlotWidget* widget = track->viewer();
|
||||
if ( !widget ) continue;
|
||||
|
||||
caf::PdmPointer<RimWellLogTrack> trackPtr( track );
|
||||
RiuRftCorrelationPlotTools::installDepthClickFilter(
|
||||
widget,
|
||||
RimDepthTrackPlot::depthAxis( orientation ),
|
||||
isVertical,
|
||||
[this, trackPtr]( double depth )
|
||||
{
|
||||
if ( trackPtr ) onRftTrackDepthClicked( trackPtr, depth );
|
||||
},
|
||||
m_trackClickFilter );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Marks the depth interval of each selected zone in the RFT plot tracks with a thin bar along the
|
||||
/// depth axis side. Stale bars are found by type and removed before new ones are attached.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimRftCorrelationReportPlot::updateSelectedZoneHighlight()
|
||||
{
|
||||
if ( !m_wellRftPlot() || !m_parameterRftCrossPlot() ) return;
|
||||
|
||||
const auto filterMode = m_parameterRftCrossPlot->filterMode();
|
||||
const bool isVertical = m_wellRftPlot->depthOrientation() == RiaDefines::Orientation::VERTICAL;
|
||||
|
||||
// Bars are drawn in plot coordinates, so the depth range is converted to the plot's depth unit
|
||||
std::vector<RiuRftCorrelationPlotTools::DepthInterval> depthRangeBars;
|
||||
if ( filterMode == RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE )
|
||||
{
|
||||
const auto fromDepthUnit = m_wellRftPlot->caseDepthUnit();
|
||||
const auto toDepthUnit = m_wellRftPlot->depthUnit();
|
||||
depthRangeBars.push_back(
|
||||
{ RiaWellLogUnitTools<double>::convertDepth( m_parameterRftCrossPlot->depthRangeMin(), fromDepthUnit, toDepthUnit ),
|
||||
RiaWellLogUnitTools<double>::convertDepth( m_parameterRftCrossPlot->depthRangeMax(), fromDepthUnit, toDepthUnit ) } );
|
||||
}
|
||||
|
||||
const std::vector<QString> selectedZones = m_parameterRftCrossPlot->selectedZones();
|
||||
|
||||
for ( size_t i = 0; i < m_wellRftPlot->plotCount(); ++i )
|
||||
{
|
||||
auto* track = dynamic_cast<RimWellLogTrack*>( m_wellRftPlot->plotByIndex( i ) );
|
||||
if ( !track || !track->viewer() ) continue;
|
||||
|
||||
std::vector<RiuRftCorrelationPlotTools::DepthInterval> barIntervals = depthRangeBars;
|
||||
if ( filterMode == RimRftCrossPlotTools::DepthFilterMode::ZONES )
|
||||
{
|
||||
// Use the zones as shaded on this track so the bars line up with them
|
||||
for ( const auto& [name, top, base] : track->formationZoneDisplayRanges() )
|
||||
{
|
||||
if ( std::find( selectedZones.begin(), selectedZones.end(), name ) != selectedZones.end() )
|
||||
barIntervals.push_back( { top, base } );
|
||||
}
|
||||
}
|
||||
|
||||
RiuRftCorrelationPlotTools::setDepthIntervalBars( track->viewer(), barIntervals, isVertical );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Gives the cross plot the formation colors used by the RFT plot tracks, looked up by formation name.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimRftCorrelationReportPlot::syncZoneColorsToCrossPlot()
|
||||
{
|
||||
if ( !m_wellRftPlot() || !m_parameterRftCrossPlot() ) return;
|
||||
|
||||
std::map<QString, QColor> zoneColors;
|
||||
for ( size_t i = 0; i < m_wellRftPlot->plotCount() && zoneColors.empty(); ++i )
|
||||
{
|
||||
auto* track = dynamic_cast<RimWellLogTrack*>( m_wellRftPlot->plotByIndex( i ) );
|
||||
if ( !track ) continue;
|
||||
|
||||
for ( const auto& [name, color] : track->formationZoneColors() )
|
||||
{
|
||||
// The alpha carries the track's shading transparency, used for the observed pressure band
|
||||
zoneColors[name] = QColor( color.r(), color.g(), color.b(), track->formationShadingAlpha() );
|
||||
}
|
||||
}
|
||||
|
||||
m_parameterRftCrossPlot->setZoneColors( zoneColors );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimRftCorrelationReportPlot::onRftTrackDepthClicked( RimWellLogTrack* track, double depth )
|
||||
{
|
||||
if ( !m_parameterRftCrossPlot() ) return;
|
||||
|
||||
// The track's zones are in plot coordinates and don't overlap, so the hit is the zone shaded at the click
|
||||
for ( const auto& [name, top, base] : track->formationZoneDisplayRanges() )
|
||||
{
|
||||
if ( depth < std::min( top, base ) || depth > std::max( top, base ) ) continue;
|
||||
|
||||
std::vector<QString> zones = m_parameterRftCrossPlot->selectedZones();
|
||||
auto it = std::find( zones.begin(), zones.end(), name );
|
||||
if ( it != zones.end() )
|
||||
zones.erase( it ); // clicking a selected zone again deselects it
|
||||
else
|
||||
zones.push_back( name );
|
||||
|
||||
m_parameterRftCrossPlot->setFilterMode( RimRftCrossPlotTools::DepthFilterMode::ZONES );
|
||||
m_parameterRftCrossPlot->setSelectedZones( zones );
|
||||
loadDataAndUpdate();
|
||||
updateConnectedEditors();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Pushes the user's manual "Well Formations File" selection in the cross plot down to the sub
|
||||
/// plots so their "Filter By: Zones" option becomes available.
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
class RimParameterRftCrossPlot;
|
||||
class RimRftTornadoPlot;
|
||||
class RimWellLogTrack;
|
||||
class RimWellRftPlot;
|
||||
|
||||
namespace ads
|
||||
@@ -66,6 +67,7 @@ public:
|
||||
std::vector<RimPlot*> childPlotsForTextExport() const override;
|
||||
|
||||
void initializeFromSourcePlot( RimWellRftPlot* source );
|
||||
void syncEnsembleParameterToRftCurves();
|
||||
|
||||
private:
|
||||
QString createDescription() const;
|
||||
@@ -91,6 +93,10 @@ private:
|
||||
void syncTornadoInputsFromCrossPlot();
|
||||
void applyDepthTypeToSubPlots();
|
||||
void applyWellFormationsToSubPlots();
|
||||
void installTrackClickFilters();
|
||||
void updateSelectedZoneHighlight();
|
||||
void syncZoneColorsToCrossPlot();
|
||||
void onRftTrackDepthClicked( RimWellLogTrack* track, double depth );
|
||||
|
||||
private:
|
||||
caf::PdmProxyValueField<QString> m_name;
|
||||
@@ -106,6 +112,7 @@ private:
|
||||
|
||||
QWidget* m_viewWidget = nullptr;
|
||||
QObject* m_contextMenuFilter = nullptr;
|
||||
QObject* m_trackClickFilter = nullptr;
|
||||
ads::CDockManager* m_dockManager = nullptr;
|
||||
ads::CDockWidget* m_rftDockWidget = nullptr;
|
||||
ads::CDockWidget* m_correlationDockWidget = nullptr;
|
||||
|
||||
@@ -24,17 +24,21 @@
|
||||
|
||||
#include "Well/RigEclipseWellLogExtractor.h"
|
||||
#include "Well/RigWellPathFormations.h"
|
||||
#include "Well/RigWellPathGeometryTools.h"
|
||||
|
||||
#include "Formations/RimWellFormationsFile.h"
|
||||
|
||||
#include "RimEclipseResultCase.h"
|
||||
#include "RimObservedFmuRftData.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimSummaryCase.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
#include "RimWellPath.h"
|
||||
#include "RimWellPlotTools.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
namespace caf
|
||||
@@ -80,9 +84,42 @@ std::vector<double> RimRftCrossPlotTools::rftCurveDepthValues( RifReaderRftInter
|
||||
std::vector<double> depths;
|
||||
reader->values( mdAddress, &depths );
|
||||
if ( depths.empty() && extractor ) depths = reader->computeMeasuredDepth( wellName, timeStep, extractor );
|
||||
if ( depths.empty() ) depths = measuredDepthFromObservedData( reader, wellName, timeStep );
|
||||
return depths;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Same fallback as RimWellLogRftCurve uses when there is no grid, so the filter sees the MD the
|
||||
/// RFT curves are drawn at.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<double>
|
||||
RimRftCrossPlotTools::measuredDepthFromObservedData( RifReaderRftInterface* reader, const QString& wellName, const QDateTime& timeStep )
|
||||
{
|
||||
auto tvdAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::TVD );
|
||||
std::vector<double> tvdDepths;
|
||||
reader->values( tvdAddress, &tvdDepths );
|
||||
if ( tvdDepths.empty() ) return {};
|
||||
|
||||
auto mdAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::MD );
|
||||
for ( RimObservedFmuRftData* observedData : RimWellPlotTools::observedFmuRftDataForWell( wellName ) )
|
||||
{
|
||||
RifReaderRftInterface* observedReader = observedData->rftReader();
|
||||
if ( !observedReader || !observedReader->availableTimeSteps( wellName ).count( timeStep ) ) continue;
|
||||
|
||||
std::vector<double> observedTvd;
|
||||
std::vector<double> observedMd;
|
||||
observedReader->values( tvdAddress, &observedTvd );
|
||||
observedReader->values( mdAddress, &observedMd );
|
||||
|
||||
// At least two samples are needed to estimate the MD/TVD relationship
|
||||
if ( observedTvd.size() < 2 || observedTvd.size() != observedMd.size() ) continue;
|
||||
|
||||
return RigWellPathGeometryTools::interpolateMdFromTvd( observedMd, observedTvd, tvdDepths );
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -93,7 +130,7 @@ std::vector<double> RimRftCrossPlotTools::filterPressuresByDepthRange( const std
|
||||
double depthRangeMax )
|
||||
{
|
||||
std::vector<DepthInterval> intervals;
|
||||
if ( useDepthRange ) intervals.push_back( { depthRangeMin, depthRangeMax } );
|
||||
if ( useDepthRange ) intervals.push_back( { depthRangeMin, depthRangeMax, QString() } );
|
||||
|
||||
return filterPressuresByDepthIntervals( depths, pressures, intervals );
|
||||
}
|
||||
@@ -111,7 +148,7 @@ std::vector<RimRftCrossPlotTools::DepthInterval> RimRftCrossPlotTools::buildDept
|
||||
{
|
||||
if ( mode == DepthFilterMode::NONE ) return {};
|
||||
|
||||
if ( mode == DepthFilterMode::DEPTH_RANGE ) return { DepthInterval{ depthRangeMin, depthRangeMax } };
|
||||
if ( mode == DepthFilterMode::DEPTH_RANGE ) return { DepthInterval{ depthRangeMin, depthRangeMax, QString() } };
|
||||
|
||||
// ZONES mode
|
||||
if ( !wellFormationsFile || selectedZones.empty() ) return {};
|
||||
@@ -128,14 +165,37 @@ std::vector<RimRftCrossPlotTools::DepthInterval> RimRftCrossPlotTools::buildDept
|
||||
if ( !isSelected ) continue;
|
||||
|
||||
if ( depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH )
|
||||
intervals.push_back( { formation.tvdTop, formation.tvdBase } );
|
||||
intervals.push_back( { formation.tvdTop, formation.tvdBase, formation.formationName } );
|
||||
else
|
||||
intervals.push_back( { formation.mdTop, formation.mdBase } );
|
||||
intervals.push_back( { formation.mdTop, formation.mdBase, formation.formationName } );
|
||||
}
|
||||
|
||||
return intervals;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<RimRftCrossPlotTools::DepthInterval> RimRftCrossPlotTools::buildAllZoneIntervals( RimWellFormationsFile* wellFormationsFile,
|
||||
const QString& wellName,
|
||||
RiaDefines::DepthType depthType )
|
||||
{
|
||||
if ( !wellFormationsFile ) return {};
|
||||
|
||||
const RigWellPathFormations* formations = wellFormationsFile->formationsForWell( wellName );
|
||||
if ( !formations ) return {};
|
||||
|
||||
const bool useTvd = depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH;
|
||||
std::vector<DepthInterval> intervals;
|
||||
for ( size_t i = 0; i < formations->formationCount(); ++i )
|
||||
{
|
||||
const RigWellPathFormation& formation = formations->formationAt( i );
|
||||
intervals.push_back(
|
||||
{ useTvd ? formation.tvdTop : formation.mdTop, useTvd ? formation.tvdBase : formation.mdBase, formation.formationName } );
|
||||
}
|
||||
return intervals;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -189,6 +249,105 @@ QString RimRftCrossPlotTools::depthFilterDescription( DepthFilterMode
|
||||
return QString( "%1 %2 - %3 m" ).arg( depthTypeAbbreviation( depthType ) ).arg( depthRangeMin ).arg( depthRangeMax );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<RimRftCrossPlotTools::ObservedPressure>
|
||||
RimRftCrossPlotTools::computeObservedPressures( const QString& wellName,
|
||||
const QDateTime& timeStep,
|
||||
const std::vector<DepthInterval>& depthIntervals,
|
||||
RiaDefines::DepthType depthType,
|
||||
const std::vector<DepthInterval>& zoneIntervals )
|
||||
{
|
||||
std::vector<ObservedPressure> result;
|
||||
if ( wellName.isEmpty() || !timeStep.isValid() ) return result;
|
||||
|
||||
for ( RimObservedFmuRftData* observedData : RimWellPlotTools::observedFmuRftDataForWell( wellName ) )
|
||||
{
|
||||
RifReaderRftInterface* reader = observedData->rftReader();
|
||||
if ( !reader || !reader->availableTimeSteps( wellName ).count( timeStep ) ) continue;
|
||||
|
||||
auto pressureAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::PRESSURE );
|
||||
std::vector<double> pressures;
|
||||
reader->values( pressureAddress, &pressures );
|
||||
if ( pressures.empty() ) continue;
|
||||
|
||||
std::vector<double> depths = rftCurveDepthValues( reader, wellName, timeStep, nullptr, depthType );
|
||||
|
||||
auto errorAddress =
|
||||
RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::PRESSURE_ERROR );
|
||||
std::vector<double> errors;
|
||||
reader->values( errorAddress, &errors );
|
||||
|
||||
// Filtering pressures and errors with the same depths/intervals keeps the two vectors aligned.
|
||||
const std::vector<double> filteredPressures = filterPressuresByDepthIntervals( depths, pressures, depthIntervals );
|
||||
std::vector<double> filteredErrors;
|
||||
if ( errors.size() == pressures.size() ) filteredErrors = filterPressuresByDepthIntervals( depths, errors, depthIntervals );
|
||||
|
||||
const std::vector<double> filteredDepths = filterPressuresByDepthIntervals( depths, depths, depthIntervals );
|
||||
|
||||
for ( size_t i = 0; i < filteredPressures.size(); ++i )
|
||||
{
|
||||
const double error = i < filteredErrors.size() && filteredErrors.size() == filteredPressures.size() ? filteredErrors[i] : 0.0;
|
||||
|
||||
QString zoneName;
|
||||
if ( filteredDepths.size() == filteredPressures.size() )
|
||||
{
|
||||
// When filtering by zones, name the sample by a selected zone so it is never shown as a zone outside the filter
|
||||
const bool filteredByZones = !depthIntervals.empty() && !depthIntervals.front().zoneName.isEmpty();
|
||||
const auto& lookup = filteredByZones || zoneIntervals.empty() ? depthIntervals : zoneIntervals;
|
||||
double thickness = std::numeric_limits<double>::infinity();
|
||||
for ( const auto& interval : lookup )
|
||||
{
|
||||
// Prefer the narrowest matching zone when formation levels overlap
|
||||
if ( filteredDepths[i] >= interval.top && filteredDepths[i] <= interval.base && interval.base - interval.top < thickness )
|
||||
{
|
||||
zoneName = interval.zoneName;
|
||||
thickness = interval.base - interval.top;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push_back( { filteredPressures[i], error, zoneName, filteredPressures[i] - error, filteredPressures[i] + error } );
|
||||
}
|
||||
}
|
||||
|
||||
// Combine multiple observations in the same zone: mean pressure, band spanning all observations
|
||||
std::vector<ObservedPressure> aggregated;
|
||||
std::vector<int> counts;
|
||||
std::map<QString, size_t> zoneIndex;
|
||||
for ( const auto& observed : result )
|
||||
{
|
||||
if ( observed.zoneName.isEmpty() )
|
||||
{
|
||||
aggregated.push_back( observed );
|
||||
counts.push_back( 1 );
|
||||
continue;
|
||||
}
|
||||
|
||||
auto [it, inserted] = zoneIndex.insert( { observed.zoneName, aggregated.size() } );
|
||||
if ( inserted )
|
||||
{
|
||||
aggregated.push_back( observed );
|
||||
counts.push_back( 1 );
|
||||
continue;
|
||||
}
|
||||
|
||||
ObservedPressure& target = aggregated[it->second];
|
||||
target.pressure += observed.pressure;
|
||||
target.rangeMin = std::min( target.rangeMin, observed.rangeMin );
|
||||
target.rangeMax = std::max( target.rangeMax, observed.rangeMax );
|
||||
counts[it->second]++;
|
||||
target.count++;
|
||||
}
|
||||
|
||||
for ( size_t i = 0; i < aggregated.size(); ++i )
|
||||
{
|
||||
aggregated[i].pressure /= counts[i];
|
||||
aggregated[i].error = std::max( aggregated[i].pressure - aggregated[i].rangeMin, aggregated[i].rangeMax - aggregated[i].pressure );
|
||||
}
|
||||
|
||||
return aggregated;
|
||||
}
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class RifReaderRftInterface;
|
||||
@@ -54,25 +55,30 @@ using DepthFilterModeEnum = caf::AppEnum<DepthFilterMode>;
|
||||
// A single inclusive depth interval, in the unit/datum of whichever depth type it was built for.
|
||||
struct DepthInterval
|
||||
{
|
||||
double top = 0.0;
|
||||
double base = 0.0;
|
||||
double top = 0.0;
|
||||
double base = 0.0;
|
||||
QString zoneName; // set when the interval comes from a formation zone
|
||||
};
|
||||
|
||||
// Short abbreviation used in axis titles and plot titles for the active depth type.
|
||||
QString depthTypeAbbreviation( RiaDefines::DepthType depthType );
|
||||
|
||||
// Returns the depth values for the given well/time step, strictly matching depthType: MD
|
||||
// (native RFT MD channel, falling back to extractor-derived MD if the channel is missing) or
|
||||
// TVD (native RFT TVD channel). Returns an empty vector if the requested depth type is not
|
||||
// available, rather than silently substituting the other depth type — callers that filter by
|
||||
// depth range rely on this to exclude/blank out data instead of comparing a range against the
|
||||
// wrong unit.
|
||||
// (native RFT MD channel, falling back to extractor-derived MD, then to MD interpolated from
|
||||
// observed FMU RFT data for the well) or TVD (native RFT TVD channel). Returns an empty vector if
|
||||
// the requested depth type is not available, rather than silently substituting the other depth
|
||||
// type — callers that filter by depth range rely on this to exclude/blank out data instead of
|
||||
// comparing a range against the wrong unit.
|
||||
std::vector<double> rftCurveDepthValues( RifReaderRftInterface* reader,
|
||||
const QString& wellName,
|
||||
const QDateTime& timeStep,
|
||||
RigEclipseWellLogExtractor* extractor,
|
||||
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH );
|
||||
|
||||
// MD for the reader's TVD samples, interpolated from the MD/TVD pairs of observed FMU RFT data for
|
||||
// the well at the time step. Empty if no such data is available.
|
||||
std::vector<double> measuredDepthFromObservedData( RifReaderRftInterface* reader, const QString& wellName, const QDateTime& timeStep );
|
||||
|
||||
// Returns the subset of pressures whose corresponding depth is within [depthRangeMin, depthRangeMax].
|
||||
// If useDepthRange is false, all pressures are returned unfiltered. If useDepthRange is true and
|
||||
// depths/pressures are not the same size, an empty vector is returned (no aligned depth data).
|
||||
@@ -93,6 +99,11 @@ std::vector<DepthInterval> buildDepthIntervals( DepthFilterMode mode
|
||||
const std::vector<QString>& selectedZones,
|
||||
RiaDefines::DepthType depthType );
|
||||
|
||||
// Returns one interval per formation zone of the well (named by zone), used to look up which zone a
|
||||
// depth belongs to. Empty if no well formations file or no formations for the well.
|
||||
std::vector<DepthInterval>
|
||||
buildAllZoneIntervals( RimWellFormationsFile* wellFormationsFile, const QString& wellName, RiaDefines::DepthType depthType );
|
||||
|
||||
// Returns the subset of pressures whose corresponding depth lies within any of depthIntervals
|
||||
// (inclusive). An empty depthIntervals list means "no filter", returning pressures unchanged; a
|
||||
// size mismatch between depths/pressures returns an empty vector (no aligned depth data).
|
||||
@@ -108,6 +119,27 @@ QString depthFilterDescription( DepthFilterMode mode,
|
||||
double depthRangeMax,
|
||||
const std::vector<QString>& selectedZones );
|
||||
|
||||
// A single observed (e.g. FMU) RFT pressure sample with its uncertainty.
|
||||
struct ObservedPressure
|
||||
{
|
||||
double pressure = 0.0;
|
||||
double error = 0.0; // observed pressure error; 0 if not available
|
||||
QString zoneName; // formation zone containing the sample; empty if not filtered by zones
|
||||
double rangeMin = 0.0; // lowest pressure - error among the observations
|
||||
double rangeMax = 0.0; // highest pressure + error among the observations
|
||||
int count = 1; // number of observations combined into this entry
|
||||
};
|
||||
|
||||
// Observed pressure samples within depthIntervals for the well/time step, across all observed data
|
||||
// sets. Samples in the same zone are combined into one entry: mean pressure, with rangeMin/rangeMax
|
||||
// covering all of them. An empty depthIntervals list means no filtering. Samples are named by the
|
||||
// selected zones when depthIntervals come from a zone filter, otherwise by zoneIntervals.
|
||||
std::vector<ObservedPressure> computeObservedPressures( const QString& wellName,
|
||||
const QDateTime& timeStep,
|
||||
const std::vector<DepthInterval>& depthIntervals,
|
||||
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH,
|
||||
const std::vector<DepthInterval>& zoneIntervals = {} );
|
||||
|
||||
// Arithmetic mean of the given samples, or infinity if samples is empty.
|
||||
double computeMean( const std::vector<double>& samples );
|
||||
|
||||
|
||||
@@ -36,12 +36,10 @@
|
||||
#include "RiuGroupedBarChartBuilder.h"
|
||||
#include "RiuPlotItem.h"
|
||||
#include "RiuQwtPlotWidget.h"
|
||||
#include "RiuRftCorrelationPlotTools.h"
|
||||
|
||||
#include "cafPdmUiCheckBoxEditor.h"
|
||||
|
||||
#include "qwt_plot.h"
|
||||
#include "qwt_text.h"
|
||||
|
||||
#include <QPaintDevice>
|
||||
|
||||
#include <limits>
|
||||
@@ -256,8 +254,7 @@ void RimRftTornadoPlot::detachAllCurves()
|
||||
{
|
||||
if ( m_plotWidget )
|
||||
{
|
||||
m_plotWidget->qwtPlot()->detachItems( QwtPlotItem::Rtti_PlotBarChart );
|
||||
m_plotWidget->qwtPlot()->detachItems( QwtPlotItem::Rtti_PlotScale );
|
||||
RiuRftCorrelationPlotTools::detachBarChartItems( m_plotWidget );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,13 +320,13 @@ void RimRftTornadoPlot::onLoadDataAndUpdate()
|
||||
m_lastCorrelations = addDataToChartBuilder( chartBuilder );
|
||||
const int labelSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_labelFontSize() );
|
||||
chartBuilder.setLabelFontSize( labelSize );
|
||||
chartBuilder.addBarChartToPlot( m_plotWidget->qwtPlot(), Qt::Horizontal, m_showOnlyTopNCorrelations() ? m_topNFilterCount() : -1 );
|
||||
RiuRftCorrelationPlotTools::addHorizontalBarChart( m_plotWidget, chartBuilder, m_showOnlyTopNCorrelations() ? m_topNFilterCount() : -1 );
|
||||
RimCorrelationBarChartTools::highlightSelectedParameterBar( m_plotWidget,
|
||||
m_selectedParameter,
|
||||
RiaColorTools::toQColor( m_barColor() ),
|
||||
RiaColorTools::toQColor( m_highlightBarColor() ) );
|
||||
|
||||
m_plotWidget->qwtPlot()->insertLegend( nullptr );
|
||||
RiuRftCorrelationPlotTools::removeLegend( m_plotWidget );
|
||||
|
||||
updateAxes();
|
||||
updatePlotTitle();
|
||||
|
||||
@@ -103,6 +103,22 @@ void RimWellRftEnsembleCurveSet::setCurveColor( const cvf::Color3f& color )
|
||||
m_appearance->setMainEnsembleColor( color );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimWellRftEnsembleCurveSet::syncEnsembleParameter( const QString& ensembleParameter )
|
||||
{
|
||||
return m_appearance->syncEnsembleParameter( ensembleParameter );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimWellRftEnsembleCurveSet::setColorByEnsembleParameter( const QString& ensembleParameter )
|
||||
{
|
||||
m_appearance->setColorByEnsembleParameter( ensembleParameter );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
QString ensembleName() const;
|
||||
|
||||
void setCurveColor( const cvf::Color3f& color );
|
||||
bool syncEnsembleParameter( const QString& ensembleParameter );
|
||||
void setColorByEnsembleParameter( const QString& ensembleParameter );
|
||||
cvf::Color3f curveColor( RimSummaryEnsemble* ensemble, const RimSummaryCase* summaryCase ) const;
|
||||
|
||||
RimRegularLegendConfig* legendConfig();
|
||||
|
||||
+27
-1
@@ -29,6 +29,8 @@
|
||||
#include "RimWellLogTrack.h"
|
||||
#include "RimWellPath.h"
|
||||
|
||||
#include "RicImportWellFormationsFeature.h"
|
||||
|
||||
#include "Formations/RimWellFormationsCollection.h"
|
||||
#include "Formations/RimWellFormationsFile.h"
|
||||
|
||||
@@ -341,7 +343,10 @@ void RimWellLogFormationSettings::uiOrdering( const QString& uiConfigName, caf::
|
||||
{
|
||||
// Well picks use explicit top/base depths and do not require a well path trajectory, so
|
||||
// this source can be used e.g. for an RFT well with no associated/modelled well path.
|
||||
uiOrdering.add( &m_wellFormationsFile );
|
||||
uiOrdering.add( &m_wellFormationsFile, { .newRow = true, .totalColumnSpan = 2, .leftLabelColumnSpan = 1 } );
|
||||
uiOrdering.addNewButton( "Add Well Picks",
|
||||
[this]() { onAddWellPicksClicked(); },
|
||||
{ .newRow = false, .totalColumnSpan = 1, .leftLabelColumnSpan = 0 } );
|
||||
if ( m_wellFormationsFile() ) uiOrdering.add( &m_wellNameInFormationsFile );
|
||||
|
||||
if ( resolveWellPickFormations() )
|
||||
@@ -393,6 +398,27 @@ void RimWellLogFormationSettings::fieldChangedByUi( const caf::PdmFieldHandle* c
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimWellLogFormationSettings::onAddWellPicksClicked()
|
||||
{
|
||||
std::vector<RimWellFormationsFile*> importedFiles = RicImportWellFormationsFeature::importFilesWithDialog();
|
||||
if ( importedFiles.empty() ) return;
|
||||
|
||||
RimWellFormationsFile* file = importedFiles.back();
|
||||
m_wellFormationsFile = file;
|
||||
|
||||
QStringList wellNames = file->wellNames();
|
||||
m_wellNameInFormationsFile = wellNames.isEmpty() ? QString() : wellNames.first();
|
||||
|
||||
if ( auto track = firstAncestorOrThisOfType<RimWellLogTrack>() )
|
||||
{
|
||||
track->loadDataAndUpdate();
|
||||
track->updateEditors();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -106,6 +106,7 @@ protected:
|
||||
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
|
||||
|
||||
private:
|
||||
void onAddWellPicksClicked();
|
||||
caf::PdmField<caf::AppEnum<RiaDefines::WellLogTrackFormationSource>> m_formationSource;
|
||||
caf::PdmPtrField<RimCase*> m_formationCase;
|
||||
caf::PdmField<caf::AppEnum<RiaDefines::WellLogTrackTrajectoryType>> m_formationTrajectoryType;
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ RimWellLogRegionAnnotationSettings::RimWellLogRegionAnnotationSettings()
|
||||
CAF_PDM_InitFieldNoDefault( &m_colorShadingLegend, "ColorShadingLegend", "Colors" );
|
||||
m_colorShadingLegend = RimRegularLegendConfig::mapToColorLegend( RimRegularLegendConfig::ColorRangesType::NORMAL );
|
||||
|
||||
CAF_PDM_InitField( &m_colorShadingTransparency, "ColorShadingTransparency", 50, "Color Transparency" );
|
||||
CAF_PDM_InitField( &m_colorShadingTransparency, "ColorShadingTransparency", 85, "Color Transparency" );
|
||||
m_colorShadingTransparency.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() );
|
||||
|
||||
CAF_PDM_InitField( &m_showRegionLabels, "ShowFormationLabels", true, "Show Labels" );
|
||||
|
||||
@@ -1587,8 +1587,9 @@ void RimWellLogTrack::updateParentPlotZoom()
|
||||
void RimWellLogTrack::updateEditors()
|
||||
{
|
||||
updateConnectedEditors();
|
||||
RimPlotWindow* plotWindow = firstAncestorOrThisOfTypeAsserted<RimPlotWindow>();
|
||||
plotWindow->updateConnectedEditors();
|
||||
|
||||
// Also refresh the owning plot window (e.g. the RFT plot that embeds the track fields in its own UI)
|
||||
if ( auto plotWindow = firstAncestorOfType<RimPlotWindow>() ) plotWindow->updateConnectedEditors();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -1780,6 +1781,104 @@ RiuQwtPlotWidget* RimWellLogTrack::viewer()
|
||||
return m_plotWidget;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
int RimWellLogTrack::formationShadingAlpha() const
|
||||
{
|
||||
if ( !m_regionAnnotationSettings ) return 255;
|
||||
return ( ( 100 - m_regionAnnotationSettings->colorShadingTransparency() ) * 255 ) / 100;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Well pick formation zones as drawn on the track: at the track's formation level, with RKB offset
|
||||
/// and in the plot's depth unit.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<std::tuple<QString, double, double>> RimWellLogTrack::formationZoneDisplayRanges() const
|
||||
{
|
||||
auto* plot = firstAncestorOrThisOfType<RimDepthTrackPlot>();
|
||||
if ( !plot ) return {};
|
||||
|
||||
if ( m_formationSettings->formationSource() != RiaDefines::WellLogTrackFormationSource::WELL_PICK_FILTER &&
|
||||
m_formationSettings->formationSource() != RiaDefines::WellLogTrackFormationSource::WELL_PICKS_NO_TRAJECTORY )
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto formations = m_formationSettings->resolveWellPickFormations();
|
||||
if ( !formations ) return {};
|
||||
|
||||
double rkbDiff = 0.0;
|
||||
if ( plot->depthType() == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH_RKB )
|
||||
{
|
||||
// Picks-only wells have no trajectory
|
||||
auto sourceWellPath = m_formationSettings->wellPathForSourceWellPath();
|
||||
if ( sourceWellPath && sourceWellPath->wellPathGeometry() )
|
||||
{
|
||||
rkbDiff = sourceWellPath->wellPathGeometry()->rkbDiff();
|
||||
}
|
||||
}
|
||||
|
||||
const auto fromDepthUnit = plot->caseDepthUnit();
|
||||
const auto toDepthUnit = plot->depthUnit();
|
||||
const auto formationLevel = static_cast<RigWellPathFormations::FormationLevel>( m_formationSettings->formationLevel() );
|
||||
auto depthRanges = formations->depthRangesUpToLevel( formationLevel, plot->depthType() );
|
||||
for ( auto& [name, top, base] : depthRanges )
|
||||
{
|
||||
top = RiaWellLogUnitTools<double>::convertDepth( top + rkbDiff, fromDepthUnit, toDepthUnit );
|
||||
base = RiaWellLogUnitTools<double>::convertDepth( base + rkbDiff, fromDepthUnit, toDepthUnit );
|
||||
}
|
||||
|
||||
return depthRanges;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::map<QString, cvf::Color3ub> RimWellLogTrack::formationZoneColors() const
|
||||
{
|
||||
std::map<QString, cvf::Color3ub> colors;
|
||||
|
||||
auto* plot = firstAncestorOrThisOfType<RimDepthTrackPlot>();
|
||||
if ( !plot || !m_regionAnnotationSettings ) return colors;
|
||||
|
||||
if ( m_formationSettings->formationSource() != RiaDefines::WellLogTrackFormationSource::WELL_PICK_FILTER &&
|
||||
m_formationSettings->formationSource() != RiaDefines::WellLogTrackFormationSource::WELL_PICKS_NO_TRAJECTORY )
|
||||
{
|
||||
return colors;
|
||||
}
|
||||
|
||||
auto formations = m_formationSettings->resolveWellPickFormations();
|
||||
if ( !formations ) return colors;
|
||||
|
||||
RimColorLegend* legend = m_regionAnnotationSettings->colorShadingLegend();
|
||||
if ( !legend ) legend = RimRegularLegendConfig::mapToColorLegend( RimRegularLegendConfig::ColorRangesType::NORMAL );
|
||||
if ( !legend ) return colors;
|
||||
|
||||
std::map<QString, cvf::Color3ub> nameToColor;
|
||||
for ( auto* item : legend->colorLegendItems() )
|
||||
{
|
||||
nameToColor[item->categoryName()] = cvf::Color3ub( item->color() );
|
||||
}
|
||||
|
||||
// Same name-first, palette-index-second assignment as updateFormationNamesOnPlot()
|
||||
const cvf::Color3ubArray paletteColors = legend->colorArray();
|
||||
const auto formationLevel = static_cast<RigWellPathFormations::FormationLevel>( m_formationSettings->formationLevel() );
|
||||
const auto depthRanges = formations->depthRangesUpToLevel( formationLevel, plot->depthType() );
|
||||
|
||||
for ( size_t i = 0; i < depthRanges.size(); i++ )
|
||||
{
|
||||
const QString& name = std::get<0>( depthRanges[i] );
|
||||
auto it = nameToColor.find( name );
|
||||
if ( it != nameToColor.end() )
|
||||
colors.emplace( name, it->second );
|
||||
else if ( paletteColors.size() > 0 )
|
||||
colors.emplace( name, paletteColors[i % paletteColors.size()] );
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -2615,8 +2714,6 @@ void RimWellLogTrack::updateFormationNamesOnPlot()
|
||||
return;
|
||||
}
|
||||
|
||||
auto formationLevel = static_cast<RigWellPathFormations::FormationLevel>( m_formationSettings->formationLevel() );
|
||||
|
||||
double rkbDiff = 0.0;
|
||||
if ( plot->depthType() == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH_RKB )
|
||||
{
|
||||
@@ -2643,20 +2740,17 @@ void RimWellLogTrack::updateFormationNamesOnPlot()
|
||||
|
||||
// Formations have a top/base depth range, so they can be drawn as shaded zones (or plain
|
||||
// lines) using the same region-display machinery as the CASE-based formation names below.
|
||||
auto depthRanges = formations->depthRangesUpToLevel( formationLevel, plot->depthType() );
|
||||
auto depthRanges = formationZoneDisplayRanges();
|
||||
if ( !depthRanges.empty() )
|
||||
{
|
||||
std::vector<QString> zoneNamesToPlot;
|
||||
std::vector<std::pair<double, double>> zoneYValues;
|
||||
std::vector<std::pair<double, double>> convertedZoneYValues;
|
||||
for ( const auto& [name, top, base] : depthRanges )
|
||||
{
|
||||
zoneNamesToPlot.push_back( name );
|
||||
zoneYValues.emplace_back( top + rkbDiff, base + rkbDiff );
|
||||
convertedZoneYValues.emplace_back( top, base );
|
||||
}
|
||||
|
||||
std::vector<std::pair<double, double>> convertedZoneYValues =
|
||||
RiaWellLogUnitTools<double>::convertDepths( zoneYValues, fromDepthUnit, toDepthUnit );
|
||||
|
||||
// Build color table ordered by formation name to ensure correct color mapping
|
||||
// when using a legend based on a LYR-file. Falls back to palette index for
|
||||
// formations not found in the legend (e.g., when using a generic color palette).
|
||||
|
||||
@@ -35,10 +35,13 @@
|
||||
#include "cafPdmObject.h"
|
||||
#include "cafPdmPtrField.h"
|
||||
|
||||
#include "cvfColor3.h"
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace RiaDefines
|
||||
@@ -98,9 +101,12 @@ public:
|
||||
RimWellLogTrack();
|
||||
~RimWellLogTrack() override;
|
||||
|
||||
QWidget* viewWidget() override;
|
||||
RiuQwtPlotWidget* viewer();
|
||||
RiuPlotWidget* plotWidget() override;
|
||||
QWidget* viewWidget() override;
|
||||
RiuQwtPlotWidget* viewer();
|
||||
std::map<QString, cvf::Color3ub> formationZoneColors() const;
|
||||
std::vector<std::tuple<QString, double, double>> formationZoneDisplayRanges() const;
|
||||
int formationShadingAlpha() const;
|
||||
RiuPlotWidget* plotWidget() override;
|
||||
|
||||
void zoomAll() override;
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/Riu3DMainWindowTools.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RiuDockWidgetTools.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RiuQwtCurveSelectorFilter.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RiuRftCorrelationPlotTools.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotItemGroup.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RiuQwtPlotTools.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RiuDepthQwtPlot.cpp
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "RiuRftCorrelationPlotTools.h"
|
||||
|
||||
#include "RiuGroupedBarChartBuilder.h"
|
||||
#include "RiuPlotCurve.h"
|
||||
#include "RiuQwtCurveSelectorFilter.h"
|
||||
#include "RiuQwtPlotCurve.h"
|
||||
#include "RiuQwtPlotWidget.h"
|
||||
#include "RiuQwtSymbol.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_plot_zoneitem.h"
|
||||
#include "qwt_scale_map.h"
|
||||
#include "qwt_text.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
namespace
|
||||
{
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Shows the title of the curve closest to the mouse cursor.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
class CurveTracker : public QwtPlotPicker
|
||||
{
|
||||
public:
|
||||
CurveTracker( QwtPlot* plot )
|
||||
: QwtPlotPicker( plot->canvas() )
|
||||
{
|
||||
setStateMachine( new QwtPickerTrackerMachine() );
|
||||
setRubberBand( QwtPicker::NoRubberBand );
|
||||
setTrackerMode( QwtPicker::AlwaysOn );
|
||||
}
|
||||
|
||||
protected:
|
||||
QwtText trackerText( const QPoint& pos ) const override
|
||||
{
|
||||
double minDistance = std::numeric_limits<double>::max();
|
||||
QString closestCurveLabel;
|
||||
|
||||
for ( QwtPlotItem* item : plot()->itemList() )
|
||||
{
|
||||
if ( item->rtti() == QwtPlotItem::Rtti_PlotCurve )
|
||||
{
|
||||
auto curve = static_cast<QwtPlotCurve*>( item );
|
||||
double distance = std::numeric_limits<double>::max();
|
||||
curve->closestPoint( pos, &distance );
|
||||
|
||||
if ( distance < minDistance )
|
||||
{
|
||||
minDistance = distance;
|
||||
closestCurveLabel = curve->title().text();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( minDistance < 20.0 )
|
||||
{
|
||||
QwtText text( closestCurveLabel );
|
||||
text.setBackgroundBrush( QBrush( Qt::white ) );
|
||||
text.setColor( Qt::black );
|
||||
return text;
|
||||
}
|
||||
return QwtText();
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Thin gray translucent bar drawn in pixel width along the left edge (vertical depth axis) or
|
||||
/// bottom edge (horizontal depth axis) of the canvas, spanning a depth interval.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
class DepthIntervalBar : public QwtPlotItem
|
||||
{
|
||||
public:
|
||||
DepthIntervalBar( double top, double base, bool isDepthVertical )
|
||||
: m_top( top )
|
||||
, m_base( base )
|
||||
, m_isDepthVertical( isDepthVertical )
|
||||
{
|
||||
setZ( 5.0 );
|
||||
}
|
||||
|
||||
void draw( QPainter* painter, const QwtScaleMap& xMap, const QwtScaleMap& yMap, const QRectF& canvasRect ) const override
|
||||
{
|
||||
constexpr double barWidth = 6.0;
|
||||
|
||||
QRectF bar;
|
||||
if ( m_isDepthVertical )
|
||||
{
|
||||
const double y0 = yMap.transform( m_top );
|
||||
const double y1 = yMap.transform( m_base );
|
||||
bar = QRectF( canvasRect.left(), std::min( y0, y1 ), barWidth, std::abs( y1 - y0 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
const double x0 = xMap.transform( m_top );
|
||||
const double x1 = xMap.transform( m_base );
|
||||
bar = QRectF( std::min( x0, x1 ), canvasRect.bottom() - barWidth, std::abs( x1 - x0 ), barWidth );
|
||||
}
|
||||
|
||||
painter->fillRect( bar, QColor( 90, 90, 90, 110 ) );
|
||||
}
|
||||
|
||||
private:
|
||||
double m_top;
|
||||
double m_base;
|
||||
bool m_isDepthVertical;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Reports the depth value under a left-button click (not drag) on a depth track canvas.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
class DepthClickFilter : public QObject
|
||||
{
|
||||
public:
|
||||
DepthClickFilter( QwtPlot* plot, QwtAxisId depthAxis, bool isDepthVertical, std::function<void( double )> callback, QObject* parent )
|
||||
: QObject( parent )
|
||||
, m_plot( plot )
|
||||
, m_depthAxis( depthAxis )
|
||||
, m_isDepthVertical( isDepthVertical )
|
||||
, m_callback( std::move( callback ) )
|
||||
{
|
||||
}
|
||||
|
||||
bool eventFilter( QObject*, QEvent* event ) override
|
||||
{
|
||||
if ( !m_plot ) return false;
|
||||
|
||||
if ( event->type() == QEvent::MouseButtonPress )
|
||||
{
|
||||
auto* mouseEvent = static_cast<QMouseEvent*>( event );
|
||||
if ( mouseEvent->button() == Qt::LeftButton ) m_pressPos = mouseEvent->pos();
|
||||
}
|
||||
else if ( event->type() == QEvent::MouseButtonRelease )
|
||||
{
|
||||
auto* mouseEvent = static_cast<QMouseEvent*>( event );
|
||||
if ( mouseEvent->button() == Qt::LeftButton && m_pressPos && ( mouseEvent->pos() - *m_pressPos ).manhattanLength() < 4 )
|
||||
{
|
||||
const double pixel = m_isDepthVertical ? mouseEvent->pos().y() : mouseEvent->pos().x();
|
||||
const double depth = m_plot->invTransform( m_depthAxis, pixel );
|
||||
|
||||
// Defer so the model and plot updates run after this mouse event is fully handled
|
||||
QTimer::singleShot( 0, this, [this, depth]() { m_callback( depth ); } );
|
||||
}
|
||||
m_pressPos.reset();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
QPointer<QwtPlot> m_plot;
|
||||
QwtAxisId m_depthAxis;
|
||||
bool m_isDepthVertical;
|
||||
std::function<void( double )> m_callback;
|
||||
std::optional<QPoint> m_pressPos;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::detachAllItems( RiuQwtPlotWidget* plotWidget )
|
||||
{
|
||||
if ( plotWidget ) plotWidget->qwtPlot()->detachItems();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::attachPointSeries( RiuQwtPlotWidget* plotWidget, const PointSeries& series )
|
||||
{
|
||||
auto* plotCurve = new RiuQwtPlotCurve;
|
||||
plotCurve->setSamplesValues( series.x, series.y );
|
||||
plotCurve->setStyle( QwtPlotCurve::NoCurve );
|
||||
|
||||
auto* symbol = new RiuQwtSymbol( series.isSelected ? RiuPlotCurveSymbol::SYMBOL_XCROSS : RiuPlotCurveSymbol::SYMBOL_ELLIPSE );
|
||||
symbol->setSize( 8, 8 );
|
||||
symbol->setColor( series.color );
|
||||
plotCurve->setSymbol( symbol );
|
||||
|
||||
plotCurve->setTitle( series.title );
|
||||
plotCurve->attach( plotWidget->qwtPlot() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::attachHorizontalLine( RiuQwtPlotWidget* plotWidget,
|
||||
double yValue,
|
||||
Qt::PenStyle style,
|
||||
const QString& label,
|
||||
const QColor& color )
|
||||
{
|
||||
auto* marker = new QwtPlotMarker();
|
||||
marker->setLineStyle( QwtPlotMarker::HLine );
|
||||
marker->setYValue( yValue );
|
||||
QPen pen( color );
|
||||
pen.setStyle( style );
|
||||
pen.setWidth( 1 );
|
||||
marker->setLinePen( pen );
|
||||
|
||||
if ( !label.isEmpty() )
|
||||
{
|
||||
QwtText text( label );
|
||||
text.setColor( Qt::black );
|
||||
marker->setLabel( text );
|
||||
marker->setLabelAlignment( Qt::AlignTop | Qt::AlignLeft );
|
||||
}
|
||||
|
||||
marker->setZ( 1000.0 );
|
||||
marker->attach( plotWidget->qwtPlot() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::attachHorizontalBand( RiuQwtPlotWidget* plotWidget, double minValue, double maxValue, const QColor& color )
|
||||
{
|
||||
auto* shading = new QwtPlotZoneItem();
|
||||
shading->setOrientation( Qt::Horizontal );
|
||||
shading->setInterval( minValue, maxValue );
|
||||
shading->setPen( color, 0.0, Qt::NoPen );
|
||||
shading->setBrush( QBrush( color ) );
|
||||
shading->setZ( 999.0 );
|
||||
shading->attach( plotWidget->qwtPlot() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::installCurveTracker( RiuQwtPlotWidget* plotWidget )
|
||||
{
|
||||
new CurveTracker( plotWidget->qwtPlot() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::installCurveSelector( RiuQwtPlotWidget* plotWidget,
|
||||
std::function<const caf::PdmUiItem*( const QPoint& )> finder )
|
||||
{
|
||||
new RiuQwtCurveSelectorFilter( plotWidget->qwtPlot(), std::move( finder ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
int RiuRftCorrelationPlotTools::closestPointIndex( RiuQwtPlotWidget* plotWidget,
|
||||
const QPoint& canvasPos,
|
||||
const std::vector<std::pair<double, double>>& points )
|
||||
{
|
||||
return RiuQwtCurveSelectorFilter::closestPointIndex( plotWidget->qwtPlot(), canvasPos, points );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::installDepthClickFilter( RiuQwtPlotWidget* plotWidget,
|
||||
const RiuPlotAxis& depthAxis,
|
||||
bool isDepthVertical,
|
||||
std::function<void( double )> callback,
|
||||
QObject* filterParent )
|
||||
{
|
||||
if ( !plotWidget || !plotWidget->qwtPlot() ) return;
|
||||
|
||||
auto* filter =
|
||||
new DepthClickFilter( plotWidget->qwtPlot(), plotWidget->toQwtPlotAxis( depthAxis ), isDepthVertical, std::move( callback ), filterParent );
|
||||
plotWidget->qwtPlot()->canvas()->installEventFilter( filter );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Stale bars are found by type and removed before the new ones are attached.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::setDepthIntervalBars( RiuQwtPlotWidget* plotWidget,
|
||||
const std::vector<DepthInterval>& intervals,
|
||||
bool isDepthVertical )
|
||||
{
|
||||
if ( !plotWidget ) return;
|
||||
|
||||
QwtPlot* qwtPlot = plotWidget->qwtPlot();
|
||||
if ( !qwtPlot ) return;
|
||||
|
||||
// itemList() is a live reference; copy it before deleting items
|
||||
const QwtPlotItemList items = qwtPlot->itemList();
|
||||
for ( QwtPlotItem* item : items )
|
||||
{
|
||||
if ( dynamic_cast<DepthIntervalBar*>( item ) ) delete item;
|
||||
}
|
||||
|
||||
for ( const auto& interval : intervals )
|
||||
{
|
||||
auto* bar = new DepthIntervalBar( interval.top, interval.base, isDepthVertical );
|
||||
bar->attach( qwtPlot );
|
||||
}
|
||||
|
||||
qwtPlot->replot();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::addHorizontalBarChart( RiuQwtPlotWidget* plotWidget, RiuGroupedBarChartBuilder& chartBuilder, int maxBarCount )
|
||||
{
|
||||
chartBuilder.addBarChartToPlot( plotWidget->qwtPlot(), Qt::Horizontal, maxBarCount );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::detachBarChartItems( RiuQwtPlotWidget* plotWidget )
|
||||
{
|
||||
if ( !plotWidget ) return;
|
||||
|
||||
plotWidget->qwtPlot()->detachItems( QwtPlotItem::Rtti_PlotBarChart );
|
||||
plotWidget->qwtPlot()->detachItems( QwtPlotItem::Rtti_PlotScale );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiuRftCorrelationPlotTools::removeLegend( RiuQwtPlotWidget* plotWidget )
|
||||
{
|
||||
plotWidget->qwtPlot()->insertLegend( nullptr );
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class QObject;
|
||||
class QPoint;
|
||||
class RiuGroupedBarChartBuilder;
|
||||
class RiuPlotAxis;
|
||||
class RiuQwtPlotWidget;
|
||||
|
||||
namespace caf
|
||||
{
|
||||
class PdmUiItem;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
/// Qwt specific plot operations used by the RFT correlation plots, kept out of the Rim* classes.
|
||||
//==================================================================================================
|
||||
namespace RiuRftCorrelationPlotTools
|
||||
{
|
||||
struct PointSeries
|
||||
{
|
||||
std::vector<double> x;
|
||||
std::vector<double> y;
|
||||
QString title;
|
||||
QColor color;
|
||||
bool isSelected = false;
|
||||
};
|
||||
|
||||
struct DepthInterval
|
||||
{
|
||||
double top;
|
||||
double base;
|
||||
};
|
||||
|
||||
void detachAllItems( RiuQwtPlotWidget* plotWidget );
|
||||
void attachPointSeries( RiuQwtPlotWidget* plotWidget, const PointSeries& series );
|
||||
void attachHorizontalLine( RiuQwtPlotWidget* plotWidget, double yValue, Qt::PenStyle style, const QString& label, const QColor& color );
|
||||
void attachHorizontalBand( RiuQwtPlotWidget* plotWidget, double minValue, double maxValue, const QColor& color );
|
||||
|
||||
// Shows the title of the closest curve under the mouse cursor
|
||||
void installCurveTracker( RiuQwtPlotWidget* plotWidget );
|
||||
|
||||
// Selects the item returned by finder when the user clicks near a curve point
|
||||
void installCurveSelector( RiuQwtPlotWidget* plotWidget, std::function<const caf::PdmUiItem*( const QPoint& )> finder );
|
||||
int closestPointIndex( RiuQwtPlotWidget* plotWidget, const QPoint& canvasPos, const std::vector<std::pair<double, double>>& points );
|
||||
|
||||
// Reports the depth under a left-button click on the plot canvas. The filter is owned by filterParent.
|
||||
void installDepthClickFilter( RiuQwtPlotWidget* plotWidget,
|
||||
const RiuPlotAxis& depthAxis,
|
||||
bool isDepthVertical,
|
||||
std::function<void( double )> callback,
|
||||
QObject* filterParent );
|
||||
|
||||
// Replaces the thin bars marking depth intervals along the depth axis side of the plot
|
||||
void setDepthIntervalBars( RiuQwtPlotWidget* plotWidget, const std::vector<DepthInterval>& intervals, bool isDepthVertical );
|
||||
|
||||
void addHorizontalBarChart( RiuQwtPlotWidget* plotWidget, RiuGroupedBarChartBuilder& chartBuilder, int maxBarCount );
|
||||
void detachBarChartItems( RiuQwtPlotWidget* plotWidget );
|
||||
void removeLegend( RiuQwtPlotWidget* plotWidget );
|
||||
} // namespace RiuRftCorrelationPlotTools
|
||||
Reference in new issue
Block a user