Skip to content

Commit b5867bf

Browse files
committed
Fix legend count is 0 if graduated/categorized expression uses geometry
Likely fixes many other bugs too with graduated/categorized renderers Fixes #15544
1 parent 17567ee commit b5867bf

8 files changed

+80
-4
lines changed

python/core/auto_generated/symbology/qgscategorizedsymbolrenderer.sip.in

+2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class QgsCategorizedSymbolRenderer : QgsFeatureRenderer
9393

9494
virtual QSet<QString> usedAttributes( const QgsRenderContext &context ) const;
9595

96+
virtual bool filterNeedsGeometry() const;
97+
9698
virtual QString dump() const;
9799

98100
virtual QgsCategorizedSymbolRenderer *clone() const /Factory/;

python/core/auto_generated/symbology/qgsgraduatedsymbolrenderer.sip.in

+2
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ class QgsGraduatedSymbolRenderer : QgsFeatureRenderer
130130

131131
virtual QSet<QString> usedAttributes( const QgsRenderContext &context ) const;
132132

133+
virtual bool filterNeedsGeometry() const;
134+
133135
virtual QString dump() const;
134136

135137
virtual QgsGraduatedSymbolRenderer *clone() const /Factory/;

src/core/symbology/qgscategorizedsymbolrenderer.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,19 @@ QSet<QString> QgsCategorizedSymbolRenderer::usedAttributes( const QgsRenderConte
452452
return attributes;
453453
}
454454

455+
bool QgsCategorizedSymbolRenderer::filterNeedsGeometry() const
456+
{
457+
QgsExpression testExpr( mAttrName );
458+
if ( !testExpr.hasParserError() )
459+
{
460+
QgsExpressionContext context;
461+
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
462+
testExpr.prepare( &context );
463+
return testExpr.needsGeometry();
464+
}
465+
return false;
466+
}
467+
455468
QString QgsCategorizedSymbolRenderer::dump() const
456469
{
457470
QString s = QStringLiteral( "CATEGORIZED: idx %1\n" ).arg( mAttrName );

src/core/symbology/qgscategorizedsymbolrenderer.h

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class CORE_EXPORT QgsCategorizedSymbolRenderer : public QgsFeatureRenderer
102102
void startRender( QgsRenderContext &context, const QgsFields &fields ) override;
103103
void stopRender( QgsRenderContext &context ) override;
104104
QSet<QString> usedAttributes( const QgsRenderContext &context ) const override;
105+
bool filterNeedsGeometry() const override;
105106
QString dump() const override;
106107
QgsCategorizedSymbolRenderer *clone() const override SIP_FACTORY;
107108
void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const override;

src/core/symbology/qgsgraduatedsymbolrenderer.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,19 @@ QSet<QString> QgsGraduatedSymbolRenderer::usedAttributes( const QgsRenderContext
425425
return attributes;
426426
}
427427

428+
bool QgsGraduatedSymbolRenderer::filterNeedsGeometry() const
429+
{
430+
QgsExpression testExpr( mAttrName );
431+
if ( !testExpr.hasParserError() )
432+
{
433+
QgsExpressionContext context;
434+
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
435+
testExpr.prepare( &context );
436+
return testExpr.needsGeometry();
437+
}
438+
return false;
439+
}
440+
428441
bool QgsGraduatedSymbolRenderer::updateRangeSymbol( int rangeIndex, QgsSymbol *symbol )
429442
{
430443
if ( rangeIndex < 0 || rangeIndex >= mRanges.size() )

src/core/symbology/qgsgraduatedsymbolrenderer.h

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
152152
void startRender( QgsRenderContext &context, const QgsFields &fields ) override;
153153
void stopRender( QgsRenderContext &context ) override;
154154
QSet<QString> usedAttributes( const QgsRenderContext &context ) const override;
155+
bool filterNeedsGeometry() const override;
155156
QString dump() const override;
156157
QgsGraduatedSymbolRenderer *clone() const override SIP_FACTORY;
157158
void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const override;

tests/src/python/test_qgscategorizedsymbolrenderer.py

+25
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,31 @@ def testMatchToSymbols(self):
458458
self.assertEqual(symbol.color().name(), '#0aff0a')
459459
renderer.stopRender(context)
460460

461+
def testUsedAttributes(self):
462+
renderer = QgsCategorizedSymbolRenderer()
463+
ctx = QgsRenderContext()
464+
465+
# attribute can contain either attribute name or an expression.
466+
# Sometimes it is not possible to distinguish between those two,
467+
# e.g. "a - b" can be both a valid attribute name or expression.
468+
# Since we do not have access to fields here, the method should return both options.
469+
renderer.setClassAttribute("value")
470+
self.assertEqual(renderer.usedAttributes(ctx), {"value"})
471+
renderer.setClassAttribute("value - 1")
472+
self.assertEqual(renderer.usedAttributes(ctx), {"value", "value - 1"})
473+
renderer.setClassAttribute("valuea - valueb")
474+
self.assertEqual(renderer.usedAttributes(ctx), {"valuea", "valuea - valueb", "valueb"})
475+
476+
def testFilterNeedsGeometry(self):
477+
renderer = QgsCategorizedSymbolRenderer()
478+
479+
renderer.setClassAttribute("value")
480+
self.assertFalse(renderer.filterNeedsGeometry())
481+
renderer.setClassAttribute("$area")
482+
self.assertTrue(renderer.filterNeedsGeometry())
483+
renderer.setClassAttribute("value - $area")
484+
self.assertTrue(renderer.filterNeedsGeometry())
485+
461486

462487
if __name__ == "__main__":
463488
unittest.main()

tests/src/python/test_qgsgraduatedsymbolrenderer.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -450,11 +450,30 @@ def testQgsGraduatedSymbolRenderer_3(self):
450450
'(0.5000-1.0000,1.0000-1.1000,1.1000-1.2000,1.2000-5.0000,)',
451451
'Quantile classification not correct')
452452

453-
# Tests still needed
453+
def testUsedAttributes(self):
454+
renderer = QgsGraduatedSymbolRenderer()
455+
ctx = QgsRenderContext()
456+
457+
# attribute can contain either attribute name or an expression.
458+
# Sometimes it is not possible to distinguish between those two,
459+
# e.g. "a - b" can be both a valid attribute name or expression.
460+
# Since we do not have access to fields here, the method should return both options.
461+
renderer.setClassAttribute("value")
462+
self.assertEqual(renderer.usedAttributes(ctx), {"value"})
463+
renderer.setClassAttribute("value - 1")
464+
self.assertEqual(renderer.usedAttributes(ctx), {"value", "value - 1"})
465+
renderer.setClassAttribute("valuea - valueb")
466+
self.assertEqual(renderer.usedAttributes(ctx), {"valuea", "valuea - valueb", "valueb"})
454467

455-
# Other calculation method tests
456-
# createRenderer function
457-
# symbolForFeature correctly selects range
468+
def testFilterNeedsGeometry(self):
469+
renderer = QgsGraduatedSymbolRenderer()
470+
471+
renderer.setClassAttribute("value")
472+
self.assertFalse(renderer.filterNeedsGeometry())
473+
renderer.setClassAttribute("$area")
474+
self.assertTrue(renderer.filterNeedsGeometry())
475+
renderer.setClassAttribute("value - $area")
476+
self.assertTrue(renderer.filterNeedsGeometry())
458477

459478

460479
if __name__ == "__main__":

0 commit comments

Comments
 (0)