From 188592410c90bc02d0045485e4afd8f48fea877b Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Fri, 30 Oct 2026 11:02:23 +0100 Subject: [PATCH 1/2] #15078: Add I/J/K connection labels to well allocation plot Add option to label connections by connection number, cell I/J/K, or both, and an option to show a tick label for every connection. Ticks on the connection number axis are placed at integer values only. --- .../Flow/RimWellAllocationPlot.cpp | 86 ++++++++- .../Flow/RimWellAllocationPlot.h | 12 ++ .../WellLog/WellLogTrack/RimWellLogTrack.cpp | 23 +++ .../WellLog/WellLogTrack/RimWellLogTrack.h | 7 + .../Well/RigAccWellFlowCalculator.cpp | 9 + .../Well/RigAccWellFlowCalculator.h | 12 +- ApplicationLibCode/UnitTests/CMakeLists.txt | 1 + .../RigAccWellFlowCalculator-Test.cpp | 63 +++++++ .../UserInterface/RiuWellLogTrack.cpp | 178 ++++++++++++++++++ .../UserInterface/RiuWellLogTrack.h | 5 + 10 files changed, 384 insertions(+), 12 deletions(-) create mode 100644 ApplicationLibCode/UnitTests/RigAccWellFlowCalculator-Test.cpp diff --git a/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.cpp b/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.cpp index 8d4dc8145e3..231e73dea2d 100644 --- a/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.cpp @@ -28,6 +28,7 @@ #include "RigEclipseCaseDataTools.h" #include "RigFlowDiagResultAddress.h" #include "RigFlowDiagResults.h" +#include "RigMainGrid.h" #include "Well/RigAccWellFlowCalculator.h" #include "Well/RigSimWellData.h" #include "Well/RigSimulationWellCenterLineCalculator.h" @@ -76,6 +77,17 @@ void AppEnum::setUp() addItem( RimWellAllocationPlot::INFLOW, "INFLOW", "Inflow Rates" ); setDefault( RimWellAllocationPlot::INFLOW ); } + +template <> +void AppEnum::setUp() +{ + addItem( RimWellAllocationPlot::ConnectionLabelType::CONNECTION_NUMBER, "CONNECTION_NUMBER", "Connection Number" ); + addItem( RimWellAllocationPlot::ConnectionLabelType::CELL_IJK, "CELL_IJK", "Cell (I, J, K)" ); + addItem( RimWellAllocationPlot::ConnectionLabelType::CONNECTION_NUMBER_AND_CELL_IJK, + "CONNECTION_NUMBER_AND_CELL_IJK", + "Connection Number and Cell (I, J, K)" ); + setDefault( RimWellAllocationPlot::ConnectionLabelType::CONNECTION_NUMBER_AND_CELL_IJK ); +} } // namespace caf //-------------------------------------------------------------------------------------------------- @@ -105,6 +117,19 @@ RimWellAllocationPlot::RimWellAllocationPlot() CAF_PDM_InitField( &m_wellName, "WellName", RiaDefines::selectionTextNone(), "Well" ); CAF_PDM_InitFieldNoDefault( &m_flowDiagSolution, "FlowDiagSolution", "Plot Type" ); CAF_PDM_InitFieldNoDefault( &m_flowType, "FlowType", "Flow Type" ); + CAF_PDM_InitFieldNoDefault( &m_connectionLabelType, + "ConnectionLabelType", + "Connection Labels", + "", + "Label the connections on the depth axis by connection number and/or grid cell (I, J, K)", + "" ); + CAF_PDM_InitField( &m_showAllConnections, + "ShowAllConnections", + true, + "Show All Connections", + "", + "Show a tick and label for every connection on the depth axis", + "" ); CAF_PDM_InitField( &m_groupSmallContributions, "GroupSmallContributions", true, "Group Small Contributions" ); CAF_PDM_InitField( &m_smallContributionsThreshold, "SmallContributionsThreshold", 0.005, "Threshold" ); CAF_PDM_InitFieldNoDefault( &m_accumulatedWellFlowPlot, "AccumulatedWellFlowPlot", "Accumulated Well Flow" ); @@ -369,6 +394,11 @@ void RimWellAllocationPlot::updateFromWell() if ( !depthValues.empty() ) { + if ( depthType == RiaDefines::DepthType::CONNECTION_NUMBER ) + { + updateConnectionTickLabels( plotTrack, *wfCalculator, brIdx ); + } + std::vector tracerNames = wfCalculator->tracerNames(); for ( const QString& tracerName : tracerNames ) { @@ -388,13 +418,9 @@ void RimWellAllocationPlot::updateFromWell() } // Shift the "bars" to make connection number tick at the midpoint of the constant value - // when showing in flow rate - if ( m_flowType == INFLOW ) + for ( double& connNum : curveDepthValues ) { - for ( double& connNum : curveDepthValues ) - { - connNum += 0.5; - } + connNum += 0.5; } } else if ( depthType == RiaDefines::DepthType::PSEUDO_LENGTH || depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH ) @@ -496,6 +522,47 @@ void RimWellAllocationPlot::updateWellFlowPlotXAxisTitle( RimWellLogTrack* plotT plotTrack->setPropertyValueAxisTitle( axisTitle ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellAllocationPlot::updateConnectionTickLabels( RimWellLogTrack* plotTrack, + const RigAccWellFlowCalculator& wfCalculator, + size_t branchIdx ) const +{ + std::map tickLabels; + + if ( m_connectionLabelType() != ConnectionLabelType::CONNECTION_NUMBER && m_case && m_case->eclipseCaseData() ) + { + const RigMainGrid* mainGrid = m_case->eclipseCaseData()->mainGrid(); + + const std::vector connectionNumbers = wfCalculator.connectionNumbersFromTop( branchIdx ); + const std::vector& resultPoints = wfCalculator.connectionWellResultPoints( branchIdx ); + + for ( size_t i = 0; i < std::min( connectionNumbers.size(), resultPoints.size() ); ++i ) + { + const RigWellResultPoint& resultPoint = resultPoints[i]; + if ( !mainGrid || !resultPoint.isCell() || resultPoint.gridIndex() >= mainGrid->gridCount() ) continue; + + const RigGridBase* grid = mainGrid->gridByIndex( resultPoint.gridIndex() ); + const auto ijk = grid ? grid->ijkFromCellIndex( resultPoint.cellIndex() ) : std::nullopt; + if ( !ijk ) continue; + + const auto ijk1 = ijk->toOneBased(); + + QString cellText = QString( "(%1, %2, %3)" ).arg( ijk1.x() ).arg( ijk1.y() ).arg( ijk1.z() ); + if ( grid != mainGrid ) cellText = QString::fromStdString( grid->gridName() ) + " " + cellText; + + const int connectionNumber = static_cast( std::round( connectionNumbers[i] ) ); + tickLabels[connectionNumber] = m_connectionLabelType() == ConnectionLabelType::CELL_IJK + ? cellText + : QString( "%1 %2" ).arg( connectionNumber ).arg( cellText ); + } + } + + plotTrack->setDepthAxisTickLabels( tickLabels ); + plotTrack->setDepthAxisIntegerTicks( true, m_showAllConnections() ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -804,7 +871,7 @@ void RimWellAllocationPlot::fieldChangedByUi( const caf::PdmFieldHandle* changed } else if ( changedField == &m_wellName || changedField == &m_timeStep || changedField == &m_flowDiagSolution || changedField == &m_groupSmallContributions || changedField == &m_smallContributionsThreshold || changedField == &m_flowType || - changedField == &m_branchDetection ) + changedField == &m_branchDetection || changedField == &m_connectionLabelType || changedField == &m_showAllConnections ) { onLoadDataAndUpdate(); } @@ -839,6 +906,11 @@ void RimWellAllocationPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOr caf::PdmUiGroup& optionGroup = *uiOrdering.addNewGroup( "Options" ); optionGroup.add( &m_flowDiagSolution ); optionGroup.add( &m_flowType ); + if ( m_accumulatedWellFlowPlot && m_accumulatedWellFlowPlot->depthType() == RiaDefines::DepthType::CONNECTION_NUMBER ) + { + optionGroup.add( &m_connectionLabelType ); + optionGroup.add( &m_showAllConnections ); + } optionGroup.add( &m_groupSmallContributions ); optionGroup.add( &m_smallContributionsThreshold ); m_smallContributionsThreshold.uiCapability()->setUiReadOnly( !m_groupSmallContributions() ); diff --git a/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.h b/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.h index 11c39fe4ab9..40fdce9fb44 100644 --- a/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.h +++ b/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.h @@ -26,6 +26,7 @@ #include +class RigAccWellFlowCalculator; class RigSimWellData; class RimEclipseResultCase; class RimFlowDiagSolution; @@ -62,6 +63,13 @@ class RimWellAllocationPlot : public RimViewWindow INFLOW }; + enum class ConnectionLabelType + { + CONNECTION_NUMBER, + CELL_IJK, + CONNECTION_NUMBER_AND_CELL_IJK + }; + public: RimWellAllocationPlot(); ~RimWellAllocationPlot() override; @@ -110,6 +118,7 @@ class RimWellAllocationPlot : public RimViewWindow void setCase( RimEclipseResultCase* eclipseCase ); void updateFromWell(); void updateWellFlowPlotXAxisTitle( RimWellLogTrack* plotTrack ); + void updateConnectionTickLabels( RimWellLogTrack* plotTrack, const RigAccWellFlowCalculator& wfCalculator, size_t branchIdx ) const; void addStackedCurve( const QString& tracerName, RiaDefines::DepthType depthType, @@ -145,6 +154,9 @@ class RimWellAllocationPlot : public RimViewWindow caf::PdmField m_smallContributionsThreshold; caf::PdmField> m_flowType; + caf::PdmField> m_connectionLabelType; + caf::PdmField m_showAllConnections; + QPointer m_wellAllocationPlotWidget; caf::PdmChildField m_accumulatedWellFlowPlot; diff --git a/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.cpp b/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.cpp index 8d4086b216c..8fc72c7394f 100644 --- a/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.cpp +++ b/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.cpp @@ -1422,6 +1422,27 @@ QString RimWellLogTrack::depthAxisTitle() const return ""; } +//-------------------------------------------------------------------------------------------------- +/// Custom text for depth axis ticks at integer depth values, e.g. cell IJK for connection numbers +//-------------------------------------------------------------------------------------------------- +void RimWellLogTrack::setDepthAxisTickLabels( const std::map& tickLabels ) +{ + m_depthAxisTickLabels = tickLabels; + + if ( m_plotWidget ) m_plotWidget->setDepthAxisTickLabels( m_depthAxisTickLabels ); +} + +//-------------------------------------------------------------------------------------------------- +/// Place depth axis ticks at integer values only, optionally with a major tick at every integer +//-------------------------------------------------------------------------------------------------- +void RimWellLogTrack::setDepthAxisIntegerTicks( bool enabled, bool tickAtEveryInteger ) +{ + m_depthAxisIntegerTicks = enabled; + m_depthAxisTickAtEveryInteger = tickAtEveryInteger; + + if ( m_plotWidget ) m_plotWidget->setDepthAxisIntegerTicks( m_depthAxisIntegerTicks, m_depthAxisTickAtEveryInteger ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -1538,6 +1559,8 @@ RiuPlotWidget* RimWellLogTrack::doCreatePlotViewWidget( QWidget* mainWindowParen { m_plotWidget = new RiuWellLogTrack( this, mainWindowParent ); updateAxisScaleEngine(); + if ( !m_depthAxisTickLabels.empty() ) m_plotWidget->setDepthAxisTickLabels( m_depthAxisTickLabels ); + if ( m_depthAxisIntegerTicks ) m_plotWidget->setDepthAxisIntegerTicks( m_depthAxisIntegerTicks, m_depthAxisTickAtEveryInteger ); for ( size_t cIdx = 0; cIdx < m_curves.size(); ++cIdx ) { diff --git a/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.h b/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.h index cc7f14c5c00..e2b75e487cd 100644 --- a/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.h +++ b/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.h @@ -125,6 +125,9 @@ class RimWellLogTrack : public RimPlot void setPropertyValueAxisTitle( const QString& text ); QString depthAxisTitle() const; + void setDepthAxisTickLabels( const std::map& tickLabels ); + void setDepthAxisIntegerTicks( bool enabled, bool tickAtEveryInteger ); + void setFormationWellPath( RimWellPath* wellPath ); RimWellPath* formationWellPath() const; void setFormationSimWellName( const QString& simWellName ); @@ -405,4 +408,8 @@ class RimWellLogTrack : public RimPlot double m_availablePropertyValueRangeMax; double m_availableDepthRangeMin; double m_availableDepthRangeMax; + + std::map m_depthAxisTickLabels; + bool m_depthAxisIntegerTicks = false; + bool m_depthAxisTickAtEveryInteger = false; }; diff --git a/ApplicationLibCode/ReservoirDataModel/Well/RigAccWellFlowCalculator.cpp b/ApplicationLibCode/ReservoirDataModel/Well/RigAccWellFlowCalculator.cpp index 4c30d2670ed..bda742a052d 100644 --- a/ApplicationLibCode/ReservoirDataModel/Well/RigAccWellFlowCalculator.cpp +++ b/ApplicationLibCode/ReservoirDataModel/Well/RigAccWellFlowCalculator.cpp @@ -196,6 +196,14 @@ std::vector RigAccWellFlowCalculator::connectionNumbersFromTop( size_t b return result; } +//-------------------------------------------------------------------------------------------------- +/// The well result points of the connections, in the same order as connectionNumbersFromTop() +//-------------------------------------------------------------------------------------------------- +const std::vector& RigAccWellFlowCalculator::connectionWellResultPoints( size_t branchIdx ) const +{ + return m_connectionFlowPrBranch[branchIdx].wellResultPoints; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -485,6 +493,7 @@ void RigAccWellFlowCalculator::calculateAccumulatedFlowPrConnection( size_t bran BranchFlow& branchFlow = m_connectionFlowPrBranch[branchIdx]; storeFlowOnDepth( &branchFlow, connNumFromTop, accFlowPrTracer, flowPrTracer ); + branchFlow.wellResultPoints.push_back( wellCell ); --clSegIdx; } diff --git a/ApplicationLibCode/ReservoirDataModel/Well/RigAccWellFlowCalculator.h b/ApplicationLibCode/ReservoirDataModel/Well/RigAccWellFlowCalculator.h index 16f749e6e87..87e3542df29 100644 --- a/ApplicationLibCode/ReservoirDataModel/Well/RigAccWellFlowCalculator.h +++ b/ApplicationLibCode/ReservoirDataModel/Well/RigAccWellFlowCalculator.h @@ -58,9 +58,9 @@ class RigEclCellIndexCalculator #include -#include "cvfVector3.h" +#include "RigWellResultPoint.h" -struct RigWellResultPoint; +#include "cvfVector3.h" class RigAccWellFlowCalculator { @@ -81,9 +81,10 @@ class RigAccWellFlowCalculator const std::vector& pipeBranchMeasuredDepths, bool totalFlowOnly ); - std::vector connectionNumbersFromTop( size_t branchIdx ) const; - const std::vector& accumulatedTracerFlowPrConnection( const QString& tracerName, size_t branchIdx ) const; - const std::vector& tracerFlowPrConnection( const QString& tracerName, size_t branchIdx ) const; + std::vector connectionNumbersFromTop( size_t branchIdx ) const; + const std::vector& connectionWellResultPoints( size_t branchIdx ) const; + const std::vector& accumulatedTracerFlowPrConnection( const QString& tracerName, size_t branchIdx ) const; + const std::vector& tracerFlowPrConnection( const QString& tracerName, size_t branchIdx ) const; const std::vector& pseudoLengthFromTop( size_t branchIdx ) const; const std::vector& trueVerticalDepth( size_t branchIdx ) const; @@ -134,6 +135,7 @@ class RigAccWellFlowCalculator { std::vector depthValuesFromTop; std::vector trueVerticalDepth; + std::vector wellResultPoints; // Only populated for connection based flow std::map> accFlowPrTracer; std::map> flowPrTracer; }; diff --git a/ApplicationLibCode/UnitTests/CMakeLists.txt b/ApplicationLibCode/UnitTests/CMakeLists.txt index 63334065040..44dae4a253f 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists.txt +++ b/ApplicationLibCode/UnitTests/CMakeLists.txt @@ -86,6 +86,7 @@ set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/RifCaseRealizationParametersReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigWellLogExtractor-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigWellPath-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RigAccWellFlowCalculator-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigWellPathGeometryTools-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifEclipseSummaryAddress-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaTimeHistoryCurveTools-Test.cpp diff --git a/ApplicationLibCode/UnitTests/RigAccWellFlowCalculator-Test.cpp b/ApplicationLibCode/UnitTests/RigAccWellFlowCalculator-Test.cpp new file mode 100644 index 00000000000..f50c18ae54c --- /dev/null +++ b/ApplicationLibCode/UnitTests/RigAccWellFlowCalculator-Test.cpp @@ -0,0 +1,63 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "gtest/gtest.h" + +#include "Well/RigAccWellFlowCalculator.h" +#include "Well/RigWellResultPoint.h" + +//-------------------------------------------------------------------------------------------------- +/// The well result points of the connections must be reported in the same order as the connection numbers +//-------------------------------------------------------------------------------------------------- +TEST( RigAccWellFlowCalculatorTest, ConnectionWellResultPointsMatchConnectionNumbers ) +{ + const std::vector cellIndicesFromTop = { 10, 20, 30 }; + + std::vector clCoords; + std::vector resultPoints; + double z = 0.0; + for ( size_t cellIndex : cellIndicesFromTop ) + { + RigWellResultPoint resultPoint; + resultPoint.setGridIndex( 0 ); + resultPoint.setGridCellIndex( cellIndex ); + resultPoint.setFlowData( 1.0, 1.0, 0.0, 0.0 ); + + // Two centerline points per cell, entering and leaving the cell + for ( int i = 0; i < 2; ++i ) + { + clCoords.emplace_back( 0.0, 0.0, z ); + resultPoints.push_back( resultPoint ); + z -= 10.0; + } + } + + RigAccWellFlowCalculator calculator( { clCoords }, { resultPoints }, 0.0 ); + + const std::vector connectionNumbers = calculator.connectionNumbersFromTop( 0 ); + const std::vector& connectionResultCells = calculator.connectionWellResultPoints( 0 ); + + ASSERT_EQ( cellIndicesFromTop.size(), connectionNumbers.size() ); + ASSERT_EQ( connectionNumbers.size(), connectionResultCells.size() ); + + for ( size_t i = 0; i < connectionNumbers.size(); ++i ) + { + const size_t connectionIndexFromTop = static_cast( connectionNumbers[i] ) - 1; + EXPECT_EQ( cellIndicesFromTop[connectionIndexFromTop], connectionResultCells[i].cellIndex() ); + } +} diff --git a/ApplicationLibCode/UserInterface/RiuWellLogTrack.cpp b/ApplicationLibCode/UserInterface/RiuWellLogTrack.cpp index c481ffb7bac..780a8a975cf 100644 --- a/ApplicationLibCode/UserInterface/RiuWellLogTrack.cpp +++ b/ApplicationLibCode/UserInterface/RiuWellLogTrack.cpp @@ -45,6 +45,109 @@ #include +#include +#include + +namespace +{ +//================================================================================================== +/// Scale draw replacing the numeric tick label by a custom text for integer tick values +//================================================================================================== +class RiuDepthAxisTickLabelScaleDraw : public QwtScaleDraw +{ +public: + void setTickLabels( const std::map& tickLabels ) + { + m_tickLabels = tickLabels; + invalidateCache(); + } + + QwtText label( double value ) const override + { + const double rounded = std::round( value ); + if ( std::abs( value - rounded ) < 1.0e-6 ) + { + auto it = m_tickLabels.find( std::lround( value ) ); + if ( it != m_tickLabels.end() ) return QwtText( it->second ); + } + + return QwtScaleDraw::label( value ); + } + +private: + std::map m_tickLabels; +}; + +//================================================================================================== +/// Linear scale engine placing ticks at integer values only. Major ticks are either automatic (with a +/// step of at least one) or at every integer. Minor ticks are placed at the remaining integers. +//================================================================================================== +class RiuIntegerTickScaleEngine : public QwtLinearScaleEngine +{ +public: + enum class Mode + { + OFF, + AUTO_MAJOR_TICKS, + MAJOR_TICK_AT_EVERY_INTEGER + }; + + void setMode( Mode mode ) { m_mode = mode; } + + QwtScaleDiv divideScale( double x1, double x2, int maxMajorSteps, int maxMinorSteps, double stepSize ) const override + { + if ( m_mode == Mode::OFF ) return QwtLinearScaleEngine::divideScale( x1, x2, maxMajorSteps, maxMinorSteps, stepSize ); + + // Avoid creating a huge number of ticks when the axis range is large + const double maxIntegerTickCount = 1000.0; + const bool isRangeLimited = std::abs( x2 - x1 ) < maxIntegerTickCount; + + if ( m_mode == Mode::MAJOR_TICK_AT_EVERY_INTEGER && isRangeLimited ) stepSize = 1.0; + + QwtScaleDiv scaleDiv = QwtLinearScaleEngine::divideScale( x1, x2, maxMajorSteps, 0, stepSize ); + + QList majorTicks = scaleDiv.ticks( QwtScaleDiv::MajorTick ); + if ( majorTicks.size() > 1 ) + { + // Use an integer step of at least one to keep the major ticks at integer values + const double step = std::abs( majorTicks[1] - majorTicks[0] ); + const double integerStep = std::max( 1.0, std::round( step ) ); + if ( std::abs( step - integerStep ) > 1e-6 ) + { + scaleDiv = QwtLinearScaleEngine::divideScale( x1, x2, maxMajorSteps, 0, integerStep ); + majorTicks = scaleDiv.ticks( QwtScaleDiv::MajorTick ); + } + } + + QList minorTicks; + if ( m_mode == Mode::AUTO_MAJOR_TICKS && isRangeLimited ) + { + std::set majorTickValues; + for ( double tick : majorTicks ) + { + majorTickValues.insert( std::llround( tick ) ); + } + + const double first = std::ceil( std::min( x1, x2 ) ); + const double last = std::floor( std::max( x1, x2 ) ); + + for ( double value = first; value <= last; value += 1.0 ) + { + if ( !majorTickValues.contains( std::llround( value ) ) ) minorTicks.push_back( value ); + } + } + + scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks ); + scaleDiv.setTicks( QwtScaleDiv::MediumTick, {} ); + + return scaleDiv; + } + +private: + Mode m_mode = Mode::OFF; +}; +} // namespace + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -90,6 +193,81 @@ void RiuWellLogTrack::setAxisEnabled( QwtAxis::Position axis, bool enabled ) setAxisTitleEnabled( plotAxis, enabled ); } +//-------------------------------------------------------------------------------------------------- +/// Replace the numeric depth axis tick labels by custom text for the given integer depth values. +/// Ticks without a custom text show the numeric value. +//-------------------------------------------------------------------------------------------------- +void RiuWellLogTrack::setDepthAxisTickLabels( const std::map& tickLabels ) +{ + auto depthTrackPlot = m_plotDefinition ? m_plotDefinition->firstAncestorOfType() : nullptr; + if ( !depthTrackPlot ) return; + + const auto qwtAxis = toQwtPlotAxis( depthTrackPlot->depthAxis() ); + + auto tickLabelScaleDraw = dynamic_cast( qwtPlot()->axisScaleDraw( qwtAxis ) ); + if ( !tickLabelScaleDraw ) + { + if ( tickLabels.empty() ) return; + + const QwtScaleDraw* currentScaleDraw = qwtPlot()->axisScaleDraw( qwtAxis ); + + tickLabelScaleDraw = new RiuDepthAxisTickLabelScaleDraw; + tickLabelScaleDraw->setMinimumExtent( currentScaleDraw->minimumExtent() ); + for ( auto component : { QwtAbstractScaleDraw::Backbone, QwtAbstractScaleDraw::Ticks, QwtAbstractScaleDraw::Labels } ) + { + tickLabelScaleDraw->enableComponent( component, currentScaleDraw->hasComponent( component ) ); + } + + // The plot takes ownership of the scale draw + qwtPlot()->setAxisScaleDraw( qwtAxis, tickLabelScaleDraw ); + } + + tickLabelScaleDraw->setTickLabels( tickLabels ); + scheduleReplot(); +} + +//-------------------------------------------------------------------------------------------------- +/// Place depth axis ticks at integer values only, e.g. for connection numbers. When tickAtEveryInteger is +/// set, every integer gets a major tick, otherwise the major ticks are automatic and minor ticks fill in. +//-------------------------------------------------------------------------------------------------- +void RiuWellLogTrack::setDepthAxisIntegerTicks( bool enabled, bool tickAtEveryInteger ) +{ + using Mode = RiuIntegerTickScaleEngine::Mode; + + auto depthTrackPlot = m_plotDefinition ? m_plotDefinition->firstAncestorOfType() : nullptr; + if ( !depthTrackPlot ) return; + + const auto qwtAxis = toQwtPlotAxis( depthTrackPlot->depthAxis() ); + + auto scaleEngine = dynamic_cast( qwtPlot()->axisScaleEngine( qwtAxis ) ); + if ( !scaleEngine ) + { + if ( !enabled ) return; + + const QwtScaleEngine* currentScaleEngine = qwtPlot()->axisScaleEngine( qwtAxis ); + + scaleEngine = new RiuIntegerTickScaleEngine; + scaleEngine->setAttributes( currentScaleEngine->attributes() ); + scaleEngine->setReference( currentScaleEngine->reference() ); + scaleEngine->setMargins( currentScaleEngine->lowerMargin(), currentScaleEngine->upperMargin() ); + + // The plot takes ownership of the scale engine + qwtPlot()->setAxisScaleEngine( qwtAxis, scaleEngine ); + } + + Mode mode = Mode::OFF; + if ( enabled ) + { + mode = tickAtEveryInteger ? Mode::MAJOR_TICK_AT_EVERY_INTEGER : Mode::AUTO_MAJOR_TICKS; + } + scaleEngine->setMode( mode ); + + // Reapply the axis range to recompute the ticks + const QwtInterval currentRange = qwtPlot()->axisInterval( qwtAxis ); + setAxisScale( qwtAxis, currentRange.minValue(), currentRange.maxValue() ); + scheduleReplot(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/UserInterface/RiuWellLogTrack.h b/ApplicationLibCode/UserInterface/RiuWellLogTrack.h index 83cd6729559..889963b7051 100644 --- a/ApplicationLibCode/UserInterface/RiuWellLogTrack.h +++ b/ApplicationLibCode/UserInterface/RiuWellLogTrack.h @@ -23,6 +23,8 @@ #include "qwt_plot.h" +#include + class RimWellLogTrack; class RiuPlotAnnotationTool; class RimPlotAxisAnnotation; @@ -44,6 +46,9 @@ class RiuWellLogTrack : public RiuQwtPlotWidget void setAxisEnabled( QwtAxis::Position axis, bool enabled ); + void setDepthAxisTickLabels( const std::map& tickLabels ); + void setDepthAxisIntegerTicks( bool enabled, bool tickAtEveryInteger ); + void createAnnotationsInPlot( const std::vector& annotations ); private: From 2116d1a6dfdb5444acf90782a66cb03bd131c911 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Fri, 30 Oct 2026 11:58:57 +0100 Subject: [PATCH 2/2] #15078: Include cell I/J/K in well allocation plot data --- .../Flow/RimWellAllocationPlot.cpp | 24 ++++++++++---- .../WellLog/WellLogTrack/RimWellLogTrack.cpp | 31 ++++++++++++------- .../WellLog/WellLogTrack/RimWellLogTrack.h | 2 ++ 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.cpp b/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.cpp index 231e73dea2d..f7efe99b341 100644 --- a/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/Flow/RimWellAllocationPlot.cpp @@ -125,7 +125,7 @@ RimWellAllocationPlot::RimWellAllocationPlot() "" ); CAF_PDM_InitField( &m_showAllConnections, "ShowAllConnections", - true, + false, "Show All Connections", "", "Show a tick and label for every connection on the depth axis", @@ -411,8 +411,8 @@ void RimWellAllocationPlot::updateFromWell() if ( !accFlow.empty() ) { - // Add a dummy zero connection number at the end to make the curve reach the y-axis when showing in flow rate. The - // lowest connection number is 1.0, so adding a zero value will not cause any issue when showing in accumulated flow. + // Add a dummy zero connection number at the end to make the curve reach the y-axis. The lowest connection number + // is 1.0, so the dummy point (shifted to 0.5 below) stays before the first connection in both flow types. accFlow.push_back( accFlow.back() ); curveDepthValues.push_back( 0.0 ); } @@ -503,6 +503,7 @@ void RimWellAllocationPlot::updateFromWell() m_totalWellAllocationPlot->updateConnectedEditors(); accumulatedWellFlowPlot()->updateConnectedEditors(); + updateConnectedEditors(); m_tofAccumulatedPhaseFractionsPlot->reloadFromWell(); m_tofAccumulatedPhaseFractionsPlot->updateConnectedEditors(); @@ -529,14 +530,15 @@ void RimWellAllocationPlot::updateConnectionTickLabels( RimWellLogTrack* const RigAccWellFlowCalculator& wfCalculator, size_t branchIdx ) const { - std::map tickLabels; + std::map cellTexts; - if ( m_connectionLabelType() != ConnectionLabelType::CONNECTION_NUMBER && m_case && m_case->eclipseCaseData() ) + if ( m_case && m_case->eclipseCaseData() ) { const RigMainGrid* mainGrid = m_case->eclipseCaseData()->mainGrid(); const std::vector connectionNumbers = wfCalculator.connectionNumbersFromTop( branchIdx ); const std::vector& resultPoints = wfCalculator.connectionWellResultPoints( branchIdx ); + CAF_ASSERT( connectionNumbers.size() == resultPoints.size() ); for ( size_t i = 0; i < std::min( connectionNumbers.size(), resultPoints.size() ); ++i ) { @@ -552,13 +554,23 @@ void RimWellAllocationPlot::updateConnectionTickLabels( RimWellLogTrack* QString cellText = QString( "(%1, %2, %3)" ).arg( ijk1.x() ).arg( ijk1.y() ).arg( ijk1.z() ); if ( grid != mainGrid ) cellText = QString::fromStdString( grid->gridName() ) + " " + cellText; - const int connectionNumber = static_cast( std::round( connectionNumbers[i] ) ); + const int connectionNumber = std::lround( connectionNumbers[i] ); + cellTexts[connectionNumber] = cellText; + } + } + + std::map tickLabels; + if ( m_connectionLabelType() != ConnectionLabelType::CONNECTION_NUMBER ) + { + for ( const auto& [connectionNumber, cellText] : cellTexts ) + { tickLabels[connectionNumber] = m_connectionLabelType() == ConnectionLabelType::CELL_IJK ? cellText : QString( "%1 %2" ).arg( connectionNumber ).arg( cellText ); } } + plotTrack->setConnectionCellTexts( cellTexts ); plotTrack->setDepthAxisTickLabels( tickLabels ); plotTrack->setDepthAxisIntegerTicks( true, m_showAllConnections() ); } diff --git a/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.cpp b/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.cpp index 8fc72c7394f..6c11ec9d165 100644 --- a/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.cpp +++ b/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.cpp @@ -803,16 +803,11 @@ QString RimWellLogTrack::asciiDataForPlotExport() const std::vector curveDepths; std::vector> curvesPlotXValues; - auto depthType = parentWellLogPlot()->depthType(); - auto depthUnit = parentWellLogPlot()->depthUnit(); - bool isWellAllocInflowPlot = false; - { - auto wapl = parentWellLogPlot()->firstAncestorOfType(); - if ( wapl ) - { - isWellAllocInflowPlot = ( wapl->flowType() == RimWellAllocationPlot::INFLOW ); - } - } + auto depthType = parentWellLogPlot()->depthType(); + auto depthUnit = parentWellLogPlot()->depthUnit(); + bool isWellAllocPlot = parentWellLogPlot()->firstAncestorOfType() != nullptr; + + const bool exportCellTexts = depthType == RiaDefines::DepthType::CONNECTION_NUMBER && !m_connectionCellTexts.empty(); RiaWellLogCurveMerger curveMerger; bool foundNonMatchingDepths = false; @@ -857,6 +852,7 @@ QString RimWellLogTrack::asciiDataForPlotExport() const if ( depthType == RiaDefines::DepthType::CONNECTION_NUMBER ) { out += "Connection"; + if ( exportCellTexts ) out += " \tCell (I, J, K)"; } else if ( depthType == RiaDefines::DepthType::MEASURED_DEPTH ) { @@ -922,13 +918,18 @@ QString RimWellLogTrack::asciiDataForPlotExport() const curveDepth = curveDepths[i]; - if ( isWellAllocInflowPlot ) + if ( isWellAllocPlot ) { curveDepth -= 0.5; // To shift the values that was shifted to get the numbers between the changes } } out += QString::number( curveDepth, 'f', 3 ); + if ( exportCellTexts ) + { + auto it = m_connectionCellTexts.find( std::lround( curveDepth ) ); + out += " \t" + ( it != m_connectionCellTexts.end() ? it->second : QString( "-" ) ); + } for ( std::vector plotVector : curvesPlotXValues ) { out += QString( " \t%1" ).arg( QString::number( plotVector[i], 'f', 3 ), 12 ); @@ -1432,6 +1433,14 @@ void RimWellLogTrack::setDepthAxisTickLabels( const std::map& tick if ( m_plotWidget ) m_plotWidget->setDepthAxisTickLabels( m_depthAxisTickLabels ); } +//-------------------------------------------------------------------------------------------------- +/// Grid cell text for each connection number, included in the plot data export +//-------------------------------------------------------------------------------------------------- +void RimWellLogTrack::setConnectionCellTexts( const std::map& cellTexts ) +{ + m_connectionCellTexts = cellTexts; +} + //-------------------------------------------------------------------------------------------------- /// Place depth axis ticks at integer values only, optionally with a major tick at every integer //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.h b/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.h index e2b75e487cd..ea158f346bf 100644 --- a/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.h +++ b/ApplicationLibCode/ProjectDataModel/WellLog/WellLogTrack/RimWellLogTrack.h @@ -126,6 +126,7 @@ class RimWellLogTrack : public RimPlot QString depthAxisTitle() const; void setDepthAxisTickLabels( const std::map& tickLabels ); + void setConnectionCellTexts( const std::map& cellTexts ); void setDepthAxisIntegerTicks( bool enabled, bool tickAtEveryInteger ); void setFormationWellPath( RimWellPath* wellPath ); @@ -410,6 +411,7 @@ class RimWellLogTrack : public RimPlot double m_availableDepthRangeMax; std::map m_depthAxisTickLabels; + std::map m_connectionCellTexts; bool m_depthAxisIntegerTicks = false; bool m_depthAxisTickAtEveryInteger = false; };