[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.
This commit is contained in:
Alex 2019-01-17 21:38:21 -05:00 committed by Nyall Dawson
parent 7c5dcd67ba
commit b43943a9b0
5 changed files with 79 additions and 1 deletions

View File

@ -488,6 +488,9 @@ Returns the legend's renderer settings object.
virtual void finalizeRestoreFromXml();
virtual QgsExpressionContext createExpressionContext() const;
public slots:

View File

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

View File

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

View File

@ -436,6 +436,8 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
void finalizeRestoreFromXml() override;
QgsExpressionContext createExpressionContext() const override;
public slots:

View File

@ -29,7 +29,8 @@ from qgis.core import (QgsLayoutItemLegend,
QgsLayoutMeasurement,
QgsLayoutItem,
QgsLayoutPoint,
QgsLayoutSize)
QgsLayoutSize,
QgsExpression)
from qgis.testing import (start_app,
unittest
)
@ -269,6 +270,41 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
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()