///////////////////////////////////////////////////////////////////////////////// // // 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 // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RiuQtChartsPlotCurve.h" #include "RiaPlotDefines.h" #include "RiuQtChartsPlotCurveSymbol.h" #include "RiuQtChartsPlotWidget.h" #include "cvfBoundingBox.h" #include #include #include #include #include #include //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuQtChartsPlotCurve::RiuQtChartsPlotCurve( RimPlotCurve* ownerRimCurve, const QString& title ) : RiuPlotCurve( ownerRimCurve, title ) { m_plotWidget = nullptr; m_lineSeries = new QtCharts::QLineSeries(); m_lineSeries->setName( title ); m_areaSeries = new QtCharts::QAreaSeries(); m_areaSeries->setName( title ); m_scatterSeries = new QtCharts::QScatterSeries(); m_scatterSeries->setName( title ); m_axisX = RiuPlotAxis::defaultBottom(); m_axisY = RiuPlotAxis::defaultLeft(); m_isVisibleInLegend = false; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuQtChartsPlotCurve::~RiuQtChartsPlotCurve() { if ( m_plotWidget && m_plotWidget->qtChart() ) { m_plotWidget->detach( this ); auto* line = lineSeries(); if ( line ) { m_plotWidget->qtChart()->removeSeries( line ); // removeSeries() releases chart ownership of the data, delete data to avoid memory leak delete line; } auto* area = areaSeries(); if ( area ) { m_plotWidget->qtChart()->removeSeries( area ); // removeSeries() releases chart ownership of the data, delete data to avoid memory leak delete area; } auto* scatter = scatterSeries(); if ( scatter ) { m_plotWidget->qtChart()->removeSeries( scatter ); // removeSeries() releases chart ownership of the data, delete data to avoid memory leak delete scatter; } } // Delete if it is still owned by by plot curve delete m_lineSeries; m_lineSeries = nullptr; delete m_areaSeries; m_areaSeries = nullptr; delete m_scatterSeries; m_scatterSeries = nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setTitle( const QString& title ) { if ( !isQtChartObjectsPresent() ) return; lineSeries()->setName( title ); areaSeries()->setName( title ); scatterSeries()->setName( title ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setAppearance( RiuQwtPlotCurveDefines::LineStyleEnum lineStyle, RiuQwtPlotCurveDefines::CurveInterpolationEnum interpolationType, int requestedCurveThickness, const QColor& curveColor, const QBrush& fillBrush /* = QBrush( Qt::NoBrush )*/ ) { if ( !isQtChartObjectsPresent() ) return; Qt::PenStyle penStyle = RiuQwtPlotCurveDefines::convertToPenStyle( lineStyle ); QPen curvePen( curveColor ); curvePen.setWidth( requestedCurveThickness ); curvePen.setStyle( penStyle ); lineSeries()->setPen( curvePen ); lineSeries()->setBrush( fillBrush ); areaSeries()->setPen( curvePen ); areaSeries()->setBrush( fillBrush ); if ( fillBrush.style() == Qt::NoBrush ) { lineSeries()->show(); areaSeries()->hide(); setVisibleInLegend( m_isVisibleInLegend ); } else { lineSeries()->hide(); areaSeries()->show(); setVisibleInLegend( m_isVisibleInLegend ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setBrush( const QBrush& brush ) { if ( !isQtChartObjectsPresent() ) return; lineSeries()->setBrush( brush ); areaSeries()->setBrush( brush ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setColor( const QColor& color ) { QPen curvePen = lineSeries()->pen(); curvePen.setColor( color ); lineSeries()->setPen( curvePen ); curvePen = areaSeries()->pen(); curvePen.setColor( color ); areaSeries()->setPen( curvePen ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::attachToPlot( RiuPlotWidget* plotWidget ) { m_plotWidget = dynamic_cast( plotWidget ); CAF_ASSERT( m_plotWidget ); if ( m_plotWidget->getLineSeries( this ) && m_plotWidget->getScatterSeries( this ) ) { m_plotWidget->qtChart()->legend()->setMarkerShape( QtCharts::QLegend::MarkerShape::MarkerShapeFromSeries ); setVisibleInLegend( true ); lineSeries()->show(); } else { if ( !m_lineSeries ) m_lineSeries = new QtCharts::QLineSeries(); if ( !m_areaSeries ) m_areaSeries = new QtCharts::QAreaSeries(); if ( !m_scatterSeries ) m_scatterSeries = new QtCharts::QScatterSeries(); m_plotWidget->attach( this, m_lineSeries, m_areaSeries, m_scatterSeries, m_axisX, m_axisY ); // Plot widget takes ownership. m_lineSeries = nullptr; m_areaSeries = nullptr; m_scatterSeries = nullptr; } if ( scatterSeries() ) { if ( m_symbol ) { scatterSeries()->show(); auto qtChartsSymbol = dynamic_cast( m_symbol.get() ); CAF_ASSERT( qtChartsSymbol ); qtChartsSymbol->applyToScatterSeries( scatterSeries() ); } else { scatterSeries()->hide(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::detach() { QtCharts::QLineSeries* line = lineSeries(); if ( line ) { line->hide(); } QtCharts::QAreaSeries* area = areaSeries(); if ( area ) { area->hide(); } if ( scatterSeries() ) { scatterSeries()->hide(); } if ( m_plotWidget ) { m_plotWidget->detach( this ); setVisibleInLegend( false ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::showInPlot() { // show(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setSamplesInPlot( const std::vector& xValues, const std::vector& yValues ) { if ( !isQtChartObjectsPresent() ) return; CAF_ASSERT( xValues.size() == yValues.size() ); QVector values( static_cast( xValues.size() ) ); for ( int i = 0; i < static_cast( xValues.size() ); i++ ) { values[i] = QPointF( xValues[i], yValues[i] ); } QtCharts::QLineSeries* line = lineSeries(); line->replace( values ); QtCharts::QLineSeries* upper = new QtCharts::QLineSeries; upper->replace( values ); areaSeries()->setUpperSeries( upper ); updateScatterSeries(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::updateScatterSeries() { if ( !scatterSeries() ) return; double minX = std::numeric_limits::max(); double maxX = -std::numeric_limits::max(); QVector points; if ( lineSeries() ) { points = lineSeries()->pointsVector(); bool foundAxis = false; auto axes = lineSeries()->attachedAxes(); for ( auto axis : axes ) { if ( axis->orientation() == Qt::Orientation::Horizontal ) { QtCharts::QValueAxis* valueAxis = dynamic_cast( axis ); QtCharts::QDateTimeAxis* dateTimeAxis = dynamic_cast( axis ); if ( valueAxis ) { minX = valueAxis->min(); maxX = valueAxis->max(); foundAxis = true; } else if ( dateTimeAxis ) { minX = dateTimeAxis->min().toMSecsSinceEpoch(); maxX = dateTimeAxis->max().toMSecsSinceEpoch(); foundAxis = true; } } } if ( !foundAxis ) { for ( auto p : points ) { minX = std::min( minX, p.x() ); maxX = std::max( maxX, p.x() ); } } } QVector scatterValues; if ( !points.empty() ) { double range = maxX - minX; double displaySize = 1400; if ( m_plotWidget && m_plotWidget->qtChart() ) { // Use the max size since plot area can be small before the widget is shown displaySize = std::max( displaySize, m_plotWidget->qtChart()->plotArea().width() ); } double rangePerPixel = range / displaySize; double skipDistance = rangePerPixel * m_symbolSkipPixelDistance; // Always have symbol on first point scatterValues << points[0]; int lastDrawnIndex = 0; for ( int i = 1; i < static_cast( points.size() ); i++ ) { // Skip points until skip distance is reached double diff = points[i].x() - points[lastDrawnIndex].x(); // Always add last point. bool isLastPoint = i == points.size() - 1; if ( diff > skipDistance || isLastPoint ) { scatterValues << points[i]; lastDrawnIndex = i; } } } scatterSeries()->replace( scatterValues ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RiuQtChartsPlotCurve::isQtChartObjectsPresent() const { if ( !lineSeries() ) return false; return true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setZ( int z ) { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::clearErrorBars() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::updateErrorBarsAppearance( bool showErrorBars, const QColor& curveColor ) { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setXAxis( RiuPlotAxis axis ) { m_axisX = axis; if ( m_plotWidget ) { m_plotWidget->setXAxis( axis, lineSeries(), this ); m_plotWidget->setXAxis( axis, areaSeries(), this ); m_plotWidget->setXAxis( axis, scatterSeries(), this ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setYAxis( RiuPlotAxis axis ) { m_axisY = axis; if ( m_plotWidget ) { m_plotWidget->setYAxis( axis, lineSeries(), this ); m_plotWidget->setYAxis( axis, areaSeries(), this ); m_plotWidget->setYAxis( axis, scatterSeries(), this ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RiuQtChartsPlotCurve::numSamples() const { if ( !lineSeries() ) return 0; return lineSeries()->count(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RiuQtChartsPlotCurve::sample( int index ) const { CAF_ASSERT( index >= 0 && index <= numSamples() ); auto p = lineSeries()->at( index ); return std::make_pair( p.x(), p.y() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RiuQtChartsPlotCurve::xDataRange() const { cvf::BoundingBox bb = computeBoundingBox(); return std::make_pair( bb.min().x(), bb.max().x() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RiuQtChartsPlotCurve::yDataRange() const { cvf::BoundingBox bb = computeBoundingBox(); return std::make_pair( bb.min().y(), bb.max().y() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::BoundingBox RiuQtChartsPlotCurve::computeBoundingBox() const { auto points = lineSeries()->pointsVector(); cvf::BoundingBox bb; for ( auto p : points ) bb.add( cvf::Vec3d( p.x(), p.y(), 0.0 ) ); return bb; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setVisibleInLegend( bool isVisibleInLegend ) { if ( !m_plotWidget ) return; CAF_ASSERT( m_plotWidget->qtChart() ); CAF_ASSERT( m_plotWidget->qtChart()->legend() ); // The markers can be set visible independent to the visibility state of the containing legend. Use the // visibility state of the legend to override the visibility flag if ( !m_plotWidget->qtChart()->legend()->isAttachedToChart() ) isVisibleInLegend = false; if ( !m_plotWidget->qtChart()->legend()->isVisible() ) isVisibleInLegend = false; bool showScatterMarker = isVisibleInLegend && m_symbol; bool showLineMarker = isVisibleInLegend && !m_symbol; auto setLegendVisibility = [this]( auto series, bool isVisible ) { if ( series ) { auto markers = m_plotWidget->qtChart()->legend()->markers( series ); if ( !markers.isEmpty() ) markers[0]->setVisible( isVisible ); } }; m_isVisibleInLegend = showLineMarker || showScatterMarker; setLegendVisibility( lineSeries(), showLineMarker ); setLegendVisibility( areaSeries(), false ); setLegendVisibility( scatterSeries(), showScatterMarker ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QtCharts::QLineSeries* RiuQtChartsPlotCurve::lineSeries() const { if ( m_lineSeries ) return m_lineSeries; if ( m_plotWidget ) return dynamic_cast( m_plotWidget->getLineSeries( this ) ); return nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QtCharts::QScatterSeries* RiuQtChartsPlotCurve::scatterSeries() const { if ( m_scatterSeries ) return m_scatterSeries; if ( m_plotWidget ) return dynamic_cast( m_plotWidget->getScatterSeries( this ) ); return nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QtCharts::QAreaSeries* RiuQtChartsPlotCurve::areaSeries() const { if ( m_areaSeries ) return m_areaSeries; if ( m_plotWidget ) return dynamic_cast( m_plotWidget->getAreaSeries( this ) ); return nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setSymbol( RiuPlotCurveSymbol* symbol ) { if ( symbol ) { auto qtChartsSymbol = dynamic_cast( symbol ); CAF_ASSERT( qtChartsSymbol ); m_symbol.reset( symbol ); if ( scatterSeries() ) { qtChartsSymbol->applyToScatterSeries( scatterSeries() ); updateScatterSeries(); updateLineAndAreaSeries(); } } else { m_symbol.reset(); if ( scatterSeries() ) scatterSeries()->hide(); updateLineAndAreaSeries(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::updateLineAndAreaSeries() { bool isFilled = areaSeries() && areaSeries()->brush().style() != Qt::NoBrush; bool isLine = lineSeries() && lineSeries()->pen().style() != Qt::PenStyle::NoPen; if ( areaSeries() ) areaSeries()->setVisible( isFilled ); if ( lineSeries() ) lineSeries()->setVisible( isLine ); setVisibleInLegend( m_isVisibleInLegend ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiuPlotCurveSymbol* RiuQtChartsPlotCurve::createSymbol( RiuPlotCurveSymbol::PointSymbolEnum symbol ) const { return new RiuQtChartsPlotCurveSymbol( symbol ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setCurveFittingTolerance( double tolerance ) { // Not supported } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::setLegendIconSize( const QSize& iconSize ) { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QSize RiuQtChartsPlotCurve::legendIconSize() const { // Default from Qwt return QSize( 8, 8 ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QPixmap RiuQtChartsPlotCurve::legendIcon( const QSizeF& iconSize ) const { return QPixmap(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiuQtChartsPlotCurve::axisRangeChanged() { updateScatterSeries(); }