Skip to content

Commit

Permalink
Merge pull request #6817 from wonder-sk/legend-text-on-symbols
Browse files Browse the repository at this point in the history
Legend: optional text on top of symbols for vector layers
  • Loading branch information
wonder-sk committed Apr 19, 2018
2 parents 16465cb + f1a31d0 commit 2d7addc
Show file tree
Hide file tree
Showing 17 changed files with 642 additions and 3 deletions.
28 changes: 28 additions & 0 deletions python/core/layertree/qgslayertreemodellegendnode.sip.in
Expand Up @@ -231,6 +231,34 @@ to the associated vector layer's renderer.
.. seealso:: :py:func:`symbol`

.. versionadded:: 2.14
%End

QString textOnSymbolLabel() const;
%Docstring
Returns label of text to be shown on top of the symbol.

.. versionadded:: 3.2
%End

void setTextOnSymbolLabel( const QString &label );
%Docstring
Sets label of text to be shown on top of the symbol.

.. versionadded:: 3.2
%End

QgsTextFormat textOnSymbolTextFormat() const;
%Docstring
Returns text format of the label to be shown on top of the symbol.

.. versionadded:: 3.2
%End

void setTextOnSymbolTextFormat( const QgsTextFormat &format );
%Docstring
Sets format of text to be shown on top of the symbol.

.. versionadded:: 3.2
%End

public slots:
Expand Down
66 changes: 66 additions & 0 deletions python/core/qgsmaplayerlegend.sip.in
Expand Up @@ -11,6 +11,7 @@




class QgsMapLayerLegend : QObject
{
%Docstring
Expand All @@ -31,6 +32,20 @@ Constructor for QgsMapLayerLegend
%End


virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
%Docstring
Reads configuration from a DOM element previously written by writeXml()

.. versionadded:: 3.2
%End

virtual QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const;
%Docstring
Writes configuration to a DOM element, to be used later with readXml()

.. versionadded:: 3.2
%End

virtual QList<QgsLayerTreeModelLegendNode *> createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer ) = 0 /Factory/;
%Docstring
Return list of legend nodes to be used for a particular layer tree layer node.
Expand Down Expand Up @@ -84,6 +99,7 @@ update according to layer node's custom properties (order of items, user labels




class QgsDefaultVectorLayerLegend : QgsMapLayerLegend
{
%Docstring
Expand All @@ -98,8 +114,58 @@ Default legend implementation for vector layers
public:
explicit QgsDefaultVectorLayerLegend( QgsVectorLayer *vl );

bool textOnSymbolEnabled() const;
%Docstring
Returns whether the "text on symbol" functionality is enabled. When enabled, legend symbols
may have extra text rendered on top. The content of labels and their style is controlled
by textOnSymbolContent() and textOnSymbolTextFormat().

.. versionadded:: 3.2
%End

void setTextOnSymbolEnabled( bool enabled );
%Docstring
Sets whether the "text on symbol" functionality is enabled. When enabled, legend symbols
may have extra text rendered on top. The content of labels and their style is controlled
by textOnSymbolContent() and textOnSymbolTextFormat().

.. versionadded:: 3.2
%End

QgsTextFormat textOnSymbolTextFormat() const;
%Docstring
Returns text format of symbol labels for "text on symbol" functionality.

.. versionadded:: 3.2
%End

void setTextOnSymbolTextFormat( const QgsTextFormat &format );
%Docstring
Sets text format of symbol labels for "text on symbol" functionality.

.. versionadded:: 3.2
%End

QHash<QString, QString> textOnSymbolContent() const;
%Docstring
Returns per-symbol content of labels for "text on symbol" functionality. In the passed dictionary
the keys are rule keys of legend items, the values are labels to be shown.

.. versionadded:: 3.2
%End

void setTextOnSymbolContent( const QHash<QString, QString> &content );
%Docstring
Sets per-symbol content of labels for "text on symbol" functionality. In the passed dictionary
the keys are rule keys of legend items, the values are labels to be shown.

.. versionadded:: 3.2
%End

virtual QList<QgsLayerTreeModelLegendNode *> createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer ) /Factory/;

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

};

Expand Down
7 changes: 7 additions & 0 deletions python/gui/qgstextformatwidget.sip.in
Expand Up @@ -48,6 +48,13 @@ Constructor for QgsTextFormatWidget.
QgsTextFormat format() const;
%Docstring
Returns the current formatting settings defined by the widget.
%End

void setFormat( const QgsTextFormat &format );
%Docstring
Sets the current formatting settings

.. versionadded:: 3.2
%End

public slots:
Expand Down
2 changes: 2 additions & 0 deletions src/app/CMakeLists.txt
Expand Up @@ -133,6 +133,7 @@ SET(QGIS_APP_SRCS
qgstextannotationdialog.cpp
qgssvgannotationdialog.cpp
qgsundowidget.cpp
qgsvectorlayerlegendwidget.cpp
qgsvectorlayerproperties.cpp
qgsmapthemes.cpp
qgshandlebadlayers.cpp
Expand Down Expand Up @@ -361,6 +362,7 @@ SET (QGIS_APP_MOC_HDRS
qgssvgannotationdialog.h
qgstextannotationdialog.h
qgsundowidget.h
qgsvectorlayerlegendwidget.h
qgsvectorlayerproperties.h
qgsmapthemes.h
qgshandlebadlayers.h
Expand Down
194 changes: 194 additions & 0 deletions src/app/qgsvectorlayerlegendwidget.cpp
@@ -0,0 +1,194 @@
/***************************************************************************
qgsvectorlayerlegendwidget.cpp
---------------------
Date : April 2018
Copyright : (C) 2018 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsvectorlayerlegendwidget.h"

#include <QBoxLayout>
#include <QStandardItemModel>
#include <QTreeView>

#include "qgsexpressionbuilderdialog.h"
#include "qgsmapcanvas.h"
#include "qgsmaplayerlegend.h"
#include "qgsrenderer.h"
#include "qgssymbollayerutils.h"
#include "qgstextformatwidget.h"
#include "qgsvectorlayer.h"


QgsVectorLayerLegendWidget::QgsVectorLayerLegendWidget( QWidget *parent )
: QWidget( parent )
{
mLegendTreeView = new QTreeView;
mLegendTreeView->setRootIsDecorated( false );

mTextOnSymbolFormatButton = new QPushButton( tr( "Set Text Format…" ) );
connect( mTextOnSymbolFormatButton, &QPushButton::clicked, this, &QgsVectorLayerLegendWidget::openTextFormatWidget );

mTextOnSymbolFromExpressionButton = new QPushButton( tr( "Set Labels from Expression…" ) );
connect( mTextOnSymbolFromExpressionButton, &QPushButton::clicked, this, &QgsVectorLayerLegendWidget::labelsFromExpression );

mTextOnSymbolGroupBox = new QgsCollapsibleGroupBox;

QHBoxLayout *buttonsLayout = new QHBoxLayout;
buttonsLayout->addWidget( mTextOnSymbolFormatButton );
buttonsLayout->addWidget( mTextOnSymbolFromExpressionButton );

QVBoxLayout *groupLayout = new QVBoxLayout;
groupLayout->addWidget( mLegendTreeView );
groupLayout->addLayout( buttonsLayout );

mTextOnSymbolGroupBox->setTitle( tr( "Text on Symbols" ) );
mTextOnSymbolGroupBox->setCheckable( true );
mTextOnSymbolGroupBox->setLayout( groupLayout );
mTextOnSymbolGroupBox->setCollapsed( true );

QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget( mTextOnSymbolGroupBox );
setLayout( layout );
}


void QgsVectorLayerLegendWidget::setLayer( QgsVectorLayer *layer )
{
mLayer = layer;

QgsDefaultVectorLayerLegend *legend = qobject_cast<QgsDefaultVectorLayerLegend *>( layer->legend() );
if ( !legend )
return;

mTextOnSymbolGroupBox->setChecked( legend->textOnSymbolEnabled() );
mTextOnSymbolTextFormat = legend->textOnSymbolTextFormat();
populateLegendTreeView( legend->textOnSymbolContent() );
}


void QgsVectorLayerLegendWidget::populateLegendTreeView( const QHash<QString, QString> &content )
{
QStandardItemModel *model = new QStandardItemModel;
model->setColumnCount( 2 );
model->setHorizontalHeaderLabels( QStringList() << tr( "Symbol" ) << tr( "Text" ) );

const QgsLegendSymbolList lst = mLayer->renderer()->legendSymbolItems();
for ( const QgsLegendSymbolItem &symbolItem : lst )
{
if ( !symbolItem.symbol() )
continue;

QgsRenderContext context;
QSize iconSize( 16, 16 );
QIcon icon = QgsSymbolLayerUtils::symbolPreviewPixmap( symbolItem.symbol(), iconSize, 0, &context );

QStandardItem *item1 = new QStandardItem( icon, symbolItem.label() );
item1->setEditable( false );
QStandardItem *item2 = new QStandardItem;
if ( symbolItem.ruleKey().isEmpty() )
{
item1->setEnabled( false );
item2->setEnabled( true );
}
else
{
item1->setData( symbolItem.ruleKey() );
if ( content.contains( symbolItem.ruleKey() ) )
item2->setText( content.value( symbolItem.ruleKey() ) );
}
model->appendRow( QList<QStandardItem *>() << item1 << item2 );
}
mLegendTreeView->setModel( model );
mLegendTreeView->resizeColumnToContents( 0 );
}


void QgsVectorLayerLegendWidget::applyToLayer()
{
QgsDefaultVectorLayerLegend *legend = new QgsDefaultVectorLayerLegend( mLayer );
legend->setTextOnSymbolEnabled( mTextOnSymbolGroupBox->isChecked() );
legend->setTextOnSymbolTextFormat( mTextOnSymbolTextFormat );

QHash<QString, QString> content;
if ( QStandardItemModel *model = qobject_cast<QStandardItemModel *>( mLegendTreeView->model() ) )
{
for ( int i = 0; i < model->rowCount(); ++i )
{
QString ruleKey = model->item( i, 0 )->data().toString();
QString label = model->item( i, 1 )->text();
if ( !label.isEmpty() )
content[ruleKey] = label;
}
}
legend->setTextOnSymbolContent( content );

mLayer->setLegend( legend );
}


void QgsVectorLayerLegendWidget::openTextFormatWidget()
{
QgsTextFormatWidget *textOnSymbolFormatWidget = new QgsTextFormatWidget( mTextOnSymbolTextFormat );
QDialogButtonBox *dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget( textOnSymbolFormatWidget );
layout->addWidget( dialogButtonBox );
QDialog dlg;
connect( dialogButtonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
connect( dialogButtonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
dlg.setLayout( layout );
if ( !dlg.exec() )
return;

mTextOnSymbolTextFormat = textOnSymbolFormatWidget->format();
}


void QgsVectorLayerLegendWidget::labelsFromExpression()
{
QHash<QString, QString> content;
QgsRenderContext context( QgsRenderContext::fromMapSettings( mCanvas->mapSettings() ) );

QgsExpressionBuilderDialog dlgExpression( mLayer );
dlgExpression.setExpressionContext( context.expressionContext() );
if ( !dlgExpression.exec() )
return;

QgsExpression expr( dlgExpression.expressionText() );
expr.prepare( &context.expressionContext() );

std::unique_ptr< QgsFeatureRenderer > r( mLayer->renderer()->clone() );

QgsFeature f;
QgsFeatureRequest request;
request.setSubsetOfAttributes( r->usedAttributes( context ), mLayer->fields() );
QgsFeatureIterator fi = mLayer->getFeatures();

r->startRender( context, mLayer->fields() );
while ( fi.nextFeature( f ) )
{
context.expressionContext().setFeature( f );
const QSet<QString> keys = r->legendKeysForFeature( f, context );
for ( const QString &key : keys )
{
if ( content.contains( key ) )
continue;

QString label = expr.evaluate( &context.expressionContext() ).toString();
if ( !label.isEmpty() )
content[key] = label;
}
}
r->stopRender( context );

populateLegendTreeView( content );
}

0 comments on commit 2d7addc

Please sign in to comment.