From b0a73b65e398b409c2746f44311ced3be1c22e65 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Mon, 23 Jun 2025 15:37:05 +0700 Subject: [PATCH] Rework categories handling, move it into the QgsPlotData to increase logic robustness and simplicity --- python/PyQt6/core/auto_additions/qgsplot.py | 16 +++---- .../core/auto_generated/plot/qgsplot.sip.in | 9 ++-- python/core/auto_additions/qgsplot.py | 16 +++---- .../core/auto_generated/plot/qgsplot.sip.in | 9 ++-- src/core/plot/qgschart.cpp | 26 +++++----- src/core/plot/qgsplot.cpp | 47 +++++++------------ src/core/plot/qgsplot.h | 13 ++--- 7 files changed, 59 insertions(+), 77 deletions(-) diff --git a/python/PyQt6/core/auto_additions/qgsplot.py b/python/PyQt6/core/auto_additions/qgsplot.py index c0197505b84..b230d3f6bac 100644 --- a/python/PyQt6/core/auto_additions/qgsplot.py +++ b/python/PyQt6/core/auto_additions/qgsplot.py @@ -13,11 +13,6 @@ try: QgsPlot.__group__ = ['plot'] except (NameError, AttributeError): pass -try: - QgsAbstractPlotSeries.__virtual_methods__ = ['categories'] - QgsAbstractPlotSeries.__group__ = ['plot'] -except (NameError, AttributeError): - pass try: Qgs2DPlot.__virtual_methods__ = ['render', 'renderContent', 'interiorPlotArea'] Qgs2DPlot.__overridden_methods__ = ['writeXml', 'readXml'] @@ -25,13 +20,16 @@ try: except (NameError, AttributeError): pass try: - QgsXyPlotSeries.__overridden_methods__ = ['categories'] - QgsXyPlotSeries.__group__ = ['plot'] + Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea'] + Qgs2DXyPlot.__group__ = ['plot'] except (NameError, AttributeError): pass try: - Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea'] - Qgs2DXyPlot.__group__ = ['plot'] + QgsAbstractPlotSeries.__group__ = ['plot'] +except (NameError, AttributeError): + pass +try: + QgsXyPlotSeries.__group__ = ['plot'] except (NameError, AttributeError): pass try: diff --git a/python/PyQt6/core/auto_generated/plot/qgsplot.sip.in b/python/PyQt6/core/auto_generated/plot/qgsplot.sip.in index 1f24db57085..4621d6f5a88 100644 --- a/python/PyQt6/core/auto_generated/plot/qgsplot.sip.in +++ b/python/PyQt6/core/auto_generated/plot/qgsplot.sip.in @@ -58,7 +58,6 @@ class QgsAbstractPlotSeries void setName( const QString &name ); QgsSymbol *symbol() const; void setSymbol( QgsSymbol *symbol /Transfer/ ); - virtual QList categories() const; private: QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other ); @@ -75,10 +74,7 @@ class QgsXyPlotSeries : QgsAbstractPlotSeries QgsXyPlotSeries(); ~QgsXyPlotSeries(); - virtual QList categories() const; - - - void append( const QVariant &x, const double &y ); + void append( const double &x, const double &y ); void clear(); private: @@ -100,6 +96,9 @@ class QgsPlotData void addSeries( QgsAbstractPlotSeries *series /Transfer/ ); void clearSeries(); + QStringList categories() const; + void setCategories( const QStringList &categories ); + }; class QgsPlotAxis diff --git a/python/core/auto_additions/qgsplot.py b/python/core/auto_additions/qgsplot.py index c0197505b84..b230d3f6bac 100644 --- a/python/core/auto_additions/qgsplot.py +++ b/python/core/auto_additions/qgsplot.py @@ -13,11 +13,6 @@ try: QgsPlot.__group__ = ['plot'] except (NameError, AttributeError): pass -try: - QgsAbstractPlotSeries.__virtual_methods__ = ['categories'] - QgsAbstractPlotSeries.__group__ = ['plot'] -except (NameError, AttributeError): - pass try: Qgs2DPlot.__virtual_methods__ = ['render', 'renderContent', 'interiorPlotArea'] Qgs2DPlot.__overridden_methods__ = ['writeXml', 'readXml'] @@ -25,13 +20,16 @@ try: except (NameError, AttributeError): pass try: - QgsXyPlotSeries.__overridden_methods__ = ['categories'] - QgsXyPlotSeries.__group__ = ['plot'] + Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea'] + Qgs2DXyPlot.__group__ = ['plot'] except (NameError, AttributeError): pass try: - Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea'] - Qgs2DXyPlot.__group__ = ['plot'] + QgsAbstractPlotSeries.__group__ = ['plot'] +except (NameError, AttributeError): + pass +try: + QgsXyPlotSeries.__group__ = ['plot'] except (NameError, AttributeError): pass try: diff --git a/python/core/auto_generated/plot/qgsplot.sip.in b/python/core/auto_generated/plot/qgsplot.sip.in index 1f24db57085..4621d6f5a88 100644 --- a/python/core/auto_generated/plot/qgsplot.sip.in +++ b/python/core/auto_generated/plot/qgsplot.sip.in @@ -58,7 +58,6 @@ class QgsAbstractPlotSeries void setName( const QString &name ); QgsSymbol *symbol() const; void setSymbol( QgsSymbol *symbol /Transfer/ ); - virtual QList categories() const; private: QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other ); @@ -75,10 +74,7 @@ class QgsXyPlotSeries : QgsAbstractPlotSeries QgsXyPlotSeries(); ~QgsXyPlotSeries(); - virtual QList categories() const; - - - void append( const QVariant &x, const double &y ); + void append( const double &x, const double &y ); void clear(); private: @@ -100,6 +96,9 @@ class QgsPlotData void addSeries( QgsAbstractPlotSeries *series /Transfer/ ); void clearSeries(); + QStringList categories() const; + void setCategories( const QStringList &categories ); + }; class QgsPlotAxis diff --git a/src/core/plot/qgschart.cpp b/src/core/plot/qgschart.cpp index b3ec744e267..3343cc496b0 100644 --- a/src/core/plot/qgschart.cpp +++ b/src/core/plot/qgschart.cpp @@ -30,8 +30,8 @@ void QgsBarChart::renderContent( QgsRenderContext &context, const QRectF &plotAr return; } - const QList categories = seriesList.at( 0 )->categories(); - if ( categories.isEmpty() ) + const QStringList categories = plotData.categories(); + if ( xAxis().type() == Qgis::PlotAxisType::ValueType && categories.isEmpty() ) { return; } @@ -60,17 +60,17 @@ void QgsBarChart::renderContent( QgsRenderContext &context, const QRectF &plotAr const double barStartAdjustement = -( barsWidth / 2 ) + barWidth * seriesIndex; if ( const QgsXyPlotSeries *xySeries = dynamic_cast( series ) ) { - const QList> data = xySeries->data(); - for ( const std::pair &pair : data ) + const QList> data = xySeries->data(); + for ( const std::pair &pair : data ) { double x, y; if ( xAxis().type() == Qgis::PlotAxisType::ValueType ) { - x = ( pair.first.toDouble() ) * xScale + barStartAdjustement; + x = ( pair.first - xMinimum() ) * xScale + barStartAdjustement; } else if ( xAxis().type() == Qgis::PlotAxisType::CategoryType ) { - x = ( categoriesWidth * categories.indexOf( pair.first ) ) + ( categoriesWidth / 2 ) + barStartAdjustement; + x = ( categoriesWidth * pair.first ) + ( categoriesWidth / 2 ) + barStartAdjustement; } y = ( pair.second - yMinimum() ) * yScale; @@ -100,8 +100,8 @@ void QgsLineChart::renderContent( QgsRenderContext &context, const QRectF &plotA return; } - const QList categories = seriesList.at( 0 )->categories(); - if ( categories.isEmpty() ) + const QStringList categories = plotData.categories(); + if ( xAxis().type() == Qgis::PlotAxisType::ValueType && categories.isEmpty() ) { return; } @@ -127,26 +127,26 @@ void QgsLineChart::renderContent( QgsRenderContext &context, const QRectF &plotA if ( const QgsXyPlotSeries *xySeries = dynamic_cast( series ) ) { - const QList> data = xySeries->data(); + const QList> data = xySeries->data(); QVector points; QList values; points.fill( QPointF(), xAxis().type() == Qgis::PlotAxisType::ValueType ? data.size() : categories.size() ); int dataIndex = 0; - for ( const std::pair &pair : data ) + for ( const std::pair &pair : data ) { double x, y; if ( xAxis().type() == Qgis::PlotAxisType::ValueType ) { - x = ( pair.first.toDouble() ) * xScale; + x = ( pair.first - xMinimum() ) * xScale; } else if ( xAxis().type() == Qgis::PlotAxisType::CategoryType ) { - x = ( categoriesWidth * categories.indexOf( pair.first ) ) + ( categoriesWidth / 2 ); + x = ( categoriesWidth * pair.first ) + ( categoriesWidth / 2 ); } y = ( pair.second - yMinimum() ) * yScale; values << pair.second; - points.replace( xAxis().type() == Qgis::PlotAxisType::ValueType ? dataIndex : categories.indexOf( pair.first ), QPointF( plotArea.x() + x, + points.replace( xAxis().type() == Qgis::PlotAxisType::ValueType ? dataIndex : pair.first, QPointF( plotArea.x() + x, plotArea.y() + plotArea.height() - y ) ); dataIndex++; } diff --git a/src/core/plot/qgsplot.cpp b/src/core/plot/qgsplot.cpp index e4b9d8adb86..ee37e202a1b 100644 --- a/src/core/plot/qgsplot.cpp +++ b/src/core/plot/qgsplot.cpp @@ -335,12 +335,7 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData QgsNumericFormatContext numericContext; // categories - QList categories; - const QList seriesList = plotData.series(); - if ( !seriesList.isEmpty() ) - { - categories = seriesList.at( 0 )->categories(); - } + const QStringList categories = plotData.categories(); // calculate text metrics double maxYAxisLabelWidth = 0; @@ -386,8 +381,7 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData { for ( int i = 0; i < categories.size(); i++ ) { - const QString text = categories.at( i ).toString(); - maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { text } ) ); + maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { categories.at( i ) } ) ); } } @@ -521,9 +515,8 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData { const double currentX = ( i * categoryWidth ) + categoryWidth / 2.0; plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), categories.at( i ), true ) ); - const QString text = categories.at( i ).toString(); QgsTextRenderer::drawText( QPointF( currentX + chartAreaLeft, mSize.height() - context.convertToPainterUnits( mMargins.bottom(), Qgis::RenderUnit::Millimeters ) ), - 0, Qgis::TextHorizontalAlignment::Center, { text }, context, mXAxis.textFormat() ); + 0, Qgis::TextHorizontalAlignment::Center, { categories.at( i ) }, context, mXAxis.textFormat() ); } } @@ -582,12 +575,11 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData { const double currentY = ( i * categoryHeight ) + categoryHeight / 2.0; plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), categories.at( i ), true ) ); - const QString text = categories.at( i ).toString(); - const double height = QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text } ); + const double height = QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { categories.at( i ) } ); QgsTextRenderer::drawText( QPointF( maxYAxisLabelWidth + context.convertToPainterUnits( mMargins.left(), Qgis::RenderUnit::Millimeters ), chartAreaBottom - currentY + height / 2 ), - 0, Qgis::TextHorizontalAlignment::Right, { text }, context, mYAxis.textFormat(), false ); + 0, Qgis::TextHorizontalAlignment::Right, { categories.at( i ) }, context, mYAxis.textFormat(), false ); } } @@ -947,6 +939,16 @@ void QgsPlotData::clearSeries() mSeries.clear(); } +QStringList QgsPlotData::categories() const +{ + return mCategories; +} + +void QgsPlotData::setCategories( const QStringList &categories ) +{ + mCategories = categories; +} + // // QgsAbstractPlotSeries // @@ -971,31 +973,16 @@ void QgsAbstractPlotSeries::setSymbol( QgsSymbol *symbol ) mSymbol.reset( symbol ); } -QList QgsAbstractPlotSeries::categories() const -{ - return QList(); -} - // // QgsXyPlotSeries // -QList QgsXyPlotSeries::categories() const -{ - QList categories; - for ( const std::pair &pair : std::as_const( mData ) ) - { - categories << pair.first; - } - return categories; -} - -QList> QgsXyPlotSeries::data() const +QList> QgsXyPlotSeries::data() const { return mData; } -void QgsXyPlotSeries::append( const QVariant &x, const double &y ) +void QgsXyPlotSeries::append( const double &x, const double &y ) { mData << std::make_pair( x, y ); } diff --git a/src/core/plot/qgsplot.h b/src/core/plot/qgsplot.h index ce8dd4d8659..e3586607fcf 100644 --- a/src/core/plot/qgsplot.h +++ b/src/core/plot/qgsplot.h @@ -74,7 +74,6 @@ class CORE_EXPORT QgsAbstractPlotSeries void setName( const QString &name ); QgsSymbol *symbol() const; void setSymbol( QgsSymbol *symbol SIP_TRANSFER ); - virtual QList categories() const; private: #ifdef SIP_RUN @@ -92,10 +91,8 @@ class CORE_EXPORT QgsXyPlotSeries : public QgsAbstractPlotSeries QgsXyPlotSeries() = default; ~QgsXyPlotSeries() = default; - QList categories() const override; - - QList> data() const SIP_SKIP; - void append( const QVariant &x, const double &y ); + QList> data() const SIP_SKIP; + void append( const double &x, const double &y ); void clear(); private: @@ -103,7 +100,7 @@ class CORE_EXPORT QgsXyPlotSeries : public QgsAbstractPlotSeries QgsXyPlotSeries( const QgsXyPlotSeries &other ); #endif - QList> mData; + QList> mData; }; class CORE_EXPORT QgsPlotData @@ -117,9 +114,13 @@ class CORE_EXPORT QgsPlotData void addSeries( QgsAbstractPlotSeries *series SIP_TRANSFER ); void clearSeries(); + QStringList categories() const; + void setCategories( const QStringList &categories ); + private: QList mSeries; + QStringList mCategories; }; /**