Files
ResInsight/ApplicationLibCode/UserInterface/RiuMultiPlotPage.cpp
T
Magne Sjaastad 4e3a539e4c #15050: Split multi plot common legend into color and symbol legends
Show the color and symbol legends as two widgets below the page title, also when exporting the page. Falls back to the merged legend otherwise.
2026-10-28 14:07:00 +01:00

1352 lines
50 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2015- Statoil ASA
// Copyright (C) 2015- Ceetron Solutions AS
//
// 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 "RiuMultiPlotPage.h"
#include "RiaApplication.h"
#include "RiaGuiApplication.h"
#include "RiaPlotDefines.h"
#include "RiaPlotWindowRedrawScheduler.h"
#include "RiaPreferences.h"
#include "RimContextCommandBuilder.h"
#include "RimMultiPlot.h"
#include "RimPlotCurve.h"
#include "RimPlotWindow.h"
#include "RiuDraggableOverlayFrame.h"
#include "RiuGuiTheme.h"
#include "RiuMainWindow.h"
#include "RiuPlotMainWindow.h"
#include "RiuPlotObjectPicker.h"
#include "RiuPlotWidget.h"
#include "RiuQwtLegendOverlayContentFrame.h"
#include "RiuQwtPlotLegend.h"
#include "RiuQwtPlotTools.h"
#include "RiuQwtPlotWidget.h"
#include "cafCmdFeatureMenuBuilder.h"
#include "cafSelectionManager.h"
#include "cafAssert.h"
#include "qwt_axis.h"
#include "qwt_legend.h"
#include "qwt_legend_label.h"
#include "qwt_plot_layout.h"
#include "qwt_plot_renderer.h"
#include "qwt_scale_draw.h"
#include "qwt_scale_widget.h"
#include <QDebug>
#include <QFocusEvent>
#include <QFontMetrics>
#include <QGraphicsDropShadowEffect>
#include <QHBoxLayout>
#include <QMenu>
#include <QPainter>
#include <QScrollBar>
#include <QTimer>
#include <QWidget>
#include <cmath>
#include <optional>
namespace
{
bool useCommonLegend( const RimPlotWindow* plotWindow )
{
auto multiPlot = dynamic_cast<const RimMultiPlot*>( plotWindow );
return multiPlot && multiPlot->useCommonLegend();
}
} // namespace
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuMultiPlotPage::RiuMultiPlotPage( RimPlotWindow* plotDefinition, QWidget* parent )
: QWidget( parent )
, m_plotDefinition( plotDefinition )
, m_previewMode( false )
, m_showSubTitles( false )
, m_autoAlignAxes( true )
, m_titleFontSize( 12 )
, m_subTitleFontSize( 11 )
, m_legendFontSize( 8 )
, m_axisTitleFontSize( 8 )
, m_axisValueFontSize( 8 )
{
CAF_ASSERT( m_plotDefinition );
m_layout = new QVBoxLayout( this );
m_layout->setContentsMargins( 0, 0, 0, 0 );
m_layout->setSpacing( 4 );
m_plotTitle = createTitleLabel();
m_layout->addWidget( m_plotTitle );
m_plotTitle->setVisible( true );
m_plotLayout = new QHBoxLayout;
m_layout->addLayout( m_plotLayout );
m_plotWidgetFrame = new QFrame;
m_plotLayout->addWidget( m_plotWidgetFrame, 1 );
m_plotWidgetFrame->setVisible( true );
m_gridLayout = new QGridLayout( m_plotWidgetFrame );
m_gridLayout->setContentsMargins( 0, 0, 0, 0 );
m_gridLayout->setSpacing( 0 );
new RiuPlotObjectPicker( m_plotTitle, m_plotDefinition );
setFocusPolicy( Qt::StrongFocus );
setAcceptDrops( true );
setObjectName( QString( "%1" ).arg( reinterpret_cast<uint64_t>( this ) ) );
applyLook();
updateMarginsFromPageLayout();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuMultiPlotPage::~RiuMultiPlotPage()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimViewWindow* RiuMultiPlotPage::ownerViewWindow() const
{
return m_plotDefinition;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimPlotWindow* RiuMultiPlotPage::ownerPlotDefinition()
{
return m_plotDefinition;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::addPlot( RiuPlotWidget* plotWidget )
{
// Insert the plot to the left of the scroll bar
insertPlot( plotWidget, m_plotWidgets.size() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::insertPlot( RiuPlotWidget* plotWidget, size_t index )
{
m_plotWidgets.insert( static_cast<int>( index ), plotWidget );
plotWidget->setVisible( false );
QString subTitleText = plotWidget->plotTitle();
QLabel* subTitle = new QLabel( subTitleText );
subTitle->setAlignment( Qt::AlignHCenter | Qt::AlignBottom );
subTitle->setWordWrap( true );
subTitle->setVisible( false );
m_subTitles.insert( static_cast<int>( index ), subTitle );
// Remove legend overlays already attached to the plot widget
auto matcher = []( RiuDraggableOverlayFrame* p )
{ return dynamic_cast<RiuQwtLegendOverlayContentFrame*>( p->contentFrame() ) != nullptr; };
plotWidget->clearOverlayFrames( matcher );
RiuQwtPlotWidget* qwtPlotWidget = dynamic_cast<RiuQwtPlotWidget*>( plotWidget );
RiuQwtPlotLegend* legend = nullptr;
RiuDraggableOverlayFrame* legendFrame = new RiuDraggableOverlayFrame( plotWidget->getParentForOverlay(), plotWidget->overlayMargins() );
if ( m_plotDefinition->legendsVisible() && plotWidget->plotDefinition()->legendsVisible() )
{
legend = new RiuQwtPlotLegend( this );
int legendColumns = 1;
if ( m_plotDefinition->legendsHorizontal() )
{
legendColumns = 0; // unlimited
}
legend->setMaxColumns( legendColumns );
legend->horizontalScrollBar()->setVisible( false );
legend->verticalScrollBar()->setVisible( false );
// The legend item mode must be set before the widget is created
// See https://qwt.sourceforge.io/class_qwt_legend.html#af977ff3e749f8281ee8ad4b926542b50
auto legendItemMode = m_plotDefinition->legendItemsClickable() ? QwtLegendData::Clickable : QwtLegendData::ReadOnly;
legend->setDefaultItemMode( legendItemMode );
if ( qwtPlotWidget )
{
legend->connect( qwtPlotWidget->qwtPlot(),
SIGNAL( legendDataChanged( const QVariant&, const QList<QwtLegendData>& ) ),
SLOT( updateLegend( const QVariant&, const QList<QwtLegendData>& ) ) );
qwtPlotWidget->connect( legend, SIGNAL( clicked( const QVariant&, int ) ), SLOT( onLegendClicked( const QVariant&, int ) ) );
}
QObject::connect( legend, SIGNAL( legendUpdated() ), this, SLOT( onLegendUpdated() ) );
legend->contentsWidget()->layout()->setAlignment( Qt::AlignBottom | Qt::AlignHCenter );
legend->setVisible( false );
legendFrame->setVisible( false );
plotWidget->updateLegend();
}
m_legends.insert( static_cast<int>( index ), legend );
m_legendFrames.insert( static_cast<int>( index ), legendFrame );
scheduleUpdate();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::removePlot( RiuPlotWidget* plotWidget )
{
if ( !plotWidget ) return;
int plotWidgetIdx = m_plotWidgets.indexOf( plotWidget );
if ( plotWidgetIdx < 0 ) return;
m_plotWidgets.removeAt( plotWidgetIdx );
plotWidget->setParent( nullptr );
RiuQwtPlotLegend* legend = m_legends[plotWidgetIdx];
if ( legend )
{
legend->setParent( nullptr );
delete legend;
}
m_legends.removeAt( plotWidgetIdx );
QLabel* subTitle = m_subTitles[plotWidgetIdx];
subTitle->setParent( nullptr );
m_subTitles.removeAt( plotWidgetIdx );
delete subTitle;
m_childCountForAdjustSizeOperation.clear();
scheduleUpdate();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::removeAllPlots()
{
auto plotWidgets = m_plotWidgets;
for ( RiuPlotWidget* plotWidget : plotWidgets )
{
removePlot( plotWidget );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setPlotTitle( const QString& plotTitle )
{
m_plotTitle->setText( plotTitle );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setTitleVisible( bool visible )
{
m_plotTitle->setVisible( visible );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setTitleFontSizes( int titleFontSize, int subTitleFontSize )
{
m_titleFontSize = titleFontSize;
m_subTitleFontSize = subTitleFontSize;
scheduleUpdate();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setLegendFontSize( int legendFontSize )
{
m_legendFontSize = legendFontSize;
scheduleUpdate();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setAxisFontSizes( int axisTitleFontSize, int axisValueFontSize )
{
m_axisTitleFontSize = axisTitleFontSize;
m_axisValueFontSize = axisValueFontSize;
scheduleUpdate();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setSubTitlesVisible( bool visible )
{
m_showSubTitles = visible;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuMultiPlotPage::previewModeEnabled() const
{
return m_previewMode;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setPagePreviewModeEnabled( bool previewMode )
{
m_previewMode = previewMode;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiuMultiPlotPage::indexOfPlotWidget( RiuPlotWidget* plotWidget )
{
return m_plotWidgets.indexOf( plotWidget );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::scheduleUpdate( RiaDefines::MultiPlotPageUpdateType whatToUpdate )
{
CAF_ASSERT( m_plotDefinition );
RiaPlotWindowRedrawScheduler::instance()->scheduleMultiPlotPageUpdate( this, whatToUpdate );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::scheduleReplotOfAllPlots()
{
for ( RiuPlotWidget* plotWidget : visiblePlotWidgets() )
{
plotWidget->scheduleReplot();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setHostsCommonLegend( bool hostsCommonLegend )
{
m_hostsCommonLegend = hostsCommonLegend;
}
//--------------------------------------------------------------------------------------------------
/// With a common legend, the legends of the individual sub plots are not displayed
//--------------------------------------------------------------------------------------------------
bool RiuMultiPlotPage::showSubPlotLegends() const
{
return !useCommonLegend( m_plotDefinition );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updateSubTitles()
{
for ( int i = 0; i < m_plotWidgets.size(); ++i )
{
if ( m_plotWidgets[i] && m_plotWidgets[i]->isChecked() )
{
m_subTitles[i]->setText( m_plotWidgets[i]->plotTitle() );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::renderTo( QPaintDevice* paintDevice )
{
auto scaling = RiaDefines::scalingFactor( paintDevice );
QPainter painter( paintDevice );
renderTo( &painter, scaling );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::renderTo( QPainter* painter, double scalingFactor )
{
painter->fillRect( painter->viewport(), Qt::white );
m_plotTitle->render( painter );
// Subtract margins because we are rendering into part inside the margins
QPoint marginOffset( m_layout->contentsMargins().left(), m_layout->contentsMargins().top() );
for ( auto subTitle : subTitlesForVisiblePlots() )
{
if ( subTitle->isVisible() )
{
QPoint renderOffset = m_plotWidgetFrame->mapToParent( subTitle->frameGeometry().topLeft() ) - marginOffset;
subTitle->render( painter, renderOffset );
}
}
for ( auto legend : legendsForVisiblePlots() )
{
if ( legend && !legend->isHidden() )
{
QPoint renderOffset = m_plotWidgetFrame->mapToParent( legend->frameGeometry().topLeft() ) - marginOffset;
legend->render( painter, renderOffset );
}
}
// The common legends are direct children of the page, not of the plot widget frame
for ( RiuQwtPlotLegend* commonLegend : { m_commonLegend.data(), m_commonSymbolLegend.data() } )
{
if ( commonLegend && !commonLegend->isHidden() )
{
QPoint renderOffset = commonLegend->frameGeometry().topLeft() - marginOffset;
commonLegend->render( painter, renderOffset );
}
}
for ( auto plotWidget : visiblePlotWidgets() )
{
QRect plotWidgetGeometry = plotWidget->frameGeometry();
QPoint plotWidgetTopLeft = plotWidgetGeometry.topLeft();
QPoint plotWidgetFrameTopLeft = m_plotWidgetFrame->frameGeometry().topLeft();
plotWidgetGeometry.moveTo( plotWidgetTopLeft + plotWidgetFrameTopLeft - marginOffset );
plotWidget->renderTo( painter, plotWidgetGeometry, scalingFactor );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::contextMenuEvent( QContextMenuEvent* event )
{
QMenu menu;
caf::CmdFeatureMenuBuilder menuBuilder;
caf::SelectionManager::instance()->setSelectedItem( ownerPlotDefinition() );
menuBuilder << "RicShowPlotDataFeature";
menuBuilder << "RicShowContributingWellsFromPlotFeature";
menuBuilder << "RicNewDefaultSummaryPlotFeature";
if ( auto* pdmObject = dynamic_cast<caf::PdmUiObjectHandle*>( ownerPlotDefinition() ) )
{
pdmObject->appendMenuItems( menuBuilder );
}
menuBuilder.appendToMenu( &menu );
if ( !menu.actions().empty() )
{
menu.exec( event->globalPos() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QLabel* RiuMultiPlotPage::createTitleLabel() const
{
QLabel* plotTitle = new QLabel( "", nullptr );
plotTitle->setAlignment( Qt::AlignHCenter );
plotTitle->setWordWrap( true );
plotTitle->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
return plotTitle;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::showEvent( QShowEvent* event )
{
QWidget::showEvent( event );
performUpdate( RiaDefines::MultiPlotPageUpdateType::ALL );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuMultiPlotPage::hasHeightForWidth() const
{
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiuMultiPlotPage::heightForWidth( int width ) const
{
// The page aspect ratio is only relevant in page preview mode. Otherwise, a parent scroll area (e.g. a dock widget) will size the
// page taller than the visible area, and the bottom of the plot is clipped.
if ( !m_previewMode ) return -1;
QPageLayout pageLayout = m_plotDefinition->pageLayout();
QRectF rect = pageLayout.fullRectPoints();
double aspectRatio = rect.height() / rect.width();
int height = static_cast<int>( width * aspectRatio );
return height;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updateMarginsFromPageLayout()
{
if ( m_previewMode )
{
QPageLayout pageLayout = m_plotDefinition->pageLayout();
const int resolution = RiaGuiApplication::applicationResolution();
QMargins margins = pageLayout.marginsPixels( resolution );
m_layout->setContentsMargins( margins );
}
else
{
m_layout->setContentsMargins( 0, 0, 0, 0 );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QSize RiuMultiPlotPage::sizeHint() const
{
if ( m_previewMode )
{
QPageLayout pageLayout = RiaPreferences::current()->defaultPageLayout();
if ( m_plotDefinition )
{
pageLayout = m_plotDefinition->pageLayout();
}
const int resolution = RiaGuiApplication::applicationResolution();
QRect rect = pageLayout.fullRectPixels( resolution );
return rect.size();
}
return QWidget::sizeHint();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QSize RiuMultiPlotPage::minimumSizeHint() const
{
QSize fullSizeHint = sizeHint();
return QSize( fullSizeHint.width() / 2, fullSizeHint.height() / 2 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<int, int> RiuMultiPlotPage::rowAndColumnCount( int plotWidgetCount ) const
{
if ( plotWidgetCount == 0 )
{
return std::make_pair( 0, 0 );
}
int columnCount = std::max( 1, m_plotDefinition->columnCount() );
int rowCount = static_cast<int>( std::ceil( plotWidgetCount / static_cast<double>( columnCount ) ) );
return std::make_pair( rowCount, columnCount );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuMultiPlotPage::showYAxis( int row, int column ) const
{
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::performUpdate( RiaDefines::MultiPlotPageUpdateType whatToUpdate )
{
auto multiPlot = dynamic_cast<RimMultiPlot*>( m_plotDefinition.p() );
if ( multiPlot && !multiPlot->isValid() ) return;
if ( RiaDefines::isFullUpdate( whatToUpdate ) )
{
applyLook();
updateTitleFont();
updateMarginsFromPageLayout();
reinsertPlotWidgets();
alignCanvasTops();
if ( m_autoAlignAxes ) alignAxes();
updatePlotLayouts();
return;
}
if ( RiaDefines::isLegendUpdate( whatToUpdate ) )
{
refreshLegends();
alignCanvasTops();
if ( m_autoAlignAxes ) alignAxes();
}
if ( RiaDefines::isTitleUpdate( whatToUpdate ) )
{
updateTitleFont();
updateSubTitles();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::onLegendUpdated()
{
scheduleUpdate( RiaDefines::MultiPlotPageUpdateType::LEGEND );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::refreshLegends()
{
// The common legend is rebuilt from the sub plot legend data. A full layout rebuild here could trigger new legend
// data changes in the plots, and thereby an endless update loop
if ( useCommonLegend( m_plotDefinition ) ) updateCommonLegend();
QList<QPointer<RiuQwtPlotLegend>> legends = legendsForVisiblePlots();
for ( const auto& l : legends )
{
if ( !l ) continue;
if ( !showSubPlotLegends() )
{
l->hide();
continue;
}
l->setVisible( !l->isEmpty() );
updateLegendFont( l );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updatePlotLayouts()
{
QList<QPointer<RiuPlotWidget>> plotWidgets = visiblePlotWidgets();
for ( auto p : plotWidgets )
p->updateLayout();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::reinsertPlotWidgets()
{
clearGridLayout();
QList<QPointer<QLabel>> subTitles = subTitlesForVisiblePlots();
QList<QPointer<RiuQwtPlotLegend>> legends = legendsForVisiblePlots();
QList<QPointer<RiuDraggableOverlayFrame>> legendFrames = legendFramesForVisiblePlots();
QList<QPointer<RiuPlotWidget>> plotWidgets = visiblePlotWidgets();
m_visibleIndexToPositionMapping.clear();
if ( !plotWidgets.empty() )
{
auto [rowCount, columnCount] = rowAndColumnCount( plotWidgets.size() );
int row = 0;
int column = 0;
for ( int visibleIndex = 0; visibleIndex < plotWidgets.size(); ++visibleIndex )
{
auto plotWidget = plotWidgets[visibleIndex];
auto legend = legends[visibleIndex];
auto legendFrame = legendFrames[visibleIndex];
auto subTitle = subTitles[visibleIndex];
int expectedColSpan = plotWidget->colSpan();
int colSpan = std::min( expectedColSpan, columnCount );
int rowSpan = plotWidget->rowSpan();
auto position = findAvailableRowAndColumn( row, column, colSpan, columnCount );
std::tie( row, column ) = position;
m_visibleIndexToPositionMapping[visibleIndex] = position;
reinsertPlotWidget( plotWidget, legend, legendFrame, subTitle, row, column, rowSpan, colSpan );
}
}
updateCommonLegend();
}
//--------------------------------------------------------------------------------------------------
/// Build one legend from the legend data of all sub plots. Entries with identical title and icon are merged.
///
/// For summary curves where the color is given by one dimension (e.g. vector) and the symbol by another (e.g.
/// case), one color legend and one symbol legend are shown instead, to avoid one entry per combination.
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updateCommonLegend()
{
if ( m_commonLegend ) m_layout->removeWidget( m_commonLegend );
if ( m_commonSymbolLegend ) m_layout->removeWidget( m_commonSymbolLegend );
QList<QList<QwtLegendData>> legendData;
QList<QList<QwtLegendData>> symbolLegendData;
bool isActive = useCommonLegend( m_plotDefinition ) && m_hostsCommonLegend && m_plotDefinition->legendsVisible();
if ( isActive )
{
std::vector<QwtPlot*> qwtPlots;
std::vector<RimPlotCurve*> curves;
for ( auto plotWidget : visiblePlotWidgets() )
{
auto plot = plotWidget->plotDefinition();
if ( plot && !plot->legendsVisible() ) continue;
if ( plot )
{
auto plotCurves = plot->visibleCurvesForLegend();
curves.insert( curves.end(), plotCurves.begin(), plotCurves.end() );
}
if ( auto qwtPlotWidget = dynamic_cast<RiuQwtPlotWidget*>( plotWidget.data() ) )
{
qwtPlots.push_back( qwtPlotWidget->qwtPlot() );
}
}
if ( !RiuQwtPlotTools::createColorAndSymbolLegendData( curves, legendData, symbolLegendData ) )
{
legendData = RiuQwtPlotTools::uniqueLegendData( qwtPlots );
}
}
// The common legends are placed below the title, centered above all sub plots
updateCommonLegendWidget( m_commonLegend, m_commonLegendItemCount, legendData, 1 );
updateCommonLegendWidget( m_commonSymbolLegend, m_commonSymbolLegendItemCount, symbolLegendData, 2 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updateCommonLegendWidget( QPointer<RiuQwtPlotLegend>& legend,
int& itemCount,
const QList<QList<QwtLegendData>>& legendData,
int layoutIndex )
{
if ( legendData.isEmpty() )
{
if ( legend ) legend->hide();
return;
}
if ( !legend )
{
legend = new RiuQwtPlotLegend( this );
legend->horizontalScrollBar()->setVisible( false );
legend->verticalScrollBar()->setVisible( false );
legend->setDefaultItemMode( QwtLegendData::ReadOnly );
legend->contentsWidget()->layout()->setAlignment( Qt::AlignBottom | Qt::AlignHCenter );
legend->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Maximum );
}
for ( int i = 0; i < itemCount; i++ )
{
legend->updateLegend( QVariant( i ), {} );
}
itemCount = 0;
for ( const auto& data : legendData )
{
legend->updateLegend( QVariant( itemCount++ ), data );
}
m_layout->insertWidget( layoutIndex, legend, 0, Qt::AlignHCenter );
updateLegendColumns( legend );
updateLegendFont( legend );
legend->show();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::reinsertPlotWidget( RiuPlotWidget* plotWidget,
RiuQwtPlotLegend* legend,
RiuDraggableOverlayFrame* legendFrame,
QLabel* subTitle,
int row,
int column,
int rowSpan,
int colSpan )
{
if ( !showSubPlotLegends() )
{
if ( legend ) legend->hide();
if ( legendFrame )
{
legendFrame->hide();
plotWidget->removeOverlayFrame( legendFrame );
}
legend = nullptr;
}
m_gridLayout->addWidget( subTitle, 3 * row, column, 1, colSpan );
addLegendWidget( plotWidget, legend, legendFrame, 3 * row + 1, column, colSpan );
m_gridLayout->addWidget( plotWidget, 3 * row + 2, column, 1 + ( rowSpan - 1 ) * 3, colSpan );
updateSubTitleVisibility( subTitle );
setDefaultAxisProperties( plotWidget, row, column );
adjustHeadingSpacing( plotWidget );
plotWidget->show();
updateLegendVisibility( plotWidget, legend, legendFrame );
setRowAndColumnStretches( row, column, rowSpan, colSpan );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::addLegendWidget( RiuPlotWidget* plotWidget,
RiuQwtPlotLegend* legend,
RiuDraggableOverlayFrame* legendFrame,
int row,
int column,
int colSpan )
{
if ( !legend ) return;
if ( m_plotDefinition->legendPosition() == RimPlotWindow::LegendPosition::ABOVE )
{
m_gridLayout->addWidget( legend, row, column, 1, colSpan, Qt::AlignHCenter | Qt::AlignBottom );
}
else
{
auto anchor = RiuDraggableOverlayFrame::AnchorCorner::TopRight;
if ( m_plotDefinition->legendPosition() == RimPlotWindow::LegendPosition::INSIDE_UPPER_RIGHT )
anchor = RiuDraggableOverlayFrame::AnchorCorner::TopRight;
else if ( m_plotDefinition->legendPosition() == RimPlotWindow::LegendPosition::INSIDE_UPPER_LEFT )
anchor = RiuDraggableOverlayFrame::AnchorCorner::TopLeft;
auto overlayFrame = new RiuQwtLegendOverlayContentFrame;
overlayFrame->setLegend( legend );
legendFrame->setContentFrame( overlayFrame );
legendFrame->setAnchorCorner( anchor );
plotWidget->removeOverlayFrame( legendFrame );
plotWidget->addOverlayFrame( legendFrame );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updateLegendVisibility( RiuPlotWidget* plotWidget, RiuQwtPlotLegend* legend, RiuDraggableOverlayFrame* legendFrame )
{
if ( !legend ) return;
if ( m_plotDefinition->legendsVisible() && !legend->isEmpty() )
{
updateLegendColumns( legend );
updateLegendFont( legend );
legend->show();
if ( m_plotDefinition->legendPosition() == RimPlotWindow::LegendPosition::INSIDE_UPPER_LEFT ||
m_plotDefinition->legendPosition() == RimPlotWindow::LegendPosition::INSIDE_UPPER_RIGHT )
{
plotWidget->addOverlayFrame( legendFrame );
legendFrame->show();
}
else
{
plotWidget->removeOverlayFrame( legendFrame );
legendFrame->hide();
}
}
else
{
legend->hide();
legendFrame->hide();
plotWidget->removeOverlayFrame( legendFrame );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updateSubTitleVisibility( QLabel* subTitle )
{
bool isSubTitleVisible = m_showSubTitles && !subTitle->text().isEmpty();
subTitle->setVisible( isSubTitleVisible );
QFont subTitleFont = subTitle->font();
subTitleFont.setPointSize( m_subTitleFontSize );
subTitle->setFont( subTitleFont );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setDefaultAxisProperties( RiuPlotWidget* plotWidget, int row, int column )
{
plotWidget->setAxisLabelsAndTicksEnabled( RiuPlotAxis::defaultLeft(), showYAxis( row, column ), showYAxis( row, column ) );
plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultLeft(), showYAxis( row, column ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::adjustHeadingSpacing( RiuPlotWidget* plotWidget )
{
// Adjust the space below a graph to make sure the heading of the row below is closest to the
// corresponding graph
if ( !m_plotDefinition ) return;
int bottomMargin = m_plotDefinition->bottomMargin();
if ( bottomMargin < 0 ) return;
auto margins = plotWidget->contentsMargins();
margins.setBottom( bottomMargin );
plotWidget->setContentsMargins( margins );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setRowAndColumnStretches( int row, int column, int rowSpan, int colSpan )
{
// Set basic row and column stretches
for ( int r = row; r < row + rowSpan; ++r )
{
m_gridLayout->setRowStretch( 3 * r + 2, 1 );
}
for ( int c = column; c < column + colSpan; ++c )
{
int colStretch = 6; // Empirically chosen to try to counter the width of the axis on the first track
if ( showYAxis( row, column ) ) colStretch += 1;
m_gridLayout->setColumnStretch( c, std::max( colStretch, m_gridLayout->columnStretch( c ) ) );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updateLegendFont( RiuQwtPlotLegend* legend )
{
QFont legendFont = legend->font();
legendFont.setPointSize( m_legendFontSize );
// Set font size for all existing labels
QList<QwtLegendLabel*> labels = legend->findChildren<QwtLegendLabel*>();
for ( QwtLegendLabel* label : labels )
{
label->setFont( legendFont );
label->setMargin( 0 );
label->setSpacing( 3 );
}
legend->setFont( legendFont );
legend->setContentsMargins( 0, 0, 0, 0 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updateLegendColumns( RiuQwtPlotLegend* legend )
{
int legendColumns = 1;
if ( m_plotDefinition->legendsHorizontal() )
{
legendColumns = 0; // unlimited
}
legend->setMaxColumns( legendColumns );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiuMultiPlotPage::alignCanvasTops()
{
CAF_ASSERT( m_legends.size() == m_plotWidgets.size() );
QList<QPointer<RiuPlotWidget>> plotWidgets = visiblePlotWidgets();
QList<QPointer<RiuQwtPlotLegend>> legends = legendsForVisiblePlots();
if ( plotWidgets.empty() ) return 0;
auto rowAndColumnCount = this->rowAndColumnCount( plotWidgets.size() );
std::vector<double> maxExtents( rowAndColumnCount.first, 0.0 );
for ( int visibleIndex = 0; visibleIndex < plotWidgets.size(); ++visibleIndex )
{
RiuQwtPlotWidget* qwtPlotWidget = dynamic_cast<RiuQwtPlotWidget*>( plotWidgets[visibleIndex].data() );
if ( qwtPlotWidget )
{
int row = visibleIndex / rowAndColumnCount.second;
if ( plotWidgets[visibleIndex]->axisEnabled( RiuPlotAxis::defaultTop() ) )
{
QFont font = qwtPlotWidget->qwtPlot()->axisFont( QwtAxis::XTop );
maxExtents[row] = std::max( maxExtents[row], qwtPlotWidget->qwtPlot()->axisScaleDraw( QwtAxis::XTop )->extent( font ) );
}
}
}
for ( int visibleIndex = 0; visibleIndex < plotWidgets.size(); ++visibleIndex )
{
RiuQwtPlotWidget* qwtPlotWidget = dynamic_cast<RiuQwtPlotWidget*>( plotWidgets[visibleIndex].data() );
if ( qwtPlotWidget )
{
int row = visibleIndex / rowAndColumnCount.second;
qwtPlotWidget->qwtPlot()->axisScaleDraw( QwtAxis::XTop )->setMinimumExtent( maxExtents[row] );
auto legend = legends[visibleIndex];
if ( legend )
{
int previousChildCount = -1;
auto it = m_childCountForAdjustSizeOperation.find( legend );
if ( it != m_childCountForAdjustSizeOperation.end() )
{
previousChildCount = it->second;
}
auto legendItemCount = legend->contentsWidget()->children().size();
if ( previousChildCount != legendItemCount )
{
legends[visibleIndex]->adjustSize();
m_childCountForAdjustSizeOperation[legend] = legendItemCount;
}
}
}
}
return maxExtents[0];
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::clearGridLayout()
{
if ( m_gridLayout )
{
for ( int tIdx = 0; tIdx < m_plotWidgets.size(); ++tIdx )
{
m_gridLayout->removeWidget( m_subTitles[tIdx] );
if ( m_legends[tIdx] )
{
m_gridLayout->removeWidget( m_legends[tIdx] );
}
m_gridLayout->removeWidget( m_plotWidgets[tIdx] );
}
QLayoutItem* item;
while ( ( item = m_gridLayout->takeAt( 0 ) ) != nullptr )
{
}
QWidget().setLayout( m_gridLayout );
delete m_gridLayout;
m_gridLayout = new QGridLayout( m_plotWidgetFrame );
}
for ( int tIdx = 0; tIdx < m_plotWidgets.size(); ++tIdx )
{
if ( m_plotWidgets[tIdx] )
{
m_plotWidgets[tIdx]->removeOverlayFrame( m_legendFrames[tIdx] );
m_plotWidgets[tIdx]->hide();
}
if ( m_legends[tIdx] )
{
m_legends[tIdx]->hide();
}
if ( m_subTitles[tIdx] )
{
m_subTitles[tIdx]->hide();
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<QPointer<RiuPlotWidget>> RiuMultiPlotPage::visiblePlotWidgets() const
{
QList<QPointer<RiuPlotWidget>> plotWidgets;
for ( QPointer<RiuPlotWidget> plotWidget : m_plotWidgets )
{
if ( plotWidget && plotWidget->isChecked() )
{
plotWidgets.push_back( plotWidget );
}
}
return plotWidgets;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<QPointer<RiuQwtPlotLegend>> RiuMultiPlotPage::legendsForVisiblePlots() const
{
QList<QPointer<RiuQwtPlotLegend>> legends;
for ( int i = 0; i < m_plotWidgets.size(); ++i )
{
if ( m_plotWidgets[i] && m_plotWidgets[i]->isChecked() )
{
legends.push_back( m_legends[i] );
}
}
return legends;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<QPointer<RiuDraggableOverlayFrame>> RiuMultiPlotPage::legendFramesForVisiblePlots() const
{
QList<QPointer<RiuDraggableOverlayFrame>> legendFrames;
for ( int i = 0; i < m_plotWidgets.size(); ++i )
{
if ( m_plotWidgets[i] && m_plotWidgets[i]->isChecked() )
{
legendFrames.push_back( m_legendFrames[i] );
}
}
return legendFrames;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<QPointer<QLabel>> RiuMultiPlotPage::subTitlesForVisiblePlots() const
{
QList<QPointer<QLabel>> subTitles;
for ( int i = 0; i < m_plotWidgets.size(); ++i )
{
if ( m_plotWidgets[i] && m_plotWidgets[i]->isChecked() )
{
m_subTitles[i]->setText( m_plotWidgets[i]->plotTitle() );
subTitles.push_back( m_subTitles[i] );
}
}
return subTitles;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<int, int> RiuMultiPlotPage::findAvailableRowAndColumn( int startRow, int startColumn, int columnSpan, int columnCount ) const
{
int availableRow = startRow;
int availableColumn = startColumn;
while ( true )
{
for ( ; availableColumn < columnCount; ++availableColumn )
{
bool fits = true;
for ( int c = availableColumn; ( c < availableColumn + columnSpan ) && fits; ++c )
{
if ( c >= columnCount )
{
fits = false;
}
if ( m_gridLayout->itemAtPosition( 3 * availableRow, c ) != nullptr )
{
fits = false;
}
}
if ( fits )
{
return std::make_pair( availableRow, availableColumn );
}
}
availableColumn = 0;
availableRow++;
}
return std::make_pair( availableRow, availableColumn );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::applyLook()
{
QPalette newPalette( palette() );
auto backgroundColor = RiuGuiTheme::getColorByVariableName( "mainBackgroundColor" );
newPalette.setColor( QPalette::Window, backgroundColor );
setPalette( newPalette );
setAutoFillBackground( true );
setBackgroundRole( QPalette::Window );
if ( m_previewMode )
{
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
QGraphicsDropShadowEffect* dropShadowEffect = new QGraphicsDropShadowEffect( this );
dropShadowEffect->setOffset( 4.0, 4.0 );
dropShadowEffect->setBlurRadius( 4.0 );
dropShadowEffect->setColor( QColor( 40, 40, 40, 40 ) );
setGraphicsEffect( dropShadowEffect );
}
else
{
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
setGraphicsEffect( nullptr );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::updateTitleFont()
{
auto titleFont = m_plotTitle->font();
titleFont.setPointSize( m_titleFontSize );
m_plotTitle->setFont( titleFont );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::alignAxes()
{
auto [rowCount, columnCount] = rowAndColumnCount( visiblePlotWidgets().size() );
auto matchRow = []( int row, int column, int targetRow ) { return row == targetRow; };
for ( int row = 0; row < rowCount; row++ )
{
alignAxis( QwtAxisId( QwtAxis::Position::XTop, 0 ), row, matchRow );
alignAxis( QwtAxisId( QwtAxis::Position::XBottom, 0 ), row, matchRow );
}
auto matchColumn = []( int row, int column, int targetColumn ) { return column == targetColumn; };
for ( int column = 0; column < columnCount; column++ )
{
alignAxis( QwtAxisId( QwtAxis::Position::YLeft, 0 ), column, matchColumn );
alignAxis( QwtAxisId( QwtAxis::Position::YRight, 0 ), column, matchColumn );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::alignAxis( QwtAxisId axis, int targetRowOrColumn, std::function<bool( int, int, int )> matchPosition )
{
auto rowAndColumnFromIdx = [this]( int idx ) -> std::optional<std::pair<int, int>>
{
auto hit = m_visibleIndexToPositionMapping.find( idx );
if ( hit == m_visibleIndexToPositionMapping.end() ) return std::nullopt;
return hit->second;
};
QList<QPointer<RiuPlotWidget>> plotWidgets = visiblePlotWidgets();
// Find the max extent of the "scale draws" for the given axis
double maxExtent = 0;
for ( int tIdx = 0; tIdx < plotWidgets.size(); ++tIdx )
{
RiuPlotWidget* plotWidget = plotWidgets[tIdx];
auto position = rowAndColumnFromIdx( tIdx );
if ( !position ) continue;
auto [row, column] = *position;
bool matchesRowOrColumn = matchPosition( row, column, targetRowOrColumn );
if ( plotWidget && matchesRowOrColumn )
{
RiuQwtPlotWidget* riuQwtPlotWidget = dynamic_cast<RiuQwtPlotWidget*>( plotWidget );
if ( riuQwtPlotWidget )
{
if ( QwtPlot* p = riuQwtPlotWidget->qwtPlot() )
{
if ( QwtScaleWidget* scaleWidget = p->axisWidget( axis ) )
{
QwtScaleDraw* sd = scaleWidget->scaleDraw();
sd->setMinimumExtent( 0.0 );
maxExtent = std::max( sd->extent( scaleWidget->font() ), maxExtent );
}
}
}
}
}
// Set minimum extent for all "scale draws" for the given axis.
for ( int tIdx = 0; tIdx < plotWidgets.size(); ++tIdx )
{
RiuPlotWidget* plotWidget = plotWidgets[tIdx];
auto position = rowAndColumnFromIdx( tIdx );
if ( !position ) continue;
auto [row, column] = *position;
bool matchesRowOrColumn = matchPosition( row, column, targetRowOrColumn );
if ( plotWidget && matchesRowOrColumn )
{
RiuQwtPlotWidget* riuQwtPlotWidget = dynamic_cast<RiuQwtPlotWidget*>( plotWidget );
if ( riuQwtPlotWidget )
{
if ( QwtPlot* p = riuQwtPlotWidget->qwtPlot() )
{
if ( QwtScaleWidget* scaleWidget = p->axisWidget( axis ) )
{
scaleWidget->scaleDraw()->setMinimumExtent( maxExtent );
}
}
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMultiPlotPage::setAutoAlignAxes( bool autoAlignAxes )
{
m_autoAlignAxes = autoAlignAxes;
}