ResInsight/ApplicationLibCode/UserInterface/RiuQtChartsPlotWidget.cpp
2022-04-07 17:49:07 +02:00

1351 lines
50 KiB
C++

////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021- 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 "RiuQtChartsPlotWidget.h"
#include "RiaDefines.h"
#include "RiaFontCache.h"
#include "RiaGuiApplication.h"
#include "RiaPlotDefines.h"
#include "RiaPlotWindowRedrawScheduler.h"
#include "RimPlot.h"
#include "RiuDraggableOverlayFrame.h"
#include "RiuGuiTheme.h"
#include "RiuPlotCurveInfoTextProvider.h"
#include "RiuPlotMainWindowTools.h"
#include "RiuPlotWidget.h"
#include "RiuQtChartView.h"
#include "RiuQtChartsPlotCurve.h"
#include "RiuQtChartsToolTip.h"
#include "RiuQwtDateScaleWrapper.h"
#include "RiuQwtPlotTools.h"
#include "caf.h"
#include "cafAssert.h"
#include "cvfTrace.h"
#include <QCategoryAxis>
#include <QGraphicsLayout>
#include <QLogValueAxis>
#include <QVBoxLayout>
#include <QValueAxis>
#include <QtGlobal>
#include <cmath>
#include <limits>
using namespace QtCharts;
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuQtChartsPlotWidget::RiuQtChartsPlotWidget( RimPlot* plotDefinition,
QWidget* parent,
RiuPlotCurveInfoTextProvider* plotCurveNameProvider )
: RiuPlotWidget( plotDefinition, parent )
, m_plotCurveNameProvider( plotCurveNameProvider )
, m_dateScaleWrapper( new RiuQwtDateScaleWrapper() )
, m_toolTip( nullptr )
{
CAF_ASSERT( m_plotDefinition );
QVBoxLayout* layout = new QVBoxLayout;
layout->setContentsMargins( 0, 0, 0, 0 );
setLayout( layout );
QChart* chart = new QChart();
chart->layout()->setContentsMargins( 0, 0, 0, 0 );
chart->setBackgroundRoundness( 0 );
chart->setAcceptDrops( true );
chart->installEventFilter( this );
m_viewer = new RiuQtChartView( nullptr, parent );
m_viewer->setChart( chart );
m_viewer->setRenderHint( QPainter::Antialiasing );
layout->addWidget( m_viewer );
addAxis( RiuPlotAxis::defaultBottom(), true, true );
addAxis( RiuPlotAxis::defaultLeft(), true, true );
addAxis( RiuPlotAxis::defaultRight(), true, true );
addAxis( RiuPlotAxis::defaultTop(), false, false );
m_viewer->setRubberBand( QChartView::RectangleRubberBand );
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuQtChartsPlotWidget::~RiuQtChartsPlotWidget()
{
if ( m_plotDefinition )
{
m_plotDefinition->detachAllCurves();
}
delete m_dateScaleWrapper;
m_dateScaleWrapper = nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::axisRangeChanged()
{
auto catAxis = categoryAxis();
if ( catAxis )
{
auto min = catAxis->min();
auto max = catAxis->max();
auto existingLabels = catAxis->categoriesLabels();
for ( const auto& l : existingLabels )
{
catAxis->remove( l );
}
auto positionLabel = m_dateScaleWrapper->positionsAndLabels( min, max );
for ( auto [pos, label] : positionLabel )
{
catAxis->append( label, pos );
}
}
if ( qtChart()->isZoomed() ) emit plotZoomed();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiuQtChartsPlotWidget::axisTitleFontSize( RiuPlotAxis axis ) const
{
if ( axisEnabled( axis ) )
{
return plotAxis( axis )->titleFont().pointSize();
}
return -1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiuQtChartsPlotWidget::axisValueFontSize( RiuPlotAxis axis ) const
{
if ( axisEnabled( axis ) )
{
return plotAxis( axis )->labelsFont().pointSize();
}
return -1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisFontsAndAlignment( RiuPlotAxis axis,
int titleFontSize,
int valueFontSize,
bool titleBold,
int alignment )
{
int titleFontPixelSize = caf::FontTools::pointSizeToPixelSize( titleFontSize );
int valueFontPixelSize = caf::FontTools::pointSizeToPixelSize( valueFontSize );
// Axis number font
QFont axisFont = plotAxis( axis )->labelsFont();
axisFont.setPixelSize( valueFontPixelSize );
axisFont.setBold( false );
plotAxis( axis )->setLabelsFont( axisFont );
// Axis title font
QFont axisTitleFont = plotAxis( axis )->labelsFont();
axisTitleFont.setPixelSize( titleFontPixelSize );
axisTitleFont.setBold( titleBold );
plotAxis( axis )->setTitleFont( axisTitleFont );
applyAxisTitleToPlot( axis );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxesFontsAndAlignment( int titleFontSize, int valueFontSize, bool titleBold, int alignment )
{
for ( const auto& axisTitlePair : m_axisTitles )
{
setAxisFontsAndAlignment( axisTitlePair.first, titleFontSize, valueFontSize, titleBold, alignment );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::enableAxisNumberLabels( RiuPlotAxis axis, bool isEnabled )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisTitleText( RiuPlotAxis axis, const QString& title )
{
m_axisTitles[axis] = title;
applyAxisTitleToPlot( axis );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisTitleEnabled( RiuPlotAxis axis, bool enable )
{
m_axisTitlesEnabled[axis] = enable;
applyAxisTitleToPlot( axis );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisFormat( RiuPlotAxis axis, const QString& format )
{
auto ax = plotAxis( axis );
auto valueAxis = dynamic_cast<QValueAxis*>( ax );
if ( valueAxis ) valueAxis->setLabelFormat( format );
auto logAxis = dynamic_cast<QLogValueAxis*>( ax );
if ( logAxis ) logAxis->setLabelFormat( format );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setPlotTitle( const QString& plotTitle )
{
m_plotTitle = plotTitle;
applyPlotTitleToPlot();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const QString& RiuQtChartsPlotWidget::plotTitle() const
{
return m_plotTitle;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setPlotTitleEnabled( bool enabled )
{
m_plotTitleEnabled = enabled;
applyPlotTitleToPlot();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuQtChartsPlotWidget::plotTitleEnabled() const
{
return m_plotTitleEnabled;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setPlotTitleFontSize( int titleFontSize )
{
QFont font = qtChart()->titleFont();
font.setPixelSize( caf::FontTools::pointSizeToPixelSize( titleFontSize ) );
qtChart()->setTitleFont( font );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setLegendFontSize( int fontSize )
{
if ( qtChart()->legend() )
{
QFont font = qtChart()->legend()->font();
font.setPixelSize( caf::FontTools::pointSizeToPixelSize( fontSize ) );
qtChart()->legend()->setFont( font );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setInternalLegendVisible( bool visible )
{
if ( visible )
{
insertLegend( RiuPlotWidget::Legend::BOTTOM );
}
else
{
clearLegend();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::insertLegend( RiuPlotWidget::Legend legendPosition )
{
auto mapLegendPosition = []( RiuPlotWidget::Legend pos ) {
if ( pos == RiuPlotWidget::Legend::BOTTOM )
return Qt::AlignBottom;
else if ( pos == RiuPlotWidget::Legend::TOP )
return Qt::AlignTop;
else if ( pos == RiuPlotWidget::Legend::LEFT )
return Qt::AlignLeft;
return Qt::AlignRight;
};
QLegend* legend = qtChart()->legend();
legend->setAlignment( mapLegendPosition( legendPosition ) );
if ( !legend->isAttachedToChart() )
{
legend->attachToChart();
}
replot();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::clearLegend()
{
QLegend* legend = qtChart()->legend();
legend->detachFromChart();
legend->hide();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<double, double> RiuQtChartsPlotWidget::axisRange( RiuPlotAxis axis ) const
{
auto ax = plotAxis( axis );
auto valueAxis = dynamic_cast<QValueAxis*>( ax );
if ( valueAxis ) return std::make_pair( valueAxis->min(), valueAxis->max() );
auto logAxis = dynamic_cast<QLogValueAxis*>( ax );
if ( logAxis ) return std::make_pair( logAxis->min(), logAxis->max() );
return std::make_pair( 0.0, 1.0 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisRange( RiuPlotAxis axis, double min, double max )
{
// Note: Especially the Y-axis may be inverted
if ( plotAxis( axis )->isReverse() )
{
setAxisScale( axis, max, min );
}
else
{
setAxisScale( axis, min, max );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisInverted( RiuPlotAxis axis, bool isInverted )
{
auto ax = plotAxis( axis );
ax->setReverse( isInverted );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisLabelsAndTicksEnabled( RiuPlotAxis axis, bool enableLabels, bool enableTicks )
{
plotAxis( axis )->setLabelsVisible( enableLabels );
plotAxis( axis )->setGridLineVisible( enableTicks );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::enableGridLines( RiuPlotAxis axis, bool majorGridLines, bool minorGridLines )
{
plotAxis( axis )->setGridLineVisible( majorGridLines );
plotAxis( axis )->setMinorGridLineVisible( minorGridLines );
QPen gridLinePen( Qt::lightGray, 1.0, Qt::SolidLine );
plotAxis( axis )->setGridLinePen( gridLinePen );
QPen minorGridLinePen( Qt::lightGray, 1.0, Qt::DashLine );
plotAxis( axis )->setMinorGridLinePen( minorGridLinePen );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setMajorAndMinorTickIntervals( RiuPlotAxis axis,
double majorTickInterval,
double minorTickInterval,
double minValue,
double maxValue )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setMajorAndMinorTickIntervalsAndRange( RiuPlotAxis axis,
double majorTickInterval,
double minorTickInterval,
double minTickValue,
double maxTickValue,
double rangeMin,
double rangeMax )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAutoTickIntervalCounts( RiuPlotAxis axis,
int maxMajorTickIntervalCount,
int maxMinorTickIntervalCount )
{
setAxisMaxMajor( axis, maxMajorTickIntervalCount );
setAxisMaxMinor( axis, maxMinorTickIntervalCount );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RiuQtChartsPlotWidget::majorTickInterval( RiuPlotAxis axis ) const
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 12, 0 )
// QValueAxis::tickInterval was introduced in 5.12
QAbstractAxis* ax = plotAxis( axis );
QValueAxis* valueAxis = dynamic_cast<QValueAxis*>( ax );
if ( valueAxis ) return valueAxis->tickInterval();
#endif
return 0.0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RiuQtChartsPlotWidget::minorTickInterval( RiuPlotAxis axis ) const
{
return 0.0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiuQtChartsPlotWidget::axisExtent( RiuPlotAxis axis ) const
{
CAF_ASSERT( false && "Not implemented" );
return 100;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QPoint RiuQtChartsPlotWidget::dragStartPosition() const
{
return m_clickPosition;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::scheduleReplot()
{
RiaPlotWindowRedrawScheduler::instance()->schedulePlotWidgetReplot( this );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::updateLayout()
{
updateOverlayFrameLayout();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::updateLegend()
{
qtChart()->legend()->update();
auto curves = plotDefinition()->visibleCurvesForLegend();
auto legendData = RiuQwtPlotTools::createLegendData( curves );
emit legendDataChanged( legendData );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::resizeEvent( QResizeEvent* event )
{
QWidget::resizeEvent( event );
updateOverlayFrameLayout();
event->accept();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::keyPressEvent( QKeyEvent* event )
{
switch ( event->key() )
{
case Qt::Key_Plus:
qtChart()->zoomIn();
break;
case Qt::Key_Minus:
qtChart()->zoomOut();
break;
default:
QWidget::keyPressEvent( event );
break;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::applyPlotTitleToPlot()
{
QString plotTitleToApply = m_plotTitleEnabled ? m_plotTitle : QString( "" );
m_viewer->chart()->setTitle( plotTitleToApply );
m_viewer->chart()->update();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::applyAxisTitleToPlot( RiuPlotAxis axis )
{
QString titleToApply = m_axisTitlesEnabled[axis] ? m_axisTitles[axis] : QString( "" );
plotAxis( axis )->setTitleText( titleToApply );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QSize RiuQtChartsPlotWidget::sizeHint() const
{
return QSize( 0, 0 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QSize RiuQtChartsPlotWidget::minimumSizeHint() const
{
return QSize( 0, 0 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuQtChartsPlotWidget::isZoomerActive() const
{
return false;
}
//--------------------------------------------------------------------------------------------------
/// Empty default implementation
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::endZoomOperations()
{
}
//--------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::renderTo( QPainter* painter, const QRect& targetRect, double scaling )
{
// TODO: handle scaling...
painter->setRenderHint( QPainter::Antialiasing );
m_viewer->render( painter, targetRect );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::renderTo( QPaintDevice* paintDevice, const QRect& targetRect )
{
int resolution = paintDevice->logicalDpiX();
double scaling = resolution / static_cast<double>( RiaGuiApplication::applicationResolution() );
QPainter painter( paintDevice );
renderTo( &painter, targetRect, scaling );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiuQtChartsPlotWidget::overlayMargins() const
{
return m_overlayMargins;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QWidget* RiuQtChartsPlotWidget::getParentForOverlay() const
{
return m_viewer;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimViewWindow* RiuQtChartsPlotWidget::ownerViewWindow() const
{
return m_plotDefinition;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiuQtChartsPlotWidget::defaultMinimumWidth()
{
return 80;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::replot()
{
qtChart()->update();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QCategoryAxis* RiuQtChartsPlotWidget::categoryAxis()
{
for ( const auto& a : m_axes )
{
auto catAxis = dynamic_cast<QCategoryAxis*>( a.second );
if ( catAxis ) return catAxis;
}
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::updateZoomDependentCurveProperties()
{
for ( auto it : m_scatterSeriesMap )
{
auto plotCurve = dynamic_cast<RiuQtChartsPlotCurve*>( it.first );
if ( plotCurve ) plotCurve->updateScatterSeries();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setFormatStrings( const QString& dateFormat,
const QString& timeFormat,
RiaDefines::DateFormatComponents dateComponents,
RiaDefines::TimeFormatComponents timeComponents )
{
m_dateScaleWrapper->setFormatStrings( dateFormat, timeFormat, dateComponents, timeComponents );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::enableAxis( RiuPlotAxis axis, bool isEnabled )
{
m_axesEnabled[axis] = isEnabled;
plotAxis( axis )->setVisible( isEnabled );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuQtChartsPlotWidget::axisEnabled( RiuPlotAxis axis ) const
{
auto it = m_axesEnabled.find( axis );
if ( it != m_axesEnabled.end() )
return it->second;
else
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisMaxMinor( RiuPlotAxis axis, int maxMinor )
{
QAbstractAxis* ax = plotAxis( axis );
QValueAxis* valueAxis = dynamic_cast<QValueAxis*>( ax );
if ( valueAxis )
{
valueAxis->setMinorTickCount( maxMinor );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisMaxMajor( RiuPlotAxis axis, int maxMajor )
{
QAbstractAxis* ax = plotAxis( axis );
QValueAxis* valueAxis = dynamic_cast<QValueAxis*>( ax );
if ( valueAxis )
{
valueAxis->setTickCount( maxMajor );
}
else
{
m_dateScaleWrapper->setMaxMajorTicks( maxMajor );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisAutoScale( RiuPlotAxis axis, bool autoScale )
{
m_axesAutoScale[axis] = autoScale;
if ( autoScale )
{
rescaleAxis( axis );
QAbstractAxis* ax = plotAxis( axis );
QValueAxis* valueAxis = dynamic_cast<QValueAxis*>( ax );
if ( valueAxis )
{
// Block signals to avoid triggering RimSummaryPlot::onPlotZoomed
valueAxis->blockSignals( true );
valueAxis->applyNiceNumbers();
valueAxis->blockSignals( false );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisScale( RiuPlotAxis axis, double min, double max )
{
plotAxis( axis )->setRange( min, max );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuQtChartsPlotWidget::AxisScaleType RiuQtChartsPlotWidget::axisScaleType( RiuPlotAxis axis ) const
{
if ( plotAxis( axis )->type() == QAbstractAxis::AxisTypeLogValue ) return AxisScaleType::LOGARITHMIC;
if ( plotAxis( axis )->type() == QAbstractAxis::AxisTypeDateTime ) return AxisScaleType::DATE;
return AxisScaleType::LINEAR;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setAxisScaleType( RiuPlotAxis axis, RiuQtChartsPlotWidget::AxisScaleType axisScaleType )
{
QAbstractAxis* axisToBeDeleted = plotAxis( axis );
QAbstractAxis* insertaxis = nullptr;
if ( axisScaleType == AxisScaleType::LOGARITHMIC )
{
insertaxis = new QLogValueAxis;
}
else if ( axisScaleType == AxisScaleType::DATE )
{
auto categoryAxis = new QCategoryAxis;
categoryAxis->setLabelsPosition( QCategoryAxis::AxisLabelsPosition::AxisLabelsPositionOnValue );
insertaxis = categoryAxis;
}
else if ( axisScaleType == AxisScaleType::LINEAR )
{
insertaxis = new QValueAxis;
}
QChart* chart = qtChart();
if ( chart->axes().contains( axisToBeDeleted ) ) chart->removeAxis( axisToBeDeleted );
chart->addAxis( insertaxis, mapPlotAxisToQtAlignment( axis.axis() ) );
m_axes[axis] = insertaxis;
for ( auto serie : chart->series() )
{
if ( serie->attachedAxes().contains( axisToBeDeleted ) ) serie->detachAxis( axisToBeDeleted );
serie->attachAxis( insertaxis );
}
// We have the ownership of the axis object, delete to avoid memory leak
delete axisToBeDeleted;
axisToBeDeleted = nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::updateAxes()
{
axisRangeChanged();
m_viewer->chart()->update();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuPlotCurve* RiuQtChartsPlotWidget::createPlotCurve( RimPlotCurve* ownerRimCurve, const QString& title, const QColor& color )
{
return new RiuQtChartsPlotCurve( ownerRimCurve, title );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QChart* RiuQtChartsPlotWidget::qtChart()
{
return m_viewer->chart();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::attach( RiuPlotCurve* plotCurve,
QAbstractSeries* lineSeries,
QAbstractSeries* areaSeries,
QAbstractSeries* scatterSeries,
RiuPlotAxis xAxis,
RiuPlotAxis yAxis )
{
auto addToChart = [this]( std::map<RiuPlotCurve*, QAbstractSeries*>& curveSeriesMap,
auto plotCurve,
auto series,
auto xAxis,
auto yAxis,
RiuQtChartsPlotCurve* qtChartsPlotCurve ) {
if ( !series->chart() )
{
curveSeriesMap[plotCurve] = series;
qtChart()->addSeries( series );
setXAxis( xAxis, series, qtChartsPlotCurve );
setYAxis( yAxis, series, qtChartsPlotCurve );
}
};
auto qtChartsPlotCurve = dynamic_cast<RiuQtChartsPlotCurve*>( plotCurve );
addToChart( m_lineSeriesMap, plotCurve, lineSeries, xAxis, yAxis, qtChartsPlotCurve );
addToChart( m_areaSeriesMap, plotCurve, areaSeries, xAxis, yAxis, qtChartsPlotCurve );
addToChart( m_scatterSeriesMap, plotCurve, scatterSeries, xAxis, yAxis, qtChartsPlotCurve );
connect( dynamic_cast<QLineSeries*>( lineSeries ), &QLineSeries::hovered, this, &RiuQtChartsPlotWidget::tooltip );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::detach( RiuPlotCurve* plotCurve )
{
m_lineSeriesMap.erase( plotCurve );
m_areaSeriesMap.erase( plotCurve );
m_scatterSeriesMap.erase( plotCurve );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QAbstractSeries* RiuQtChartsPlotWidget::getLineSeries( const RiuPlotCurve* plotCurve ) const
{
auto series = m_lineSeriesMap.find( const_cast<RiuPlotCurve*>( plotCurve ) );
if ( series != m_lineSeriesMap.end() )
return series->second;
else
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QAbstractSeries* RiuQtChartsPlotWidget::getAreaSeries( const RiuPlotCurve* plotCurve ) const
{
auto series = m_areaSeriesMap.find( const_cast<RiuPlotCurve*>( plotCurve ) );
if ( series != m_areaSeriesMap.end() )
return series->second;
else
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QAbstractSeries* RiuQtChartsPlotWidget::getScatterSeries( const RiuPlotCurve* plotCurve ) const
{
auto series = m_scatterSeriesMap.find( const_cast<RiuPlotCurve*>( plotCurve ) );
if ( series != m_scatterSeriesMap.end() )
return series->second;
else
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::detachItems( RiuPlotWidget::PlotItemType plotItemType )
{
cvf::Trace::show( "RiuQtChartsPlotWidget::detachItems" );
if ( plotItemType == RiuPlotWidget::PlotItemType::CURVE )
{
m_lineSeriesMap.clear();
m_areaSeriesMap.clear();
m_scatterSeriesMap.clear();
qtChart()->removeAllSeries();
}
else
{
cvf::Trace::show( "Detach items not implemented for this type." );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setXAxis( RiuPlotAxis axis, QAbstractSeries* series, RiuQtChartsPlotCurve* plotCurve )
{
attachSeriesToAxis( axis, series, plotCurve );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::setYAxis( RiuPlotAxis axis, QAbstractSeries* series, RiuQtChartsPlotCurve* plotCurve )
{
attachSeriesToAxis( axis, series, plotCurve );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::ensureAxisIsCreated( RiuPlotAxis axis )
{
if ( m_axes.find( axis ) == m_axes.end() )
{
addAxis( axis, true, true );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::attachSeriesToAxis( RiuPlotAxis axis, QAbstractSeries* series, RiuQtChartsPlotCurve* plotCurve )
{
// Make sure the axis we are about to set exists.
ensureAxisIsCreated( axis );
if ( qtChart()->series().contains( series ) && !series->attachedAxes().contains( plotAxis( axis ) ) )
{
auto newAxis = plotAxis( axis );
// Detach any other axis for the same orientation
for ( auto ax : series->attachedAxes() )
{
if ( ax->orientation() == orientation( axis.axis() ) )
{
series->detachAxis( ax );
}
}
series->attachAxis( newAxis );
if ( qobject_cast<QValueAxis*>( newAxis ) || qobject_cast<QLogValueAxis*>( newAxis ) )
{
connect( newAxis, SIGNAL( rangeChanged( double, double ) ), this, SLOT( axisRangeChanged() ), Qt::UniqueConnection );
if ( plotCurve )
{
connect( newAxis,
SIGNAL( rangeChanged( double, double ) ),
plotCurve,
SLOT( axisRangeChanged() ),
Qt::UniqueConnection );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::addAxis( RiuPlotAxis plotAxis, bool isEnabled, bool isAutoScale )
{
auto* axis = new QValueAxis();
qtChart()->addAxis( axis, mapPlotAxisToQtAlignment( plotAxis.axis() ) );
m_axes[plotAxis] = axis;
m_axesEnabled[plotAxis] = isEnabled;
m_axesAutoScale[plotAxis] = isAutoScale;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::deleteAxis( RiuPlotAxis axis )
{
auto toBeDeleted = plotAxis( axis );
qtChart()->removeAxis( toBeDeleted );
m_axes.erase( axis );
m_axesEnabled.erase( axis );
m_axesAutoScale.erase( axis );
delete toBeDeleted;
toBeDeleted = nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuPlotAxis RiuQtChartsPlotWidget::createNextPlotAxis( RiaDefines::PlotAxis axis )
{
int minIdx = -1;
for ( const auto& a : m_axes )
{
if ( a.first.axis() == axis )
{
minIdx = std::max( a.first.index(), minIdx );
}
}
RiuPlotAxis plotAxis( axis, minIdx + 1 );
addAxis( plotAxis, true, true );
return plotAxis;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuQtChartsPlotWidget::isMultiAxisSupported() const
{
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::rescaleAxis( RiuPlotAxis axis )
{
if ( !m_axesAutoScale[axis] ) return;
QAbstractAxis* pAxis = plotAxis( axis );
Qt::Orientation orr = orientation( axis.axis() );
double min = std::numeric_limits<double>::max();
double max = -std::numeric_limits<double>::max();
for ( auto series : qtChart()->series() )
{
auto attachedAxes = series->attachedAxes();
if ( attachedAxes.contains( pAxis ) )
{
QVector<QPointF> points;
for ( auto attachedAxis : attachedAxes )
{
auto* valueAxis = dynamic_cast<QValueAxis*>( attachedAxis );
if ( valueAxis && valueAxis->orientation() == orr && dynamic_cast<QLineSeries*>( series ) )
{
points = dynamic_cast<QLineSeries*>( series )->pointsVector();
}
for ( auto p : points )
{
if ( orr == Qt::Orientation::Horizontal )
{
min = std::min( min, p.x() );
max = std::max( max, p.x() );
}
else
{
min = std::min( min, p.y() );
max = std::max( max, p.y() );
}
}
}
}
}
// Block signals to avoid triggering RimSummaryPlot::onPlotZoomed
pAxis->blockSignals( true );
if ( axis.axis() == RiaDefines::PlotAxis::PLOT_AXIS_BOTTOM )
{
auto catAxis = categoryAxis();
if ( catAxis )
{
auto existingLabels = catAxis->categoriesLabels();
for ( const auto& l : existingLabels )
{
catAxis->remove( l );
}
auto [adjustedMin, adjustedMax, tickCount] = m_dateScaleWrapper->adjustedRange( min, max );
catAxis->setRange( adjustedMin, adjustedMax );
auto positionLabel = m_dateScaleWrapper->positionsAndLabels( adjustedMin, adjustedMax );
for ( auto [pos, label] : positionLabel )
{
catAxis->append( label, pos );
}
}
}
else
{
pAxis->setRange( min, max );
}
pAxis->blockSignals( false );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QAbstractAxis* RiuQtChartsPlotWidget::plotAxis( RiuPlotAxis axis ) const
{
const auto ax = m_axes.find( axis );
if ( ax != m_axes.end() )
{
return ax->second;
}
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Qt::Orientation RiuQtChartsPlotWidget::orientation( RiaDefines::PlotAxis axis ) const
{
if ( axis == RiaDefines::PlotAxis::PLOT_AXIS_BOTTOM || axis == RiaDefines::PlotAxis::PLOT_AXIS_TOP )
return Qt::Orientation::Horizontal;
return Qt::Orientation::Vertical;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::dragEnterEvent( QDragEnterEvent* event )
{
RiuPlotWidget::handleDragDropEvent( event );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::dropEvent( QDropEvent* event )
{
RiuPlotWidget::handleDragDropEvent( event );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::wheelEvent( QWheelEvent* wheelEvent )
{
float factor = wheelEvent->angleDelta().y() > 0 ? 0.9 : 1.1;
QRectF plotAreaRect = m_viewer->chart()->plotArea();
QPointF centerPoint = plotAreaRect.center();
// Adjust the size of the plot area
plotAreaRect.setWidth( plotAreaRect.width() * factor );
plotAreaRect.setHeight( plotAreaRect.height() * factor );
auto position = caf::position( wheelEvent );
// Find new center which keeps the mouse location in the same place in the plot
QPointF newCenterPoint( ( 2 * centerPoint - position ) - ( centerPoint - position ) / factor );
plotAreaRect.moveCenter( newCenterPoint );
// Zoom in on the adjusted plot area
m_viewer->chart()->zoomIn( plotAreaRect );
wheelEvent->accept();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuQtChartsPlotWidget::eventFilter( QObject* watched, QEvent* event )
{
if ( RiuPlotWidget::handleDragDropEvent( event ) ) return true;
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const QColor& RiuQtChartsPlotWidget::backgroundColor() const
{
return m_viewer->chart()->backgroundBrush().color();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<RiuPlotCurve*, int> RiuQtChartsPlotWidget::findClosestCurve( const QPoint& pos, double& distanceToClick ) const
{
return std::make_pair( nullptr, -1 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Qt::Alignment RiuQtChartsPlotWidget::mapPlotAxisToQtAlignment( RiaDefines::PlotAxis axis )
{
if ( axis == RiaDefines::PlotAxis::PLOT_AXIS_BOTTOM ) return Qt::AlignBottom;
if ( axis == RiaDefines::PlotAxis::PLOT_AXIS_TOP ) return Qt::AlignTop;
if ( axis == RiaDefines::PlotAxis::PLOT_AXIS_LEFT ) return Qt::AlignLeft;
return Qt::AlignRight;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::pruneAxes( const std::set<RiuPlotAxis>& usedAxes )
{
for ( auto [plotAxis, qtAxis] : m_axes )
{
if ( usedAxes.count( plotAxis ) == 0 )
{
deleteAxis( plotAxis );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::moveAxis( RiuPlotAxis oldAxis, RiuPlotAxis newAxis )
{
deleteAxis( oldAxis );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuQtChartsPlotWidget::tooltip( const QPointF& point, bool state )
{
QAbstractSeries* series = qobject_cast<QAbstractSeries*>( sender() );
if ( !m_toolTip ) m_toolTip = new RiuQtChartsToolTip( qtChart(), series );
auto xySeries = dynamic_cast<QLineSeries*>( series );
auto snapToPoint = point;
if ( xySeries )
{
auto points = xySeries->pointsVector();
int closestIndex = -1;
for ( int i = 0; i < points.size() - 1; i++ )
{
if ( point.x() > points[i + 1].x() ) continue;
if ( points[i].x() < point.x() && point.x() < points[i + 1].x() )
{
if ( std::fabs( points[i].x() - point.x() ) < std::fabs( point.x() - points[i + 1].x() ) )
{
closestIndex = i;
}
else
{
closestIndex = i + 1;
}
break;
}
}
if ( closestIndex > 0 )
{
snapToPoint = points[closestIndex];
}
}
if ( state )
{
QString nameFromSeries = createNameFromSeries( series );
QDateTime date = QDateTime::fromMSecsSinceEpoch( snapToPoint.x() );
QString dateString = RiaQDateTimeTools::toStringUsingApplicationLocale( date, "hh:mm dd.MMMM.yyyy" );
QString text = QString( "%1 (%2)" ).arg( snapToPoint.y() ).arg( dateString );
if ( !nameFromSeries.isEmpty() ) text.prepend( nameFromSeries + ": " );
m_toolTip->setText( text );
m_toolTip->setAnchor( snapToPoint );
m_toolTip->setSeries( series );
m_toolTip->setZValue( 200 );
m_toolTip->updateGeometry();
m_toolTip->show();
}
else
{
m_toolTip->hide();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiuQtChartsPlotWidget::createNameFromSeries( QAbstractSeries* series ) const
{
if ( !m_plotCurveNameProvider ) return "";
for ( auto [plotCurve, plotSeries] : m_lineSeriesMap )
{
if ( plotSeries == series )
{
return m_plotCurveNameProvider->curveInfoText( plotCurve );
}
}
return "";
}