Skip to content

Commit

Permalink
[FEATURE] New expression variables for legend items
Browse files Browse the repository at this point in the history
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 b43943a
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 1 deletion.
3 changes: 3 additions & 0 deletions python/core/auto_generated/layout/qgslayoutitemlegend.sip.in
Expand Up @@ -488,6 +488,9 @@ Returns the legend's renderer settings object.
virtual void finalizeRestoreFromXml();


virtual QgsExpressionContext createExpressionContext() const;



public slots:

Expand Down
9 changes: 9 additions & 0 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -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>"
Expand Down
28 changes: 28 additions & 0 deletions src/core/layout/qgslayoutitemlegend.cpp
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions src/core/layout/qgslayoutitemlegend.h
Expand Up @@ -436,6 +436,8 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem

void finalizeRestoreFromXml() override;

QgsExpressionContext createExpressionContext() const override;


public slots:

Expand Down
38 changes: 37 additions & 1 deletion tests/src/python/test_qgslayoutlegend.py
Expand Up @@ -29,7 +29,8 @@
QgsLayoutMeasurement,
QgsLayoutItem,
QgsLayoutPoint,
QgsLayoutSize)
QgsLayoutSize,
QgsExpression)
from qgis.testing import (start_app,
unittest
)
Expand Down Expand Up @@ -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.