Skip to content

Commit

Permalink
Merge pull request #43121 from mhugent/vector_label_legend
Browse files Browse the repository at this point in the history
Vector label legend
  • Loading branch information
mhugent committed May 17, 2021
2 parents eaf7080 + def886d commit 5585622
Show file tree
Hide file tree
Showing 15 changed files with 547 additions and 103 deletions.
14 changes: 14 additions & 0 deletions python/core/auto_generated/labeling/qgspallabeling.sip.in
Expand Up @@ -482,6 +482,20 @@ Sets the polygon placement ``flags``, which dictate how polygon labels can be pl

QgsWkbTypes::GeometryType layerType;

void setLegendString( const QString &legendString );
%Docstring
setLegendString

:param legendString: the string to show in the legend and preview
%End

QString legendString() const;
%Docstring
legendString

:return: the string to show in the legend and in the preview icon
%End

void calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f = 0, QgsRenderContext *context = 0, double *rotatedLabelX /Out/ = 0, double *rotatedLabelY /Out/ = 0 );
%Docstring
Calculates the space required to render the provided ``text`` in map units.
Expand Down
Expand Up @@ -665,6 +665,65 @@ Construct the node using :py:class:`QgsDataDefinedSizeLegend` as definition of t

};

class QgsVectorLabelLegendNode : QgsLayerTreeModelLegendNode
{
%Docstring(signature="appended")
Produces legend node for a labeling text symbol

.. versionadded:: 3.20
%End

%TypeHeaderCode
#include "qgslayertreemodellegendnode.h"
%End
public:

QgsVectorLabelLegendNode( QgsLayerTreeLayer *nodeLayer, const QgsPalLayerSettings &labelSettings, QObject *parent = 0 );
%Docstring
QgsVectorLabelLegendNode

:param nodeLayer: the parent node
:param labelSettings: setting of the label class
:param parent: the parent object
%End
~QgsVectorLabelLegendNode();

virtual QVariant data( int role ) const;

%Docstring
data Returns data associated with the item

:param role: the data role

:return: variant containing the data for the role
%End

virtual QSizeF drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const;

%Docstring
drawSymbol

:param settings: the legend settings
:param ctx: context for the item
:param itemHeight: the height of the item

:return: size of the item
%End

virtual QJsonObject exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext &context ) const;

%Docstring
exportSymbolToJson

:param settings: the legend settings
:param context: the item context

:return: the json object
%End

};


/************************************************************************
* This file has been generated automatically from *
* *
Expand Down
16 changes: 16 additions & 0 deletions python/core/auto_generated/qgsmaplayerlegend.sip.in
Expand Up @@ -259,6 +259,22 @@ may have extra text rendered on top. The content of labels and their style is co
by :py:func:`~QgsDefaultVectorLayerLegend.textOnSymbolContent` and :py:func:`~QgsDefaultVectorLayerLegend.textOnSymbolTextFormat`.

.. versionadded:: 3.2
%End

bool showLabelLegend() const;
%Docstring
Returns whether the legend for the labeling is shown

.. versionadded:: 3.20
%End

void setShowLabelLegend( bool enabled );
%Docstring
Sets if a legend for the labeling should be shown

:param enabled: true to show label legend entries

.. versionadded:: 3.20
%End

QgsTextFormat textOnSymbolTextFormat() const;
Expand Down
6 changes: 5 additions & 1 deletion src/core/labeling/qgspallabeling.cpp
Expand Up @@ -366,6 +366,8 @@ QgsPalLayerSettings &QgsPalLayerSettings::operator=( const QgsPalLayerSettings &
geometryGeneratorType = s.geometryGeneratorType;
layerType = s.layerType;

mLegendString = s.mLegendString;

return *this;
}

Expand Down Expand Up @@ -928,6 +930,7 @@ void QgsPalLayerSettings::readXml( const QDomElement &elem, const QgsReadWriteCo
Q_NOWARN_DEPRECATED_POP
substitutions.readXml( textStyleElem.firstChildElement( QStringLiteral( "substitutions" ) ) );
useSubstitutions = textStyleElem.attribute( QStringLiteral( "useSubstitutions" ) ).toInt();
mLegendString = textStyleElem.attribute( QStringLiteral( "legendString" ), QObject::tr( "Aa" ) );

// text formatting
QDomElement textFormatElem = elem.firstChildElement( QStringLiteral( "text-format" ) );
Expand Down Expand Up @@ -1167,6 +1170,7 @@ QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWrite
substitutions.writeXml( replacementElem, doc );
textStyleElem.appendChild( replacementElem );
textStyleElem.setAttribute( QStringLiteral( "useSubstitutions" ), useSubstitutions );
textStyleElem.setAttribute( QStringLiteral( "legendString" ), mLegendString );

// text formatting
QDomElement textFormatElem = doc.createElement( QStringLiteral( "text-format" ) );
Expand Down Expand Up @@ -1341,7 +1345,7 @@ QPixmap QgsPalLayerSettings::labelSettingsPreviewPixmap( const QgsPalLayerSettin
if ( tempFormat.background().enabled() )
ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );

const QStringList text = QStringList() << ( previewText.isEmpty() ? QObject::tr( "Aa" ) : previewText );
const QStringList text = QStringList() << ( previewText.isEmpty() ? settings.legendString() : previewText );
const double textHeight = QgsTextRenderer::textHeight( context, tempFormat, text, QgsTextRenderer::Rect );
QRectF textRect = rect;
textRect.setLeft( xtrans + padding );
Expand Down
14 changes: 14 additions & 0 deletions src/core/labeling/qgspallabeling.h
Expand Up @@ -798,6 +798,18 @@ class CORE_EXPORT QgsPalLayerSettings
*/
QgsWkbTypes::GeometryType layerType = QgsWkbTypes::UnknownGeometry;

/**
* \brief setLegendString
* \param legendString the string to show in the legend and preview
*/
void setLegendString( const QString &legendString ) { mLegendString = legendString; }

/**
* \brief legendString
* \return the string to show in the legend and in the preview icon
*/
QString legendString() const { return mLegendString; }

/**
* Calculates the space required to render the provided \a text in map units.
* Results will be written to \a labelX and \a labelY.
Expand Down Expand Up @@ -1087,6 +1099,8 @@ class CORE_EXPORT QgsPalLayerSettings

bool mRenderStarted = false;

QString mLegendString = QObject::tr( "Aa" );

static void initPropertyDefinitions();
};

Expand Down
90 changes: 90 additions & 0 deletions src/core/layertree/qgslayertreemodellegendnode.cpp
Expand Up @@ -1434,3 +1434,93 @@ void QgsDataDefinedSizeLegendNode::cacheImage() const
}
}

QgsVectorLabelLegendNode::QgsVectorLabelLegendNode( QgsLayerTreeLayer *nodeLayer, const QgsPalLayerSettings &labelSettings, QObject *parent ): QgsLayerTreeModelLegendNode( nodeLayer, parent ), mLabelSettings( labelSettings )
{
}

QgsVectorLabelLegendNode::~QgsVectorLabelLegendNode()
{
}

QVariant QgsVectorLabelLegendNode::data( int role ) const
{
if ( role == Qt::DisplayRole )
{
return mUserLabel;
}
if ( role == Qt::DecorationRole )
{
const int iconSize = QgsLayerTreeModel::scaleIconSize( 16 );
return QgsPalLayerSettings::labelSettingsPreviewPixmap( mLabelSettings, QSize( iconSize, iconSize ), mLabelSettings.legendString() );
}
return QVariant();
}

QSizeF QgsVectorLabelLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
{
Q_UNUSED( itemHeight );
if ( !ctx )
{
return QSizeF( 0, 0 );
}

const QgsRenderContext *renderContext = ctx->context;
if ( renderContext )
{
return drawSymbol( settings, *renderContext, ctx->columnLeft, ctx->top );
}

return QSizeF( 0, 0 );
}

QSizeF QgsVectorLabelLegendNode::drawSymbol( const QgsLegendSettings &settings, const QgsRenderContext &renderContext, double xOffset, double yOffset ) const
{
QStringList textLines( mLabelSettings.legendString() );
QgsTextFormat textFormat = mLabelSettings.format();
QgsRenderContext ctx( renderContext );
double textWidth, textHeight;
textWidthHeight( textWidth, textHeight, ctx, textFormat, textLines );
textWidth /= renderContext.scaleFactor();
textHeight /= renderContext.scaleFactor();
QPointF textPos( renderContext.scaleFactor() * ( xOffset + settings.symbolSize().width() / 2.0 - textWidth / 2.0 ), renderContext.scaleFactor() * ( yOffset + settings.symbolSize().height() / 2.0 + textHeight / 2.0 ) );

QgsScopedRenderContextScaleToPixels scopedScaleToPixels( ctx );
QgsTextRenderer::drawText( textPos, 0.0, QgsTextRenderer::AlignLeft, textLines, ctx, textFormat );

const double symbolWidth = std::max( textWidth, settings.symbolSize().width() );
const double symbolHeight = std::max( textHeight, settings.symbolSize().height() );
return QSizeF( symbolWidth, symbolHeight );
}

QJsonObject QgsVectorLabelLegendNode::exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext &context ) const
{
Q_UNUSED( settings );

const double mmToPixel = 96.0 / 25.4; //settings.dpi() is deprecated

const QStringList textLines( mLabelSettings.legendString() );
const QgsTextFormat textFormat = mLabelSettings.format();
QgsRenderContext ctx( context );
ctx.setScaleFactor( mmToPixel );

double textWidth, textHeight;
textWidthHeight( textWidth, textHeight, ctx, textFormat, textLines );
QPixmap previewPixmap = mLabelSettings.labelSettingsPreviewPixmap( mLabelSettings, QSize( textWidth, textHeight ), mLabelSettings.legendString() );

QByteArray byteArray;
QBuffer buffer( &byteArray );
previewPixmap.save( &buffer, "PNG" );
const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );

QJsonObject json;
json[ QStringLiteral( "icon" ) ] = base64;
return json;
}

void QgsVectorLabelLegendNode::textWidthHeight( double &width, double &height, QgsRenderContext &ctx, const QgsTextFormat &textFormat, const QStringList &textLines ) const
{
QFontMetricsF fm = QgsTextRenderer::fontMetrics( ctx, textFormat );
height = QgsTextRenderer::textHeight( ctx, textFormat, 'A', true );
width = QgsTextRenderer::textWidth( ctx, textFormat, textLines, &fm );
}

50 changes: 50 additions & 0 deletions src/core/layertree/qgslayertreemodellegendnode.h
Expand Up @@ -27,6 +27,7 @@

#include "qgsexpressioncontext.h"
#include "qgslegendpatchshape.h"
#include "qgspallabeling.h"

class QgsLayerTreeLayer;
class QgsLayerTreeModel;
Expand Down Expand Up @@ -722,4 +723,53 @@ class CORE_EXPORT QgsDataDefinedSizeLegendNode : public QgsLayerTreeModelLegendN
mutable QImage mImage;
};

/**
* \ingroup core
* \brief Produces legend node for a labeling text symbol
* \since QGIS 3.20
*/
class CORE_EXPORT QgsVectorLabelLegendNode : public QgsLayerTreeModelLegendNode
{
public:

/**
* \brief QgsVectorLabelLegendNode
* \param nodeLayer the parent node
* \param labelSettings setting of the label class
* \param parent the parent object
*/
QgsVectorLabelLegendNode( QgsLayerTreeLayer *nodeLayer, const QgsPalLayerSettings &labelSettings, QObject *parent = nullptr );
~QgsVectorLabelLegendNode() override;

/**
* \brief data Returns data associated with the item
* \param role the data role
* \returns variant containing the data for the role
*/
QVariant data( int role ) const override;

/**
* \brief drawSymbol
* \param settings the legend settings
* \param ctx context for the item
* \param itemHeight the height of the item
* \returns size of the item
*/
QSizeF drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const override;

/**
* \brief exportSymbolToJson
* \param settings the legend settings
* \param context the item context
* \returns the json object
*/
QJsonObject exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext &context ) const override;

private:
QgsPalLayerSettings mLabelSettings;
QSizeF drawSymbol( const QgsLegendSettings &settings, const QgsRenderContext &renderContext, double xOffset = 0.0, double yOffset = 0.0 ) const;
void textWidthHeight( double &width, double &height, QgsRenderContext &ctx, const QgsTextFormat &textFormat, const QStringList &textLines ) const;
};


#endif // QGSLAYERTREEMODELLEGENDNODE_H
31 changes: 31 additions & 0 deletions src/core/qgsmaplayerlegend.cpp
Expand Up @@ -29,6 +29,8 @@
#include "qgspointcloudrenderer.h"
#include "qgsrasterrenderer.h"
#include "qgscolorramplegendnode.h"
#include "qgsvectorlayerlabeling.h"
#include "qgsrulebasedlabeling.h"

QgsMapLayerLegend::QgsMapLayerLegend( QObject *parent )
: QObject( parent )
Expand Down Expand Up @@ -400,6 +402,32 @@ QList<QgsLayerTreeModelLegendNode *> QgsDefaultVectorLayerLegend::createLayerTre
}
}

if ( mLayer->labelsEnabled() && mShowLabelLegend )
{
const QgsAbstractVectorLayerLabeling *labeling = mLayer->labeling();
if ( labeling )
{
QStringList pList = labeling->subProviders();
for ( int i = 0; i < pList.size(); ++i )
{
const QgsPalLayerSettings s = labeling->settings( pList.at( i ) );
QString description;
const QgsRuleBasedLabeling *ruleBasedLabeling = dynamic_cast<const QgsRuleBasedLabeling *>( labeling );
if ( ruleBasedLabeling && ruleBasedLabeling->rootRule() )
{
const QgsRuleBasedLabeling::Rule *rule = ruleBasedLabeling->rootRule()->findRuleByKey( pList.at( i ) );
if ( rule )
{
description = rule->description();
}
}
QgsVectorLabelLegendNode *node = new QgsVectorLabelLegendNode( nodeLayer, s );
node->setUserLabel( description );
nodes.append( node );
}
}
}


return nodes;
}
Expand All @@ -410,6 +438,8 @@ void QgsDefaultVectorLayerLegend::readXml( const QDomElement &elem, const QgsRea
mTextOnSymbolTextFormat = QgsTextFormat();
mTextOnSymbolContent.clear();

mShowLabelLegend = elem.attribute( QStringLiteral( "showLabelLegend" ), QStringLiteral( "0" ) ).compare( QStringLiteral( "1" ), Qt::CaseInsensitive ) == 0;

QDomElement tosElem = elem.firstChildElement( QStringLiteral( "text-on-symbol" ) );
if ( !tosElem.isNull() )
{
Expand All @@ -430,6 +460,7 @@ QDomElement QgsDefaultVectorLayerLegend::writeXml( QDomDocument &doc, const QgsR
{
QDomElement elem = doc.createElement( QStringLiteral( "legend" ) );
elem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "default-vector" ) );
elem.setAttribute( QStringLiteral( "showLabelLegend" ), mShowLabelLegend );

if ( mTextOnSymbolEnabled )
{
Expand Down

0 comments on commit 5585622

Please sign in to comment.