Skip to content
Permalink
Browse files

[FEATURE] New expression variables for legend items

Adds new variables for use in data defined expressions for layout legend items, including

- @legend_title
- @legend_column_count
- @legend_split_layers
- @legend_wrap_string
- @legend_filter_by_map
- @legend_filter_out_atlas

Additionally, if the legend is linked to a map, then expressions used in that legend will also have access to the linked variables, including @map_scale, @map_extent, etc.
  • Loading branch information
roya0045 authored and nyalldawson committed Jan 18, 2019
1 parent 7c5dcd6 commit b43943a9b0171f2313e45adf30e8b55ff9474700
@@ -488,6 +488,9 @@ Returns the legend's renderer settings object.
virtual void finalizeRestoreFromXml();


virtual QgsExpressionContext createExpressionContext() const;



public slots:

@@ -757,6 +757,15 @@ void QgsExpression::initVariableHelp()
// map canvas item variables
sVariableHelpTexts.insert( QStringLiteral( "canvas_cursor_point" ), QCoreApplication::translate( "variable_help", "Last cursor position on the canvas in the project's geographical coordinates." ) );

// legend canvas item variables
sVariableHelpTexts.insert( QStringLiteral( "legend_title" ), QCoreApplication::translate( "variable_help", "Title of the legend." ) );
sVariableHelpTexts.insert( QStringLiteral( "legend_column_count" ), QCoreApplication::translate( "variable_help", "Number of column in the legend." ) );
sVariableHelpTexts.insert( QStringLiteral( "legend_split_layers" ), QCoreApplication::translate( "variable_help", "Boolean indicating if layers can be split in the legend." ) );
sVariableHelpTexts.insert( QStringLiteral( "legend_wrap_string" ), QCoreApplication::translate( "variable_help", "Characters used to wrap the legend text." ) );
sVariableHelpTexts.insert( QStringLiteral( "legend_filter_by_map" ), QCoreApplication::translate( "variable_help", "Boolean indicating if the content of the legend is filtered by the map." ) );
sVariableHelpTexts.insert( QStringLiteral( "legend_filter_out_atlas" ), QCoreApplication::translate( "variable_help", "Boolean indicating if the Atlas is filtered out of the legend." ) );


// map tool capture variables
sVariableHelpTexts.insert( QStringLiteral( "snapping_results" ), QCoreApplication::translate( "variable_help",
"<p>An array with an item for each snapped point.</p>"
@@ -818,6 +818,34 @@ void QgsLayoutItemLegend::onAtlasEnded()
updateFilterByMap();
}

QgsExpressionContext QgsLayoutItemLegend::createExpressionContext() const
{
QgsExpressionContext context = QgsLayoutItem::createExpressionContext();

// We only want the last scope from the map's expression context, as this contains
// the map specific variables. We don't want the rest of the map's context, because that
// will contain duplicate global, project, layout, etc scopes.

if ( mMap )
{
context.appendScope( mMap->createExpressionContext().popScope() );
}


QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Legend Settings" ) );

scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_title" ), title(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_column_count" ), columnCount(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_split_layers" ), splitLayer(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_wrap_string" ), wrapString(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_by_map" ), legendFilterByMapEnabled(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_out_atlas" ), legendFilterOutAtlas(), true ) );

context.appendScope( scope );

return context;
}

// -------------------------------------------------------------------------
#include "qgslayertreemodellegendnode.h"
#include "qgsvectorlayer.h"
@@ -436,6 +436,8 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem

void finalizeRestoreFromXml() override;

QgsExpressionContext createExpressionContext() const override;


public slots:

@@ -29,7 +29,8 @@
QgsLayoutMeasurement,
QgsLayoutItem,
QgsLayoutPoint,
QgsLayoutSize)
QgsLayoutSize,
QgsExpression)
from qgis.testing import (start_app,
unittest
)
@@ -269,6 +270,41 @@ def testDataDefinedColumnCount(self):
self.assertEqual(legend.columnCount(), 2)
self.assertEqual(legend.legendSettings().columnCount(), 5)

def testLegendScopeVariables(self):
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()

legend = QgsLayoutItemLegend(layout)
legend.setTitle("Legend")
layout.addLayoutItem(legend)

legend.setColumnCount(2)
legend.setWrapString('d')
legend.setLegendFilterOutAtlas(True)

expc = legend.createExpressionContext()
exp1 = QgsExpression("@legend_title")
self.assertEqual(exp1.evaluate(expc), "Legend")
exp2 = QgsExpression("@legend_column_count")
self.assertEqual(exp2.evaluate(expc), 2)
exp3 = QgsExpression("@legend_wrap_string")
self.assertEqual(exp3.evaluate(expc), 'd')
exp4 = QgsExpression("@legend_split_layers")
self.assertEqual(exp4.evaluate(expc), False)
exp5 = QgsExpression("@legend_filter_out_atlas")
self.assertEqual(exp5.evaluate(expc), True)

map = QgsLayoutItemMap(layout)
map.attemptSetSceneRect(QRectF(20, 20, 80, 80))
map.setFrameEnabled(True)
map.setExtent(QgsRectangle(781662.375, 3339523.125, 793062.375, 3345223.125))
layout.addLayoutItem(map)
map.setScale(15000)
legend.setLinkedMap(map)
expc2 = legend.createExpressionContext()
exp6 = QgsExpression("@map_scale")
self.assertAlmostEqual(exp6.evaluate(expc2), 15000, 2)


if __name__ == '__main__':
unittest.main()

0 comments on commit b43943a

Please sign in to comment.
You can’t perform that action at this time.