Fix legend count is 0 if graduated/categorized expression uses geometry

Likely fixes many other bugs too with graduated/categorized renderers

Fixes #15544
This commit is contained in:
Nyall Dawson 2018-09-24 09:42:03 +10:00
parent 17567ee5aa
commit b5867bff58
8 changed files with 80 additions and 4 deletions

View File

@ -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/;

View File

@ -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/;

View File

@ -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 );

View File

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

View File

@ -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() )

View File

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

View File

@ -458,6 +458,31 @@ class TestQgsCategorizedSymbolRenderer(unittest.TestCase):
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()

View File

@ -450,11 +450,30 @@ class TestQgsGraduatedSymbolRenderer(unittest.TestCase):
'(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()
# Other calculation method tests
# createRenderer function
# symbolForFeature correctly selects range
# 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 = 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__":