Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle conditional visibility of data defined size legend nodes #57461

Merged
merged 8 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/core/layertree/qgslayertreemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1296,7 +1296,8 @@ QList<QgsLayerTreeModelLegendNode *> QgsLayerTreeModel::filterLegendNodes( const
{
for ( QgsLayerTreeModelLegendNode *node : std::as_const( nodes ) )
{
switch ( node->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::NodeType ) ).value<QgsLayerTreeModelLegendNode::NodeTypes>() )
const QgsLayerTreeModelLegendNode::NodeTypes nodeType = node->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::NodeType ) ).value<QgsLayerTreeModelLegendNode::NodeTypes>();
switch ( nodeType )
{
case QgsLayerTreeModelLegendNode::EmbeddedWidget:
filtered << node;
Expand All @@ -1319,7 +1320,15 @@ QList<QgsLayerTreeModelLegendNode *> QgsLayerTreeModel::filterLegendNodes( const
if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( node->layerNode()->layer() ) )
{
auto it = mHitTestResults.constFind( vl->id() );
if ( it != mHitTestResults.constEnd() && it->contains( ruleKey ) )
if ( it != mHitTestResults.constEnd() &&
( it->contains( ruleKey ) ||
( !it->isEmpty() && // If there is at least one hit for this layer...
( nodeType == QgsLayerTreeModelLegendNode::DataDefinedSizeLegend || // Show Collapsed data defined size legend...
ruleKey == QLatin1String( "data-defined-size" ) // and data defined size title or dds separated legend items
)
)
)
)
{
filtered << node;
}
Expand Down
6 changes: 4 additions & 2 deletions src/core/qgsdatadefinedsizelegend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ QgsLegendSymbolList QgsDataDefinedSizeLegend::legendSymbolList() const
QgsLegendSymbolList lst;
if ( !mTitleLabel.isEmpty() )
{
QgsLegendSymbolItem title( nullptr, mTitleLabel, QString() );
// we're abusing the ruleKey field here so we can later identify the resulting legend nodes as data defined size related
QgsLegendSymbolItem title( nullptr, mTitleLabel, QStringLiteral( "data-defined-size" ) );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of the abuse, how about we add a QMap< int, QVariant > userData to QgsLegendSymbolItem, and adapt QgsSymbolLegendNode::data to return those values for unknown roles.

Copy link
Contributor Author

@uclaros uclaros May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have a new role in QgsLayerTreeModelLegendNode::CustomRole for that, instead of handling all unknown roles.
But, why a QMap< int, QVariant >? What would you store there (in this case and/or in future uses of it)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have a new role in QgsLayerTreeModelLegendNode::CustomRole for that, instead of handling all unknown roles.

That also works. I was just thinking ahead if allowing custom roles in QgsLegendSymbolItem would also be useful in future.

But, why a QMap< int, QVariant >? What would you store there (in this case and/or in future uses of it)?

The int keys would correspond to a role from QgsLayerTreeModelLegendNode (either public or private). I'd find a QMap approach slightly preferable to having separate members for every property we decide to add in future.

lst << title;
}

Expand All @@ -145,7 +146,8 @@ QgsLegendSymbolList QgsDataDefinedSizeLegend::legendSymbolList() const
lst.reserve( mSizeClasses.size() );
for ( const SizeClass &cl : mSizeClasses )
{
QgsLegendSymbolItem si( mSymbol.get(), cl.label, QString() );
// we're abusing the ruleKey field here so we can later identify the resulting legend nodes as data defined size related
QgsLegendSymbolItem si( mSymbol.get(), cl.label, QStringLiteral( "data-defined-size" ) );
QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( si.symbol() );
double size = cl.size;
if ( mSizeScaleTransformer )
Expand Down
206 changes: 206 additions & 0 deletions tests/src/core/testqgslegendrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ class TestQgsLegendRenderer : public QgsTest
void testDiagramAttributeLegend();
void testDiagramSizeLegend();
void testDataDefinedSizeCollapsed();
void testDataDefinedSizeSeparated();
void testDataDefinedSizeCollapsedFilterByMap();
void testDataDefinedSizeSeparatedFilterByMap();
void testTextOnSymbol();
void testColumnsMixedSymbolSize();

Expand Down Expand Up @@ -1544,6 +1547,209 @@ void TestQgsLegendRenderer::testDataDefinedSizeCollapsed()
delete root;
}

void TestQgsLegendRenderer::testDataDefinedSizeSeparated()
{
const QString testName = QStringLiteral( "legend_data_defined_size_separated" );

QgsVectorLayer *vlDataDefinedSize = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "Point Layer" ), QStringLiteral( "memory" ) );
{
QgsVectorDataProvider *pr = vlDataDefinedSize->dataProvider();
QList<QgsField> attrs;
attrs << QgsField( QStringLiteral( "test_attr" ), QVariant::Int );
pr->addAttributes( attrs );

QgsFields fields;
fields.append( attrs.back() );

const QgsGeometry g = QgsGeometry::fromPointXY( QgsPointXY( 1.0, 1.0 ) );

QList<QgsFeature> features;
QgsFeature f1( fields, 1 );
f1.setAttribute( 0, 100 );
f1.setGeometry( g );
QgsFeature f2( fields, 2 );
f2.setAttribute( 0, 200 );
f2.setGeometry( g );
QgsFeature f3( fields, 3 );
f3.setAttribute( 0, 300 );
f3.setGeometry( g );
features << f1 << f2 << f3;
pr->addFeatures( features );
vlDataDefinedSize->updateFields();
}

QVariantMap props;
props[QStringLiteral( "name" )] = QStringLiteral( "circle" );
props[QStringLiteral( "color" )] = QStringLiteral( "200,200,200" );
props[QStringLiteral( "outline_color" )] = QStringLiteral( "0,0,0" );
QgsMarkerSymbol *symbol = QgsMarkerSymbol::createSimple( props );
QgsProperty ddsProperty = QgsProperty::fromField( QStringLiteral( "test_attr" ) );
ddsProperty.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Linear, 100, 300, 10, 30 ) ); // takes ownership
symbol->setDataDefinedSize( ddsProperty );

QgsDataDefinedSizeLegend *ddsLegend = new QgsDataDefinedSizeLegend();
ddsLegend->setLegendType( QgsDataDefinedSizeLegend::LegendSeparated );
ddsLegend->setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) );

QgsSingleSymbolRenderer *r = new QgsSingleSymbolRenderer( symbol ); // takes ownership
r->setDataDefinedSizeLegend( ddsLegend );
vlDataDefinedSize->setRenderer( r );

QgsLayerTree *root = new QgsLayerTree();
root->addLayer( vlDataDefinedSize );

QgsLayerTreeModel legendModel( root );

QgsLegendSettings settings;
_setStandardTestFont( settings, QStringLiteral( "Bold" ) );
QImage res = _renderLegend( &legendModel, settings );
QVERIFY( _verifyImage( res, testName ) );

delete root;
}

void TestQgsLegendRenderer::testDataDefinedSizeCollapsedFilterByMap()
{
const QString testName = QStringLiteral( "legend_data_defined_size_filter_by_map" );

QgsVectorLayer *vlDataDefinedSize = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "Point Layer" ), QStringLiteral( "memory" ) );
{
QgsVectorDataProvider *pr = vlDataDefinedSize->dataProvider();
QList<QgsField> attrs;
attrs << QgsField( QStringLiteral( "test_attr" ), QVariant::Int );
pr->addAttributes( attrs );

QgsFields fields;
fields.append( attrs.back() );

const QgsGeometry g = QgsGeometry::fromPointXY( QgsPointXY( 1.0, 1.0 ) );

QList<QgsFeature> features;
QgsFeature f1( fields, 1 );
f1.setAttribute( 0, 100 );
f1.setGeometry( g );
QgsFeature f2( fields, 2 );
f2.setAttribute( 0, 200 );
f2.setGeometry( g );
QgsFeature f3( fields, 3 );
f3.setAttribute( 0, 300 );
f3.setGeometry( g );
features << f1 << f2 << f3;
pr->addFeatures( features );
vlDataDefinedSize->updateFields();
}

QVariantMap props;
props[QStringLiteral( "name" )] = QStringLiteral( "circle" );
props[QStringLiteral( "color" )] = QStringLiteral( "200,200,200" );
props[QStringLiteral( "outline_color" )] = QStringLiteral( "0,0,0" );
QgsMarkerSymbol *symbol = QgsMarkerSymbol::createSimple( props );
QgsProperty ddsProperty = QgsProperty::fromField( QStringLiteral( "test_attr" ) );
ddsProperty.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Linear, 100, 300, 10, 30 ) ); // takes ownership
symbol->setDataDefinedSize( ddsProperty );

QgsDataDefinedSizeLegend *ddsLegend = new QgsDataDefinedSizeLegend();
ddsLegend->setLegendType( QgsDataDefinedSizeLegend::LegendCollapsed );
ddsLegend->setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) );

QgsSingleSymbolRenderer *r = new QgsSingleSymbolRenderer( symbol ); // takes ownership
r->setDataDefinedSizeLegend( ddsLegend );
vlDataDefinedSize->setRenderer( r );

QgsLayerTree *root = new QgsLayerTree();
root->addLayer( vlDataDefinedSize );

QgsLayerTreeModel legendModel( root );

QgsMapSettings mapSettings;
// extent and size to include only the red and green points
mapSettings.setExtent( QgsRectangle( 10, 10, 20, 20 ) );
mapSettings.setOutputSize( QSize( 400, 100 ) );
mapSettings.setOutputDpi( 96 );
mapSettings.setLayers( QgsProject::instance()->mapLayers().values() );

QgsLayerTreeFilterSettings filterSettings( mapSettings );
legendModel.setFilterSettings( &filterSettings );

QgsLegendSettings settings;
_setStandardTestFont( settings, QStringLiteral( "Bold" ) );
QImage res = _renderLegend( &legendModel, settings );
QVERIFY( _verifyImage( res, testName ) );

delete root;
}

void TestQgsLegendRenderer::testDataDefinedSizeSeparatedFilterByMap()
{
const QString testName = QStringLiteral( "legend_data_defined_size_filter_by_map" );

QgsVectorLayer *vlDataDefinedSize = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "Point Layer" ), QStringLiteral( "memory" ) );
{
QgsVectorDataProvider *pr = vlDataDefinedSize->dataProvider();
QList<QgsField> attrs;
attrs << QgsField( QStringLiteral( "test_attr" ), QVariant::Int );
pr->addAttributes( attrs );

QgsFields fields;
fields.append( attrs.back() );

const QgsGeometry g = QgsGeometry::fromPointXY( QgsPointXY( 1.0, 1.0 ) );

QList<QgsFeature> features;
QgsFeature f1( fields, 1 );
f1.setAttribute( 0, 100 );
f1.setGeometry( g );
QgsFeature f2( fields, 2 );
f2.setAttribute( 0, 200 );
f2.setGeometry( g );
QgsFeature f3( fields, 3 );
f3.setAttribute( 0, 300 );
f3.setGeometry( g );
features << f1 << f2 << f3;
pr->addFeatures( features );
vlDataDefinedSize->updateFields();
}

QVariantMap props;
props[QStringLiteral( "name" )] = QStringLiteral( "circle" );
props[QStringLiteral( "color" )] = QStringLiteral( "200,200,200" );
props[QStringLiteral( "outline_color" )] = QStringLiteral( "0,0,0" );
QgsMarkerSymbol *symbol = QgsMarkerSymbol::createSimple( props );
QgsProperty ddsProperty = QgsProperty::fromField( QStringLiteral( "test_attr" ) );
ddsProperty.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Linear, 100, 300, 10, 30 ) ); // takes ownership
symbol->setDataDefinedSize( ddsProperty );

QgsDataDefinedSizeLegend *ddsLegend = new QgsDataDefinedSizeLegend();
ddsLegend->setLegendType( QgsDataDefinedSizeLegend::LegendSeparated );
ddsLegend->setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) );

QgsSingleSymbolRenderer *r = new QgsSingleSymbolRenderer( symbol ); // takes ownership
r->setDataDefinedSizeLegend( ddsLegend );
vlDataDefinedSize->setRenderer( r );

QgsLayerTree *root = new QgsLayerTree();
root->addLayer( vlDataDefinedSize );

QgsLayerTreeModel legendModel( root );

QgsMapSettings mapSettings;
// extent and size to include only the red and green points
mapSettings.setExtent( QgsRectangle( 10, 10, 20, 20 ) );
mapSettings.setOutputSize( QSize( 400, 100 ) );
mapSettings.setOutputDpi( 96 );
mapSettings.setLayers( QgsProject::instance()->mapLayers().values() );

QgsLayerTreeFilterSettings filterSettings( mapSettings );
legendModel.setFilterSettings( &filterSettings );

QgsLegendSettings settings;
_setStandardTestFont( settings, QStringLiteral( "Bold" ) );
QImage res = _renderLegend( &legendModel, settings );
QVERIFY( _verifyImage( res, testName ) );

delete root;
}

void TestQgsLegendRenderer::testTextOnSymbol()
{
const QString testName = QStringLiteral( "legend_text_on_symbol" );
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading