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']
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:

View File

@ -58,7 +58,6 @@ class QgsAbstractPlotSeries
void setName( const QString &name );
QgsSymbol *symbol() const;
void setSymbol( QgsSymbol *symbol /Transfer/ );
virtual QList<QVariant> categories() const;
private:
QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other );
@ -75,10 +74,7 @@ class QgsXyPlotSeries : QgsAbstractPlotSeries
QgsXyPlotSeries();
~QgsXyPlotSeries();
virtual QList<QVariant> 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

View File

@ -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:

View File

@ -58,7 +58,6 @@ class QgsAbstractPlotSeries
void setName( const QString &name );
QgsSymbol *symbol() const;
void setSymbol( QgsSymbol *symbol /Transfer/ );
virtual QList<QVariant> categories() const;
private:
QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other );
@ -75,10 +74,7 @@ class QgsXyPlotSeries : QgsAbstractPlotSeries
QgsXyPlotSeries();
~QgsXyPlotSeries();
virtual QList<QVariant> 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

View File

@ -30,8 +30,8 @@ void QgsBarChart::renderContent( QgsRenderContext &context, const QRectF &plotAr
return;
}
const QList<QVariant> 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<const QgsXyPlotSeries *>( series ) )
{
const QList<std::pair<QVariant, double>> data = xySeries->data();
for ( const std::pair<QVariant, double> &pair : data )
const QList<std::pair<double, double>> data = xySeries->data();
for ( const std::pair<double, double> &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<QVariant> 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<const QgsXyPlotSeries *>( series ) )
{
const QList<std::pair<QVariant, double>> data = xySeries->data();
const QList<std::pair<double, double>> data = xySeries->data();
QVector<QPointF> points;
QList<double> values;
points.fill( QPointF(), xAxis().type() == Qgis::PlotAxisType::ValueType ? data.size() : categories.size() );
int dataIndex = 0;
for ( const std::pair<QVariant, double> &pair : data )
for ( const std::pair<double, double> &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++;
}

View File

@ -335,12 +335,7 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData
QgsNumericFormatContext numericContext;
// categories
QList<QVariant> categories;
const QList<QgsAbstractPlotSeries *> 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<QVariant> QgsAbstractPlotSeries::categories() const
{
return QList<QVariant>();
}
//
// QgsXyPlotSeries
//
QList<QVariant> QgsXyPlotSeries::categories() 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
QList<std::pair<double, double>> 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 );
}

View File

@ -74,7 +74,6 @@ class CORE_EXPORT QgsAbstractPlotSeries
void setName( const QString &name );
QgsSymbol *symbol() const;
void setSymbol( QgsSymbol *symbol SIP_TRANSFER );
virtual QList<QVariant> categories() const;
private:
#ifdef SIP_RUN
@ -92,10 +91,8 @@ class CORE_EXPORT QgsXyPlotSeries : public QgsAbstractPlotSeries
QgsXyPlotSeries() = default;
~QgsXyPlotSeries() = default;
QList<QVariant> categories() const override;
QList<std::pair<QVariant, double>> data() const SIP_SKIP;
void append( const QVariant &x, const double &y );
QList<std::pair<double, double>> 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<std::pair<QVariant, double>> mData;
QList<std::pair<double, double>> 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<QgsAbstractPlotSeries *> mSeries;
QStringList mCategories;
};
/**