Rework categories handling, move it into the QgsPlotData to increase logic robustness and simplicity

This commit is contained in:
Mathieu Pellerin 2025-06-23 15:37:05 +07:00 committed by Nyall Dawson
parent a0c45d9380
commit b0a73b65e3
7 changed files with 59 additions and 77 deletions

View File

@ -13,11 +13,6 @@ try:
QgsPlot.__group__ = ['plot'] QgsPlot.__group__ = ['plot']
except (NameError, AttributeError): except (NameError, AttributeError):
pass pass
try:
QgsAbstractPlotSeries.__virtual_methods__ = ['categories']
QgsAbstractPlotSeries.__group__ = ['plot']
except (NameError, AttributeError):
pass
try: try:
Qgs2DPlot.__virtual_methods__ = ['render', 'renderContent', 'interiorPlotArea'] Qgs2DPlot.__virtual_methods__ = ['render', 'renderContent', 'interiorPlotArea']
Qgs2DPlot.__overridden_methods__ = ['writeXml', 'readXml'] Qgs2DPlot.__overridden_methods__ = ['writeXml', 'readXml']
@ -25,13 +20,16 @@ try:
except (NameError, AttributeError): except (NameError, AttributeError):
pass pass
try: try:
QgsXyPlotSeries.__overridden_methods__ = ['categories'] Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea']
QgsXyPlotSeries.__group__ = ['plot'] Qgs2DXyPlot.__group__ = ['plot']
except (NameError, AttributeError): except (NameError, AttributeError):
pass pass
try: try:
Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea'] QgsAbstractPlotSeries.__group__ = ['plot']
Qgs2DXyPlot.__group__ = ['plot'] except (NameError, AttributeError):
pass
try:
QgsXyPlotSeries.__group__ = ['plot']
except (NameError, AttributeError): except (NameError, AttributeError):
pass pass
try: try:

View File

@ -58,7 +58,6 @@ class QgsAbstractPlotSeries
void setName( const QString &name ); void setName( const QString &name );
QgsSymbol *symbol() const; QgsSymbol *symbol() const;
void setSymbol( QgsSymbol *symbol /Transfer/ ); void setSymbol( QgsSymbol *symbol /Transfer/ );
virtual QList<QVariant> categories() const;
private: private:
QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other ); QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other );
@ -75,10 +74,7 @@ class QgsXyPlotSeries : QgsAbstractPlotSeries
QgsXyPlotSeries(); QgsXyPlotSeries();
~QgsXyPlotSeries(); ~QgsXyPlotSeries();
virtual QList<QVariant> categories() const; void append( const double &x, const double &y );
void append( const QVariant &x, const double &y );
void clear(); void clear();
private: private:
@ -100,6 +96,9 @@ class QgsPlotData
void addSeries( QgsAbstractPlotSeries *series /Transfer/ ); void addSeries( QgsAbstractPlotSeries *series /Transfer/ );
void clearSeries(); void clearSeries();
QStringList categories() const;
void setCategories( const QStringList &categories );
}; };
class QgsPlotAxis class QgsPlotAxis

View File

@ -13,11 +13,6 @@ try:
QgsPlot.__group__ = ['plot'] QgsPlot.__group__ = ['plot']
except (NameError, AttributeError): except (NameError, AttributeError):
pass pass
try:
QgsAbstractPlotSeries.__virtual_methods__ = ['categories']
QgsAbstractPlotSeries.__group__ = ['plot']
except (NameError, AttributeError):
pass
try: try:
Qgs2DPlot.__virtual_methods__ = ['render', 'renderContent', 'interiorPlotArea'] Qgs2DPlot.__virtual_methods__ = ['render', 'renderContent', 'interiorPlotArea']
Qgs2DPlot.__overridden_methods__ = ['writeXml', 'readXml'] Qgs2DPlot.__overridden_methods__ = ['writeXml', 'readXml']
@ -25,13 +20,16 @@ try:
except (NameError, AttributeError): except (NameError, AttributeError):
pass pass
try: try:
QgsXyPlotSeries.__overridden_methods__ = ['categories'] Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea']
QgsXyPlotSeries.__group__ = ['plot'] Qgs2DXyPlot.__group__ = ['plot']
except (NameError, AttributeError): except (NameError, AttributeError):
pass pass
try: try:
Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea'] QgsAbstractPlotSeries.__group__ = ['plot']
Qgs2DXyPlot.__group__ = ['plot'] except (NameError, AttributeError):
pass
try:
QgsXyPlotSeries.__group__ = ['plot']
except (NameError, AttributeError): except (NameError, AttributeError):
pass pass
try: try:

View File

@ -58,7 +58,6 @@ class QgsAbstractPlotSeries
void setName( const QString &name ); void setName( const QString &name );
QgsSymbol *symbol() const; QgsSymbol *symbol() const;
void setSymbol( QgsSymbol *symbol /Transfer/ ); void setSymbol( QgsSymbol *symbol /Transfer/ );
virtual QList<QVariant> categories() const;
private: private:
QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other ); QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other );
@ -75,10 +74,7 @@ class QgsXyPlotSeries : QgsAbstractPlotSeries
QgsXyPlotSeries(); QgsXyPlotSeries();
~QgsXyPlotSeries(); ~QgsXyPlotSeries();
virtual QList<QVariant> categories() const; void append( const double &x, const double &y );
void append( const QVariant &x, const double &y );
void clear(); void clear();
private: private:
@ -100,6 +96,9 @@ class QgsPlotData
void addSeries( QgsAbstractPlotSeries *series /Transfer/ ); void addSeries( QgsAbstractPlotSeries *series /Transfer/ );
void clearSeries(); void clearSeries();
QStringList categories() const;
void setCategories( const QStringList &categories );
}; };
class QgsPlotAxis class QgsPlotAxis

View File

@ -30,8 +30,8 @@ void QgsBarChart::renderContent( QgsRenderContext &context, const QRectF &plotAr
return; return;
} }
const QList<QVariant> categories = seriesList.at( 0 )->categories(); const QStringList categories = plotData.categories();
if ( categories.isEmpty() ) if ( xAxis().type() == Qgis::PlotAxisType::ValueType && categories.isEmpty() )
{ {
return; return;
} }
@ -60,17 +60,17 @@ void QgsBarChart::renderContent( QgsRenderContext &context, const QRectF &plotAr
const double barStartAdjustement = -( barsWidth / 2 ) + barWidth * seriesIndex; const double barStartAdjustement = -( barsWidth / 2 ) + barWidth * seriesIndex;
if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) ) if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) )
{ {
const QList<std::pair<QVariant, double>> data = xySeries->data(); const QList<std::pair<double, double>> data = xySeries->data();
for ( const std::pair<QVariant, double> &pair : data ) for ( const std::pair<double, double> &pair : data )
{ {
double x, y; double x, y;
if ( xAxis().type() == Qgis::PlotAxisType::ValueType ) 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 ) 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; y = ( pair.second - yMinimum() ) * yScale;
@ -100,8 +100,8 @@ void QgsLineChart::renderContent( QgsRenderContext &context, const QRectF &plotA
return; return;
} }
const QList<QVariant> categories = seriesList.at( 0 )->categories(); const QStringList categories = plotData.categories();
if ( categories.isEmpty() ) if ( xAxis().type() == Qgis::PlotAxisType::ValueType && categories.isEmpty() )
{ {
return; return;
} }
@ -127,26 +127,26 @@ void QgsLineChart::renderContent( QgsRenderContext &context, const QRectF &plotA
if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) ) if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) )
{ {
const QList<std::pair<QVariant, double>> data = xySeries->data(); const QList<std::pair<double, double>> data = xySeries->data();
QVector<QPointF> points; QVector<QPointF> points;
QList<double> values; QList<double> values;
points.fill( QPointF(), xAxis().type() == Qgis::PlotAxisType::ValueType ? data.size() : categories.size() ); points.fill( QPointF(), xAxis().type() == Qgis::PlotAxisType::ValueType ? data.size() : categories.size() );
int dataIndex = 0; int dataIndex = 0;
for ( const std::pair<QVariant, double> &pair : data ) for ( const std::pair<double, double> &pair : data )
{ {
double x, y; double x, y;
if ( xAxis().type() == Qgis::PlotAxisType::ValueType ) if ( xAxis().type() == Qgis::PlotAxisType::ValueType )
{ {
x = ( pair.first.toDouble() ) * xScale; x = ( pair.first - xMinimum() ) * xScale;
} }
else if ( xAxis().type() == Qgis::PlotAxisType::CategoryType ) 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; y = ( pair.second - yMinimum() ) * yScale;
values << pair.second; 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 ) ); plotArea.y() + plotArea.height() - y ) );
dataIndex++; dataIndex++;
} }

View File

@ -335,12 +335,7 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData
QgsNumericFormatContext numericContext; QgsNumericFormatContext numericContext;
// categories // categories
QList<QVariant> categories; const QStringList categories = plotData.categories();
const QList<QgsAbstractPlotSeries *> seriesList = plotData.series();
if ( !seriesList.isEmpty() )
{
categories = seriesList.at( 0 )->categories();
}
// calculate text metrics // calculate text metrics
double maxYAxisLabelWidth = 0; double maxYAxisLabelWidth = 0;
@ -386,8 +381,7 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData
{ {
for ( int i = 0; i < categories.size(); i++ ) for ( int i = 0; i < categories.size(); i++ )
{ {
const QString text = categories.at( i ).toString(); maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { categories.at( i ) } ) );
maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { text } ) );
} }
} }
@ -521,9 +515,8 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData
{ {
const double currentX = ( i * categoryWidth ) + categoryWidth / 2.0; const double currentX = ( i * categoryWidth ) + categoryWidth / 2.0;
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), categories.at( i ), true ) ); 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 ) ), 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; const double currentY = ( i * categoryHeight ) + categoryHeight / 2.0;
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), categories.at( i ), true ) ); 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(), { categories.at( i ) } );
const double height = QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text } );
QgsTextRenderer::drawText( QPointF( QgsTextRenderer::drawText( QPointF(
maxYAxisLabelWidth + context.convertToPainterUnits( mMargins.left(), Qgis::RenderUnit::Millimeters ), maxYAxisLabelWidth + context.convertToPainterUnits( mMargins.left(), Qgis::RenderUnit::Millimeters ),
chartAreaBottom - currentY + height / 2 ), 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(); mSeries.clear();
} }
QStringList QgsPlotData::categories() const
{
return mCategories;
}
void QgsPlotData::setCategories( const QStringList &categories )
{
mCategories = categories;
}
// //
// QgsAbstractPlotSeries // QgsAbstractPlotSeries
// //
@ -971,31 +973,16 @@ void QgsAbstractPlotSeries::setSymbol( QgsSymbol *symbol )
mSymbol.reset( symbol ); mSymbol.reset( symbol );
} }
QList<QVariant> QgsAbstractPlotSeries::categories() const
{
return QList<QVariant>();
}
// //
// QgsXyPlotSeries // QgsXyPlotSeries
// //
QList<QVariant> QgsXyPlotSeries::categories() const QList<std::pair<double, double>> QgsXyPlotSeries::data() const
{
QList<QVariant> categories;
for ( const std::pair<QVariant, double> &pair : std::as_const( mData ) )
{
categories << pair.first;
}
return categories;
}
QList<std::pair<QVariant, double>> QgsXyPlotSeries::data() const
{ {
return mData; 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 ); mData << std::make_pair( x, y );
} }

View File

@ -74,7 +74,6 @@ class CORE_EXPORT QgsAbstractPlotSeries
void setName( const QString &name ); void setName( const QString &name );
QgsSymbol *symbol() const; QgsSymbol *symbol() const;
void setSymbol( QgsSymbol *symbol SIP_TRANSFER ); void setSymbol( QgsSymbol *symbol SIP_TRANSFER );
virtual QList<QVariant> categories() const;
private: private:
#ifdef SIP_RUN #ifdef SIP_RUN
@ -92,10 +91,8 @@ class CORE_EXPORT QgsXyPlotSeries : public QgsAbstractPlotSeries
QgsXyPlotSeries() = default; QgsXyPlotSeries() = default;
~QgsXyPlotSeries() = default; ~QgsXyPlotSeries() = default;
QList<QVariant> categories() const override; QList<std::pair<double, double>> data() const SIP_SKIP;
void append( const double &x, const double &y );
QList<std::pair<QVariant, double>> data() const SIP_SKIP;
void append( const QVariant &x, const double &y );
void clear(); void clear();
private: private:
@ -103,7 +100,7 @@ class CORE_EXPORT QgsXyPlotSeries : public QgsAbstractPlotSeries
QgsXyPlotSeries( const QgsXyPlotSeries &other ); QgsXyPlotSeries( const QgsXyPlotSeries &other );
#endif #endif
QList<std::pair<QVariant, double>> mData; QList<std::pair<double, double>> mData;
}; };
class CORE_EXPORT QgsPlotData class CORE_EXPORT QgsPlotData
@ -117,9 +114,13 @@ class CORE_EXPORT QgsPlotData
void addSeries( QgsAbstractPlotSeries *series SIP_TRANSFER ); void addSeries( QgsAbstractPlotSeries *series SIP_TRANSFER );
void clearSeries(); void clearSeries();
QStringList categories() const;
void setCategories( const QStringList &categories );
private: private:
QList<QgsAbstractPlotSeries *> mSeries; QList<QgsAbstractPlotSeries *> mSeries;
QStringList mCategories;
}; };
/** /**