Skip to content

Commit 2d7addc

Browse files
authored
Merge pull request #6817 from wonder-sk/legend-text-on-symbols
Legend: optional text on top of symbols for vector layers
2 parents 16465cb + f1a31d0 commit 2d7addc

17 files changed

+642
-3
lines changed

python/core/layertree/qgslayertreemodellegendnode.sip.in

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,34 @@ to the associated vector layer's renderer.
231231
.. seealso:: :py:func:`symbol`
232232

233233
.. versionadded:: 2.14
234+
%End
235+
236+
QString textOnSymbolLabel() const;
237+
%Docstring
238+
Returns label of text to be shown on top of the symbol.
239+
240+
.. versionadded:: 3.2
241+
%End
242+
243+
void setTextOnSymbolLabel( const QString &label );
244+
%Docstring
245+
Sets label of text to be shown on top of the symbol.
246+
247+
.. versionadded:: 3.2
248+
%End
249+
250+
QgsTextFormat textOnSymbolTextFormat() const;
251+
%Docstring
252+
Returns text format of the label to be shown on top of the symbol.
253+
254+
.. versionadded:: 3.2
255+
%End
256+
257+
void setTextOnSymbolTextFormat( const QgsTextFormat &format );
258+
%Docstring
259+
Sets format of text to be shown on top of the symbol.
260+
261+
.. versionadded:: 3.2
234262
%End
235263

236264
public slots:

python/core/qgsmaplayerlegend.sip.in

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212

1313

14+
1415
class QgsMapLayerLegend : QObject
1516
{
1617
%Docstring
@@ -31,6 +32,20 @@ Constructor for QgsMapLayerLegend
3132
%End
3233

3334

35+
virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
36+
%Docstring
37+
Reads configuration from a DOM element previously written by writeXml()
38+
39+
.. versionadded:: 3.2
40+
%End
41+
42+
virtual QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const;
43+
%Docstring
44+
Writes configuration to a DOM element, to be used later with readXml()
45+
46+
.. versionadded:: 3.2
47+
%End
48+
3449
virtual QList<QgsLayerTreeModelLegendNode *> createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer ) = 0 /Factory/;
3550
%Docstring
3651
Return list of legend nodes to be used for a particular layer tree layer node.
@@ -84,6 +99,7 @@ update according to layer node's custom properties (order of items, user labels
8499

85100

86101

102+
87103
class QgsDefaultVectorLayerLegend : QgsMapLayerLegend
88104
{
89105
%Docstring
@@ -98,8 +114,58 @@ Default legend implementation for vector layers
98114
public:
99115
explicit QgsDefaultVectorLayerLegend( QgsVectorLayer *vl );
100116

117+
bool textOnSymbolEnabled() const;
118+
%Docstring
119+
Returns whether the "text on symbol" functionality is enabled. When enabled, legend symbols
120+
may have extra text rendered on top. The content of labels and their style is controlled
121+
by textOnSymbolContent() and textOnSymbolTextFormat().
122+
123+
.. versionadded:: 3.2
124+
%End
125+
126+
void setTextOnSymbolEnabled( bool enabled );
127+
%Docstring
128+
Sets whether the "text on symbol" functionality is enabled. When enabled, legend symbols
129+
may have extra text rendered on top. The content of labels and their style is controlled
130+
by textOnSymbolContent() and textOnSymbolTextFormat().
131+
132+
.. versionadded:: 3.2
133+
%End
134+
135+
QgsTextFormat textOnSymbolTextFormat() const;
136+
%Docstring
137+
Returns text format of symbol labels for "text on symbol" functionality.
138+
139+
.. versionadded:: 3.2
140+
%End
141+
142+
void setTextOnSymbolTextFormat( const QgsTextFormat &format );
143+
%Docstring
144+
Sets text format of symbol labels for "text on symbol" functionality.
145+
146+
.. versionadded:: 3.2
147+
%End
148+
149+
QHash<QString, QString> textOnSymbolContent() const;
150+
%Docstring
151+
Returns per-symbol content of labels for "text on symbol" functionality. In the passed dictionary
152+
the keys are rule keys of legend items, the values are labels to be shown.
153+
154+
.. versionadded:: 3.2
155+
%End
156+
157+
void setTextOnSymbolContent( const QHash<QString, QString> &content );
158+
%Docstring
159+
Sets per-symbol content of labels for "text on symbol" functionality. In the passed dictionary
160+
the keys are rule keys of legend items, the values are labels to be shown.
161+
162+
.. versionadded:: 3.2
163+
%End
164+
101165
virtual QList<QgsLayerTreeModelLegendNode *> createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer ) /Factory/;
102166

167+
virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
168+
virtual QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const;
103169

104170
};
105171

python/gui/qgstextformatwidget.sip.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ Constructor for QgsTextFormatWidget.
4848
QgsTextFormat format() const;
4949
%Docstring
5050
Returns the current formatting settings defined by the widget.
51+
%End
52+
53+
void setFormat( const QgsTextFormat &format );
54+
%Docstring
55+
Sets the current formatting settings
56+
57+
.. versionadded:: 3.2
5158
%End
5259

5360
public slots:

src/app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ SET(QGIS_APP_SRCS
133133
qgstextannotationdialog.cpp
134134
qgssvgannotationdialog.cpp
135135
qgsundowidget.cpp
136+
qgsvectorlayerlegendwidget.cpp
136137
qgsvectorlayerproperties.cpp
137138
qgsmapthemes.cpp
138139
qgshandlebadlayers.cpp
@@ -361,6 +362,7 @@ SET (QGIS_APP_MOC_HDRS
361362
qgssvgannotationdialog.h
362363
qgstextannotationdialog.h
363364
qgsundowidget.h
365+
qgsvectorlayerlegendwidget.h
364366
qgsvectorlayerproperties.h
365367
qgsmapthemes.h
366368
qgshandlebadlayers.h
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/***************************************************************************
2+
qgsvectorlayerlegendwidget.cpp
3+
---------------------
4+
Date : April 2018
5+
Copyright : (C) 2018 by Martin Dobias
6+
Email : wonder dot sk at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsvectorlayerlegendwidget.h"
17+
18+
#include <QBoxLayout>
19+
#include <QStandardItemModel>
20+
#include <QTreeView>
21+
22+
#include "qgsexpressionbuilderdialog.h"
23+
#include "qgsmapcanvas.h"
24+
#include "qgsmaplayerlegend.h"
25+
#include "qgsrenderer.h"
26+
#include "qgssymbollayerutils.h"
27+
#include "qgstextformatwidget.h"
28+
#include "qgsvectorlayer.h"
29+
30+
31+
QgsVectorLayerLegendWidget::QgsVectorLayerLegendWidget( QWidget *parent )
32+
: QWidget( parent )
33+
{
34+
mLegendTreeView = new QTreeView;
35+
mLegendTreeView->setRootIsDecorated( false );
36+
37+
mTextOnSymbolFormatButton = new QPushButton( tr( "Set Text Format…" ) );
38+
connect( mTextOnSymbolFormatButton, &QPushButton::clicked, this, &QgsVectorLayerLegendWidget::openTextFormatWidget );
39+
40+
mTextOnSymbolFromExpressionButton = new QPushButton( tr( "Set Labels from Expression…" ) );
41+
connect( mTextOnSymbolFromExpressionButton, &QPushButton::clicked, this, &QgsVectorLayerLegendWidget::labelsFromExpression );
42+
43+
mTextOnSymbolGroupBox = new QgsCollapsibleGroupBox;
44+
45+
QHBoxLayout *buttonsLayout = new QHBoxLayout;
46+
buttonsLayout->addWidget( mTextOnSymbolFormatButton );
47+
buttonsLayout->addWidget( mTextOnSymbolFromExpressionButton );
48+
49+
QVBoxLayout *groupLayout = new QVBoxLayout;
50+
groupLayout->addWidget( mLegendTreeView );
51+
groupLayout->addLayout( buttonsLayout );
52+
53+
mTextOnSymbolGroupBox->setTitle( tr( "Text on Symbols" ) );
54+
mTextOnSymbolGroupBox->setCheckable( true );
55+
mTextOnSymbolGroupBox->setLayout( groupLayout );
56+
mTextOnSymbolGroupBox->setCollapsed( true );
57+
58+
QVBoxLayout *layout = new QVBoxLayout;
59+
layout->addWidget( mTextOnSymbolGroupBox );
60+
setLayout( layout );
61+
}
62+
63+
64+
void QgsVectorLayerLegendWidget::setLayer( QgsVectorLayer *layer )
65+
{
66+
mLayer = layer;
67+
68+
QgsDefaultVectorLayerLegend *legend = qobject_cast<QgsDefaultVectorLayerLegend *>( layer->legend() );
69+
if ( !legend )
70+
return;
71+
72+
mTextOnSymbolGroupBox->setChecked( legend->textOnSymbolEnabled() );
73+
mTextOnSymbolTextFormat = legend->textOnSymbolTextFormat();
74+
populateLegendTreeView( legend->textOnSymbolContent() );
75+
}
76+
77+
78+
void QgsVectorLayerLegendWidget::populateLegendTreeView( const QHash<QString, QString> &content )
79+
{
80+
QStandardItemModel *model = new QStandardItemModel;
81+
model->setColumnCount( 2 );
82+
model->setHorizontalHeaderLabels( QStringList() << tr( "Symbol" ) << tr( "Text" ) );
83+
84+
const QgsLegendSymbolList lst = mLayer->renderer()->legendSymbolItems();
85+
for ( const QgsLegendSymbolItem &symbolItem : lst )
86+
{
87+
if ( !symbolItem.symbol() )
88+
continue;
89+
90+
QgsRenderContext context;
91+
QSize iconSize( 16, 16 );
92+
QIcon icon = QgsSymbolLayerUtils::symbolPreviewPixmap( symbolItem.symbol(), iconSize, 0, &context );
93+
94+
QStandardItem *item1 = new QStandardItem( icon, symbolItem.label() );
95+
item1->setEditable( false );
96+
QStandardItem *item2 = new QStandardItem;
97+
if ( symbolItem.ruleKey().isEmpty() )
98+
{
99+
item1->setEnabled( false );
100+
item2->setEnabled( true );
101+
}
102+
else
103+
{
104+
item1->setData( symbolItem.ruleKey() );
105+
if ( content.contains( symbolItem.ruleKey() ) )
106+
item2->setText( content.value( symbolItem.ruleKey() ) );
107+
}
108+
model->appendRow( QList<QStandardItem *>() << item1 << item2 );
109+
}
110+
mLegendTreeView->setModel( model );
111+
mLegendTreeView->resizeColumnToContents( 0 );
112+
}
113+
114+
115+
void QgsVectorLayerLegendWidget::applyToLayer()
116+
{
117+
QgsDefaultVectorLayerLegend *legend = new QgsDefaultVectorLayerLegend( mLayer );
118+
legend->setTextOnSymbolEnabled( mTextOnSymbolGroupBox->isChecked() );
119+
legend->setTextOnSymbolTextFormat( mTextOnSymbolTextFormat );
120+
121+
QHash<QString, QString> content;
122+
if ( QStandardItemModel *model = qobject_cast<QStandardItemModel *>( mLegendTreeView->model() ) )
123+
{
124+
for ( int i = 0; i < model->rowCount(); ++i )
125+
{
126+
QString ruleKey = model->item( i, 0 )->data().toString();
127+
QString label = model->item( i, 1 )->text();
128+
if ( !label.isEmpty() )
129+
content[ruleKey] = label;
130+
}
131+
}
132+
legend->setTextOnSymbolContent( content );
133+
134+
mLayer->setLegend( legend );
135+
}
136+
137+
138+
void QgsVectorLayerLegendWidget::openTextFormatWidget()
139+
{
140+
QgsTextFormatWidget *textOnSymbolFormatWidget = new QgsTextFormatWidget( mTextOnSymbolTextFormat );
141+
QDialogButtonBox *dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
142+
QVBoxLayout *layout = new QVBoxLayout;
143+
layout->addWidget( textOnSymbolFormatWidget );
144+
layout->addWidget( dialogButtonBox );
145+
QDialog dlg;
146+
connect( dialogButtonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
147+
connect( dialogButtonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
148+
dlg.setLayout( layout );
149+
if ( !dlg.exec() )
150+
return;
151+
152+
mTextOnSymbolTextFormat = textOnSymbolFormatWidget->format();
153+
}
154+
155+
156+
void QgsVectorLayerLegendWidget::labelsFromExpression()
157+
{
158+
QHash<QString, QString> content;
159+
QgsRenderContext context( QgsRenderContext::fromMapSettings( mCanvas->mapSettings() ) );
160+
161+
QgsExpressionBuilderDialog dlgExpression( mLayer );
162+
dlgExpression.setExpressionContext( context.expressionContext() );
163+
if ( !dlgExpression.exec() )
164+
return;
165+
166+
QgsExpression expr( dlgExpression.expressionText() );
167+
expr.prepare( &context.expressionContext() );
168+
169+
std::unique_ptr< QgsFeatureRenderer > r( mLayer->renderer()->clone() );
170+
171+
QgsFeature f;
172+
QgsFeatureRequest request;
173+
request.setSubsetOfAttributes( r->usedAttributes( context ), mLayer->fields() );
174+
QgsFeatureIterator fi = mLayer->getFeatures();
175+
176+
r->startRender( context, mLayer->fields() );
177+
while ( fi.nextFeature( f ) )
178+
{
179+
context.expressionContext().setFeature( f );
180+
const QSet<QString> keys = r->legendKeysForFeature( f, context );
181+
for ( const QString &key : keys )
182+
{
183+
if ( content.contains( key ) )
184+
continue;
185+
186+
QString label = expr.evaluate( &context.expressionContext() ).toString();
187+
if ( !label.isEmpty() )
188+
content[key] = label;
189+
}
190+
}
191+
r->stopRender( context );
192+
193+
populateLegendTreeView( content );
194+
}

0 commit comments

Comments
 (0)