Skip to content
Permalink
Browse files

Merge pull request #6817 from wonder-sk/legend-text-on-symbols

Legend: optional text on top of symbols for vector layers
  • Loading branch information
wonder-sk committed Apr 19, 2018
2 parents 16465cb + f1a31d0 commit 2d7addc9f71db99e12ae2b08fe244924c6b5da81
@@ -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:
@@ -11,6 +11,7 @@




class QgsMapLayerLegend : QObject
{
%Docstring
@@ -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.
@@ -84,6 +99,7 @@ update according to layer node's custom properties (order of items, user labels




class QgsDefaultVectorLayerLegend : QgsMapLayerLegend
{
%Docstring
@@ -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;

};

@@ -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:
@@ -133,6 +133,7 @@ SET(QGIS_APP_SRCS
qgstextannotationdialog.cpp
qgssvgannotationdialog.cpp
qgsundowidget.cpp
qgsvectorlayerlegendwidget.cpp
qgsvectorlayerproperties.cpp
qgsmapthemes.cpp
qgshandlebadlayers.cpp
@@ -361,6 +362,7 @@ SET (QGIS_APP_MOC_HDRS
qgssvgannotationdialog.h
qgstextannotationdialog.h
qgsundowidget.h
qgsvectorlayerlegendwidget.h
qgsvectorlayerproperties.h
qgsmapthemes.h
qgshandlebadlayers.h
@@ -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.
You can’t perform that action at this time.