Skip to content

Commit

Permalink
Fix legend count is 0 if graduated/categorized expression uses geometry
Browse files Browse the repository at this point in the history
Likely fixes many other bugs too with graduated/categorized renderers

Fixes #15544
  • Loading branch information
nyalldawson committed Sep 24, 2018
1 parent 17567ee commit b5867bf
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 4 deletions.
Expand Up @@ -93,6 +93,8 @@ class QgsCategorizedSymbolRenderer : QgsFeatureRenderer

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

virtual bool filterNeedsGeometry() const;

virtual QString dump() const;

virtual QgsCategorizedSymbolRenderer *clone() const /Factory/;
Expand Down
Expand Up @@ -130,6 +130,8 @@ class QgsGraduatedSymbolRenderer : QgsFeatureRenderer

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

virtual bool filterNeedsGeometry() const;

virtual QString dump() const;

virtual QgsGraduatedSymbolRenderer *clone() const /Factory/;
Expand Down
13 changes: 13 additions & 0 deletions src/core/symbology/qgscategorizedsymbolrenderer.cpp
Expand Up @@ -452,6 +452,19 @@ QSet<QString> QgsCategorizedSymbolRenderer::usedAttributes( const QgsRenderConte
return attributes;
}

bool QgsCategorizedSymbolRenderer::filterNeedsGeometry() const
{
QgsExpression testExpr( mAttrName );
if ( !testExpr.hasParserError() )
{
QgsExpressionContext context;
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
testExpr.prepare( &context );
return testExpr.needsGeometry();
}
return false;
}

QString QgsCategorizedSymbolRenderer::dump() const
{
QString s = QStringLiteral( "CATEGORIZED: idx %1\n" ).arg( mAttrName );
Expand Down
1 change: 1 addition & 0 deletions src/core/symbology/qgscategorizedsymbolrenderer.h
Expand Up @@ -102,6 +102,7 @@ class CORE_EXPORT QgsCategorizedSymbolRenderer : public QgsFeatureRenderer
void startRender( QgsRenderContext &context, const QgsFields &fields ) override;
void stopRender( QgsRenderContext &context ) override;
QSet<QString> usedAttributes( const QgsRenderContext &context ) const override;
bool filterNeedsGeometry() const override;
QString dump() const override;
QgsCategorizedSymbolRenderer *clone() const override SIP_FACTORY;
void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const override;
Expand Down
13 changes: 13 additions & 0 deletions src/core/symbology/qgsgraduatedsymbolrenderer.cpp
Expand Up @@ -425,6 +425,19 @@ QSet<QString> QgsGraduatedSymbolRenderer::usedAttributes( const QgsRenderContext
return attributes;
}

bool QgsGraduatedSymbolRenderer::filterNeedsGeometry() const
{
QgsExpression testExpr( mAttrName );
if ( !testExpr.hasParserError() )
{
QgsExpressionContext context;
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
testExpr.prepare( &context );
return testExpr.needsGeometry();
}
return false;
}

bool QgsGraduatedSymbolRenderer::updateRangeSymbol( int rangeIndex, QgsSymbol *symbol )
{
if ( rangeIndex < 0 || rangeIndex >= mRanges.size() )
Expand Down
1 change: 1 addition & 0 deletions src/core/symbology/qgsgraduatedsymbolrenderer.h
Expand Up @@ -152,6 +152,7 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
void startRender( QgsRenderContext &context, const QgsFields &fields ) override;
void stopRender( QgsRenderContext &context ) override;
QSet<QString> usedAttributes( const QgsRenderContext &context ) const override;
bool filterNeedsGeometry() const override;
QString dump() const override;
QgsGraduatedSymbolRenderer *clone() const override SIP_FACTORY;
void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const override;
Expand Down
25 changes: 25 additions & 0 deletions tests/src/python/test_qgscategorizedsymbolrenderer.py
Expand Up @@ -458,6 +458,31 @@ def testMatchToSymbols(self):
self.assertEqual(symbol.color().name(), '#0aff0a')
renderer.stopRender(context)

def testUsedAttributes(self):
renderer = QgsCategorizedSymbolRenderer()
ctx = QgsRenderContext()

# attribute can contain either attribute name or an expression.
# Sometimes it is not possible to distinguish between those two,
# e.g. "a - b" can be both a valid attribute name or expression.
# Since we do not have access to fields here, the method should return both options.
renderer.setClassAttribute("value")
self.assertEqual(renderer.usedAttributes(ctx), {"value"})
renderer.setClassAttribute("value - 1")
self.assertEqual(renderer.usedAttributes(ctx), {"value", "value - 1"})
renderer.setClassAttribute("valuea - valueb")
self.assertEqual(renderer.usedAttributes(ctx), {"valuea", "valuea - valueb", "valueb"})

def testFilterNeedsGeometry(self):
renderer = QgsCategorizedSymbolRenderer()

renderer.setClassAttribute("value")
self.assertFalse(renderer.filterNeedsGeometry())
renderer.setClassAttribute("$area")
self.assertTrue(renderer.filterNeedsGeometry())
renderer.setClassAttribute("value - $area")
self.assertTrue(renderer.filterNeedsGeometry())


if __name__ == "__main__":
unittest.main()
27 changes: 23 additions & 4 deletions tests/src/python/test_qgsgraduatedsymbolrenderer.py
Expand Up @@ -450,11 +450,30 @@ def testQgsGraduatedSymbolRenderer_3(self):
'(0.5000-1.0000,1.0000-1.1000,1.1000-1.2000,1.2000-5.0000,)',
'Quantile classification not correct')

# Tests still needed
def testUsedAttributes(self):
renderer = QgsGraduatedSymbolRenderer()
ctx = QgsRenderContext()

# attribute can contain either attribute name or an expression.
# Sometimes it is not possible to distinguish between those two,
# e.g. "a - b" can be both a valid attribute name or expression.
# Since we do not have access to fields here, the method should return both options.
renderer.setClassAttribute("value")
self.assertEqual(renderer.usedAttributes(ctx), {"value"})
renderer.setClassAttribute("value - 1")
self.assertEqual(renderer.usedAttributes(ctx), {"value", "value - 1"})
renderer.setClassAttribute("valuea - valueb")
self.assertEqual(renderer.usedAttributes(ctx), {"valuea", "valuea - valueb", "valueb"})

# Other calculation method tests
# createRenderer function
# symbolForFeature correctly selects range
def testFilterNeedsGeometry(self):
renderer = QgsGraduatedSymbolRenderer()

renderer.setClassAttribute("value")
self.assertFalse(renderer.filterNeedsGeometry())
renderer.setClassAttribute("$area")
self.assertTrue(renderer.filterNeedsGeometry())
renderer.setClassAttribute("value - $area")
self.assertTrue(renderer.filterNeedsGeometry())


if __name__ == "__main__":
Expand Down

0 comments on commit b5867bf

Please sign in to comment.