mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
Symbol aware legend expression (#9648)
This commit is contained in:
parent
eb73702982
commit
248af94ba9
@ -177,6 +177,7 @@
|
||||
<file>themes/default/mActionDistributeTop.svg</file>
|
||||
<file>themes/default/mActionDistributeVCenter.svg</file>
|
||||
<file>themes/default/mActionDistributeVSpace.svg</file>
|
||||
<file>themes/default/mActionAddExpression.svg</file>
|
||||
<file>themes/default/mActionAddLayer.svg</file>
|
||||
<file>themes/default/mActionAddMeshLayer.svg</file>
|
||||
<file>themes/default/mActionAddAllToOverview.svg</file>
|
||||
|
1
images/themes/default/mActionAddExpression.svg
Normal file
1
images/themes/default/mActionAddExpression.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14.7967" height="16.7515" viewBox="0 0 3.915 4.4322"><path d="M1.1438963 1.910687c-.5300386-.1812338-.7956598-.455994-.7956598-.8236459 0-.2899952.1400886-.521801.4198282-.695523C1.0504308.218325 1.4006524.1314639 1.817635.1314639c.3786771 0 .6791016.0610461.9025868.1822918.2238134.1193414.335556.263334.335556.4315545 0 .0880248-.034584.1652581-.1050665.2330753-.069606.0646433-.1507047.0971236-.2419812.0971236-.151033 0-.274705-.1011439-.3716727-.30322C2.203426.492662 2.002158.3527955 1.7336913.3527955c-.2125407 0-.3879799.0670766-.5252229.2017589-.1374621.134788-.2063025.3226872-.2063025.5635917 0 .4736625.2550052.710864.7638115.710864.053518 0 .1150259-.00529.1846324-.015658.1209359-.015235.2148391-.02317.2820378-.02317.1634003 0 .2457024.045176.2457024.135846 0 .1009323-.083506.1512927-.2501896.1512927-.05899 0-.1472025-.0091-.2652928-.02719-.088869-.015658-.1572714-.023382-.2054269-.023382-.538794 0-.807808.2630165-.807808.7903191 0 .2561396.071467.4634.2142919.6214638.1424964.155313.342451.2331811.5975655.2331811.3195773 0 .5308047-.1597566.6358711-.4782118.054065-.1681147.1130559-.2849169.177628-.3497717.067746-.064326.1565053-.097018.2671535-.097018.091495 0 .1729219.032692.2462495.097018.075516.06221.1129465.1426171.1129465.2409045 0 .2358261-.137462.431237-.4119482.587079-.2750334.1523507-.6067589.2287376-.9960521.2287376-.4274892 0-.808793-.098181-1.1420508-.2949678-.3310688-.197104-.4968768-.4602263-.4968768-.7887324 0-.4108178.3299744-.7124509.9894855-.9060632" fill="#5c3566"/><g transform="matrix(.13805 0 0 .13357 -.709128 -.0487503)"><rect y="19" x="19" width="13" ry="2.6149001" rx="2.6149001" height="13" fill="#5a8c5a"/><path d="M21.6 25.5h7.8m-3.9 3.9v-7.8" overflow="visible" fill="#fff" fill-rule="evenodd" stroke="#fff" stroke-width="2.5999999" stroke-linecap="round" stroke-linejoin="round"/><path d="M20.3 25.5h10.4v-2.6c0-2.6-.65-2.6-5.2-2.6s-5.2 0-5.2 2.6z" opacity=".3" fill="#fcffff" fill-rule="evenodd"/></g></svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -122,6 +122,20 @@ Also resolves textual references to layers from the project (calls resolveRefere
|
||||
Resolves reference to layer from stored layer ID (if it has not been resolved already)
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
void setLabelExpression( const QString &expression );
|
||||
%Docstring
|
||||
set the expression to evaluate
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
QString labelExpression() const;
|
||||
%Docstring
|
||||
Returns the expression member of the LayerTreeNode
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
signals:
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsLayerTreeModelLegendNode : QObject
|
||||
{
|
||||
%Docstring
|
||||
@ -26,6 +27,12 @@ and customized look.
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgslayertreemodellegendnode.h"
|
||||
%End
|
||||
%ConvertToSubClassCode
|
||||
if ( qobject_cast<QgsSymbolLegendNode *> ( sipCpp ) )
|
||||
sipType = sipType_QgsSymbolLegendNode;
|
||||
else
|
||||
sipType = 0;
|
||||
%End
|
||||
public:
|
||||
|
||||
@ -309,6 +316,23 @@ Returns text format of the label to be shown on top of the symbol.
|
||||
Sets format of text to be shown on top of the symbol.
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
QString symbolLabel() const;
|
||||
%Docstring
|
||||
Label of the symbol, user defined label will be used, otherwise will default to the label made by QGIS.
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
QString evaluateLabel( const QgsExpressionContext &context = QgsExpressionContext(), const QString &label = QString() );
|
||||
%Docstring
|
||||
Evaluates and returns the text label of the current node
|
||||
|
||||
:param context: extra QgsExpressionContext to use for evaluating the expression
|
||||
:param label: text to evaluate instead of the layer layertree string
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
public slots:
|
||||
|
@ -24,9 +24,14 @@ Overrides some functionality of QgsLayerTreeModel to better fit the needs of lay
|
||||
#include "qgslayoutitemlegend.h"
|
||||
%End
|
||||
public:
|
||||
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent /TransferThis/ = 0 );
|
||||
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent /TransferThis/ = 0, QgsLayoutItemLegend *layout = 0 );
|
||||
%Docstring
|
||||
Construct the model based on the given layer tree
|
||||
%End
|
||||
|
||||
QgsLegendModel( QgsLayerTree *rootNode, QgsLayoutItemLegend *layout );
|
||||
%Docstring
|
||||
Alternative constructor.
|
||||
%End
|
||||
|
||||
virtual QVariant data( const QModelIndex &index, int role ) const;
|
||||
@ -34,6 +39,16 @@ Construct the model based on the given layer tree
|
||||
|
||||
virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
void refreshLegend();
|
||||
%Docstring
|
||||
Emitted to refresh the legend.
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -513,7 +528,6 @@ Returns the legend's renderer settings object.
|
||||
virtual QgsExpressionContext createExpressionContext() const;
|
||||
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
virtual void refresh();
|
||||
|
@ -2279,7 +2279,6 @@ Configuration and logic to apply automatically on any edit happening on this lay
|
||||
|
||||
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
void select( QgsFeatureId featureId );
|
||||
@ -2653,6 +2652,7 @@ Emitted when the feature count for symbols on this layer has been recalculated.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
|
||||
protected:
|
||||
virtual void setExtent( const QgsRectangle &rect ) ${SIP_FINAL};
|
||||
|
||||
|
@ -29,16 +29,29 @@ QgsVectorLayer.countSymbolFeatures() and connect to the signal
|
||||
Create a new feature counter for ``layer``.
|
||||
%End
|
||||
|
||||
|
||||
virtual bool run();
|
||||
|
||||
%Docstring
|
||||
Calculates the feature count and Ids per symbol
|
||||
%End
|
||||
|
||||
|
||||
long featureCount( const QString &legendKey ) const;
|
||||
%Docstring
|
||||
Gets the feature count for a particular ``legendKey``.
|
||||
Returns the feature count for a particular ``legendKey``.
|
||||
If the key has not been found, -1 will be returned.
|
||||
%End
|
||||
|
||||
|
||||
QgsFeatureIds featureIds( const QString &symbolkey ) const;
|
||||
%Docstring
|
||||
Returns the feature Ids for a particular ``legendKey``.
|
||||
If the key has not been found an empty QSet will be returned.
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
signals:
|
||||
|
||||
void symbolsCounted();
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "qgslayoutitemlegend.h"
|
||||
#include "qgslayoutmeasurementconverter.h"
|
||||
#include "qgsunittypes.h"
|
||||
#include "qgsexpressionbuilderdialog.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QInputDialog>
|
||||
@ -104,6 +105,7 @@ QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend )
|
||||
connect( mEditPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mEditPushButton_clicked );
|
||||
connect( mCountToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mCountToolButton_clicked );
|
||||
connect( mExpressionFilterButton, &QgsLegendFilterButton::toggled, this, &QgsLayoutLegendWidget::mExpressionFilterButton_toggled );
|
||||
connect( mLayerExpressionButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mLayerExpressionButton_clicked );
|
||||
connect( mFilterByMapToolButton, &QToolButton::toggled, this, &QgsLayoutLegendWidget::mFilterByMapToolButton_toggled );
|
||||
connect( mUpdateAllPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mUpdateAllPushButton_clicked );
|
||||
connect( mAddGroupToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mAddGroupToolButton_clicked );
|
||||
@ -136,6 +138,7 @@ QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend )
|
||||
mMoveUpToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowUp.svg" ) ) );
|
||||
mMoveDownToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowDown.svg" ) ) );
|
||||
mCountToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionSum.svg" ) ) );
|
||||
mLayerExpressionButton->setIcon( QIcon( QgsApplication::iconPath( "mActionAddExpression.svg" ) ) );
|
||||
|
||||
mFontColorButton->setColorDialogTitle( tr( "Select Font Color" ) );
|
||||
mFontColorButton->setContext( QStringLiteral( "composer" ) );
|
||||
@ -994,6 +997,45 @@ void QgsLayoutLegendWidget::mExpressionFilterButton_toggled( bool checked )
|
||||
mLegend->endCommand();
|
||||
}
|
||||
|
||||
void QgsLayoutLegendWidget::mLayerExpressionButton_clicked()
|
||||
{
|
||||
|
||||
if ( !mLegend )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QModelIndex currentIndex = mItemTreeView->currentIndex();
|
||||
if ( !currentIndex.isValid() )
|
||||
return;
|
||||
|
||||
QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
|
||||
if ( !QgsLayerTree::isLayer( currentNode ) )
|
||||
return;
|
||||
|
||||
QgsLayerTreeLayer *layerNode = qobject_cast<QgsLayerTreeLayer *>( currentNode );
|
||||
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerNode->layer() );
|
||||
|
||||
if ( !vl )
|
||||
return;
|
||||
|
||||
QString currentExpression;
|
||||
if ( layerNode->labelExpression().isEmpty() )
|
||||
currentExpression = QStringLiteral( "@symbol_label" );
|
||||
else
|
||||
currentExpression = layerNode->labelExpression();
|
||||
QgsExpressionContext legendContext = mLegend->createExpressionContext();
|
||||
legendContext.appendScope( vl->createExpressionContextScope() );
|
||||
QgsExpressionBuilderDialog expressiondialog( vl, currentExpression, nullptr, "generic", legendContext );
|
||||
if ( expressiondialog.exec() )
|
||||
layerNode->setLabelExpression( expressiondialog.expressionText() );
|
||||
|
||||
mLegend->beginCommand( tr( "Update Legend" ) );
|
||||
mLegend->updateLegend();
|
||||
mLegend->adjustBoxSize();
|
||||
mLegend->endCommand();
|
||||
}
|
||||
|
||||
void QgsLayoutLegendWidget::mUpdateAllPushButton_clicked()
|
||||
{
|
||||
updateLegend();
|
||||
@ -1105,12 +1147,27 @@ void QgsLayoutLegendWidget::selectedChanged( const QModelIndex ¤t, const Q
|
||||
Q_UNUSED( current )
|
||||
Q_UNUSED( previous )
|
||||
|
||||
mLayerExpressionButton->setEnabled( false );
|
||||
|
||||
if ( mLegend && mLegend->autoUpdateModel() )
|
||||
{
|
||||
QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
|
||||
if ( !QgsLayerTree::isLayer( currentNode ) )
|
||||
return;
|
||||
|
||||
QgsLayerTreeLayer *currentLayerNode = QgsLayerTree::toLayer( currentNode );
|
||||
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( currentLayerNode->layer() );
|
||||
if ( !vl )
|
||||
return;
|
||||
|
||||
mLayerExpressionButton->setEnabled( true );
|
||||
return;
|
||||
}
|
||||
|
||||
mCountToolButton->setChecked( false );
|
||||
mCountToolButton->setEnabled( false );
|
||||
|
||||
|
||||
mExpressionFilterButton->blockSignals( true );
|
||||
mExpressionFilterButton->setChecked( false );
|
||||
mExpressionFilterButton->setEnabled( false );
|
||||
@ -1127,6 +1184,7 @@ void QgsLayoutLegendWidget::selectedChanged( const QModelIndex ¤t, const Q
|
||||
|
||||
mCountToolButton->setChecked( currentNode->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() );
|
||||
mCountToolButton->setEnabled( true );
|
||||
mLayerExpressionButton->setEnabled( true );
|
||||
|
||||
bool exprEnabled;
|
||||
QString expr = QgsLayerTreeUtils::legendFilterByExpression( *qobject_cast<QgsLayerTreeLayer *>( currentNode ), &exprEnabled );
|
||||
|
@ -85,6 +85,7 @@ class QgsLayoutLegendWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayo
|
||||
void resetLayerNodeToDefaults();
|
||||
void mUpdateAllPushButton_clicked();
|
||||
void mAddGroupToolButton_clicked();
|
||||
void mLayerExpressionButton_clicked();
|
||||
|
||||
void mFilterLegendByAtlasCheckBox_toggled( bool checked );
|
||||
|
||||
|
@ -830,6 +830,10 @@ void QgsExpression::initVariableHelp()
|
||||
sVariableHelpTexts.insert( QStringLiteral( "symbol_color" ), QCoreApplication::translate( "symbol_color", "Color of symbol used to render the feature." ) );
|
||||
sVariableHelpTexts.insert( QStringLiteral( "symbol_angle" ), QCoreApplication::translate( "symbol_angle", "Angle of symbol used to render the feature (valid for marker symbols only)." ) );
|
||||
|
||||
sVariableHelpTexts.insert( QStringLiteral( "symbol_label" ), QCoreApplication::translate( "symbol_label", "Label of the symbol, user defined label will be used, otherwise will default to the label made by QGIS." ) );
|
||||
sVariableHelpTexts.insert( QStringLiteral( "symbol_id" ), QCoreApplication::translate( "symbol_id", "Id of the symbol." ) );
|
||||
sVariableHelpTexts.insert( QStringLiteral( "symbol_count" ), QCoreApplication::translate( "symbol_count", "Total number of features defined by this symbol." ) );
|
||||
|
||||
//cluster variables
|
||||
sVariableHelpTexts.insert( QStringLiteral( "cluster_color" ), QCoreApplication::translate( "cluster_color", "Color of symbols within a cluster, or NULL if symbols have mixed colors." ) );
|
||||
sVariableHelpTexts.insert( QStringLiteral( "cluster_size" ), QCoreApplication::translate( "cluster_size", "Number of symbols contained within a cluster." ) );
|
||||
|
@ -188,3 +188,9 @@ void QgsLayerTreeLayer::layerNameChanged()
|
||||
Q_ASSERT( mRef );
|
||||
emit nameChanged( this, mRef->name() );
|
||||
}
|
||||
|
||||
void QgsLayerTreeLayer::setLabelExpression( const QString &expression )
|
||||
{
|
||||
mLabelExpression = expression;
|
||||
}
|
||||
|
||||
|
@ -128,6 +128,20 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
|
||||
*/
|
||||
void resolveReferences( const QgsProject *project, bool looseMatching = false ) override;
|
||||
|
||||
/**
|
||||
* set the expression to evaluate
|
||||
*
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
void setLabelExpression( const QString &expression );
|
||||
|
||||
/**
|
||||
* Returns the expression member of the LayerTreeNode
|
||||
*
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QString labelExpression() const { return mLabelExpression; }
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
@ -148,6 +162,8 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
|
||||
QgsMapLayerRef mRef;
|
||||
//! Layer name - only used if layer does not exist or if mUseLayerName is false
|
||||
QString mLayerName;
|
||||
//! Expression to evaluate in the legend
|
||||
QString mLabelExpression;
|
||||
|
||||
//!
|
||||
bool mUseLayerName = true;
|
||||
|
@ -29,6 +29,11 @@
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgsrasterrenderer.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
#include "qgsfeatureid.h"
|
||||
#include "qgslayoutitem.h"
|
||||
#include "qgsvectorlayerfeaturecounter.h"
|
||||
#include "qgsexpression.h"
|
||||
|
||||
|
||||
QgsLayerTreeModelLegendNode::QgsLayerTreeModelLegendNode( QgsLayerTreeLayer *nodeL, QObject *parent )
|
||||
: QObject( parent )
|
||||
@ -284,6 +289,20 @@ const QgsSymbol *QgsSymbolLegendNode::symbol() const
|
||||
return mItem.symbol();
|
||||
}
|
||||
|
||||
QString QgsSymbolLegendNode::symbolLabel() const
|
||||
{
|
||||
QString label;
|
||||
if ( mEmbeddedInParent )
|
||||
{
|
||||
QVariant legendlabel = mLayerNode->customProperty( QStringLiteral( "legend/title-label" ) );
|
||||
QString layerName = legendlabel.isNull() ? mLayerNode->name() : legendlabel.toString();
|
||||
label = mUserLabel.isEmpty() ? layerName : mUserLabel;
|
||||
}
|
||||
else
|
||||
label = mUserLabel.isEmpty() ? mItem.label() : mUserLabel;
|
||||
return label;
|
||||
}
|
||||
|
||||
void QgsSymbolLegendNode::setSymbol( QgsSymbol *symbol )
|
||||
{
|
||||
if ( !symbol )
|
||||
@ -648,31 +667,68 @@ void QgsSymbolLegendNode::updateLabel()
|
||||
|
||||
bool showFeatureCount = mLayerNode->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toBool();
|
||||
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
|
||||
mLabel = symbolLabel();
|
||||
|
||||
if ( mEmbeddedInParent )
|
||||
if ( showFeatureCount && vl )
|
||||
{
|
||||
QString layerName = mLayerNode->name();
|
||||
if ( !mLayerNode->customProperty( QStringLiteral( "legend/title-label" ) ).isNull() )
|
||||
layerName = mLayerNode->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
|
||||
|
||||
mLabel = mUserLabel.isEmpty() ? layerName : mUserLabel;
|
||||
if ( showFeatureCount && vl && vl->featureCount() >= 0 )
|
||||
mLabel += QStringLiteral( " [%1]" ).arg( vl->featureCount() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLabel = mUserLabel.isEmpty() ? mItem.label() : mUserLabel;
|
||||
if ( showFeatureCount && vl )
|
||||
{
|
||||
qlonglong count = vl->featureCount( mItem.ruleKey() );
|
||||
mLabel += QStringLiteral( " [%1]" ).arg( count != -1 ? QLocale().toString( count ) : tr( "N/A" ) );
|
||||
}
|
||||
qlonglong count = mEmbeddedInParent ? vl->featureCount() : vl->featureCount( mItem.ruleKey() ) ;
|
||||
mLabel += QStringLiteral( " [%1]" ).arg( count != -1 ? QLocale().toString( count ) : tr( "N/A" ) );
|
||||
}
|
||||
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
QString QgsSymbolLegendNode::evaluateLabel( const QgsExpressionContext &context, const QString &label )
|
||||
{
|
||||
if ( !mLayerNode )
|
||||
return QString();
|
||||
|
||||
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
|
||||
|
||||
if ( vl )
|
||||
{
|
||||
QgsExpressionContext contextCopy = QgsExpressionContext( context );
|
||||
QgsExpressionContextScope *symbolScope = createSymbolScope();
|
||||
contextCopy.appendScope( symbolScope );
|
||||
contextCopy.appendScope( vl->createExpressionContextScope() );
|
||||
|
||||
if ( label.isEmpty() )
|
||||
{
|
||||
if ( ! mLayerNode->labelExpression().isEmpty() )
|
||||
mLabel = QgsExpression::replaceExpressionText( "[%" + mLayerNode->labelExpression() + "%]", &contextCopy );
|
||||
else if ( mLabel.contains( "[%" ) )
|
||||
{
|
||||
const QString symLabel = symbolLabel();
|
||||
mLabel = QgsExpression::replaceExpressionText( symLabel, &contextCopy );
|
||||
}
|
||||
return mLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
QString eLabel;
|
||||
if ( ! mLayerNode->labelExpression().isEmpty() )
|
||||
eLabel = QgsExpression::replaceExpressionText( label + "[%" + mLayerNode->labelExpression() + "%]", &contextCopy );
|
||||
else if ( label.contains( "[%" ) )
|
||||
eLabel = QgsExpression::replaceExpressionText( label, &contextCopy );
|
||||
return eLabel;
|
||||
}
|
||||
}
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
QgsExpressionContextScope *QgsSymbolLegendNode::createSymbolScope() const
|
||||
{
|
||||
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
|
||||
|
||||
QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Symbol scope" ) );
|
||||
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_label" ), symbolLabel().remove( "[%" ).remove( "%]" ), true ) );
|
||||
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_id" ), mItem.ruleKey(), true ) );
|
||||
if ( vl )
|
||||
{
|
||||
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_count" ), QVariant::fromValue( vl->featureCount( mItem.ruleKey() ) ), true ) );
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "qgis_sip.h"
|
||||
|
||||
#include "qgsrasterdataprovider.h" // for QgsImageFetcher dtor visibility
|
||||
#include "qgsexpressioncontext.h"
|
||||
|
||||
class QgsLayerTreeLayer;
|
||||
class QgsLayerTreeModel;
|
||||
@ -48,6 +49,14 @@ class QgsRenderContext;
|
||||
class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
#ifdef SIP_RUN
|
||||
SIP_CONVERT_TO_SUBCLASS_CODE
|
||||
if ( qobject_cast<QgsSymbolLegendNode *> ( sipCpp ) )
|
||||
sipType = sipType_QgsSymbolLegendNode;
|
||||
else
|
||||
sipType = 0;
|
||||
SIP_END
|
||||
#endif
|
||||
public:
|
||||
|
||||
enum LegendNodeRoles
|
||||
@ -228,6 +237,7 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
@ -319,6 +329,20 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
|
||||
*/
|
||||
void setTextOnSymbolTextFormat( const QgsTextFormat &format ) { mTextOnSymbolTextFormat = format; }
|
||||
|
||||
/**
|
||||
* Label of the symbol, user defined label will be used, otherwise will default to the label made by QGIS.
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QString symbolLabel() const;
|
||||
|
||||
/**
|
||||
* Evaluates and returns the text label of the current node
|
||||
* \param context extra QgsExpressionContext to use for evaluating the expression
|
||||
* \param label text to evaluate instead of the layer layertree string
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QString evaluateLabel( const QgsExpressionContext &context = QgsExpressionContext(), const QString &label = QString() );
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
@ -361,6 +385,12 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
|
||||
// ident the symbol icon to make it look like a tree structure
|
||||
static const int INDENT_SIZE = 20;
|
||||
|
||||
/**
|
||||
* Create an expressionContextScope containing symbol related variables
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QgsExpressionContextScope *createSymbolScope() const SIP_FACTORY;
|
||||
|
||||
/**
|
||||
* Sets all items belonging to the same layer as this node to the same check state.
|
||||
* \param state check state
|
||||
|
@ -34,10 +34,11 @@
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
#include <QPainter>
|
||||
#include "qgsexpressioncontext.h"
|
||||
|
||||
QgsLayoutItemLegend::QgsLayoutItemLegend( QgsLayout *layout )
|
||||
: QgsLayoutItem( layout )
|
||||
, mLegendModel( new QgsLegendModel( layout->project()->layerTreeRoot() ) )
|
||||
, mLegendModel( new QgsLegendModel( layout->project()->layerTreeRoot(), this ) )
|
||||
{
|
||||
#if 0 //no longer required?
|
||||
connect( &layout->atlasComposition(), &QgsAtlasComposition::renderEnded, this, &QgsLayoutItemLegend::onAtlasEnded );
|
||||
@ -55,6 +56,7 @@ QgsLayoutItemLegend::QgsLayoutItemLegend( QgsLayout *layout )
|
||||
invalidateCache();
|
||||
update();
|
||||
} );
|
||||
connect( mLegendModel.get(), &QgsLegendModel::refreshLegend, this, &QgsLayoutItemLegend::refresh );
|
||||
}
|
||||
|
||||
QgsLayoutItemLegend *QgsLayoutItemLegend::create( QgsLayout *layout )
|
||||
@ -225,6 +227,7 @@ void QgsLayoutItemLegend::setCustomLayerTree( QgsLayerTree *rootGroup )
|
||||
mCustomLayerTree.reset( rootGroup );
|
||||
}
|
||||
|
||||
|
||||
void QgsLayoutItemLegend::setAutoUpdateModel( bool autoUpdate )
|
||||
{
|
||||
if ( autoUpdate == autoUpdateModel() )
|
||||
@ -697,6 +700,7 @@ void QgsLayoutItemLegend::setLinkedMap( QgsLayoutItemMap *map )
|
||||
}
|
||||
|
||||
updateFilterByMap();
|
||||
|
||||
}
|
||||
|
||||
void QgsLayoutItemLegend::invalidateCurrentMap()
|
||||
@ -760,9 +764,8 @@ void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
|
||||
else
|
||||
{
|
||||
mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
|
||||
|
||||
const auto constFindLayers = mLegendModel->rootGroup()->findLayers();
|
||||
for ( QgsLayerTreeLayer *nodeLayer : constFindLayers )
|
||||
const QList< QgsLayerTreeLayer * > layers = mLegendModel->rootGroup()->findLayers();
|
||||
for ( QgsLayerTreeLayer *nodeLayer : layers )
|
||||
mLegendModel->refreshLayerLegend( nodeLayer );
|
||||
}
|
||||
|
||||
@ -846,11 +849,8 @@ QgsExpressionContext QgsLayoutItemLegend::createExpressionContext() const
|
||||
// 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" ) );
|
||||
|
||||
@ -862,16 +862,26 @@ QgsExpressionContext QgsLayoutItemLegend::createExpressionContext() const
|
||||
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_out_atlas" ), legendFilterOutAtlas(), true ) );
|
||||
|
||||
context.appendScope( scope );
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
#include "qgslayertreemodellegendnode.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgsmaplayerlegend.h"
|
||||
|
||||
QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent )
|
||||
QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent, QgsLayoutItemLegend *layout )
|
||||
: QgsLayerTreeModel( rootNode, parent )
|
||||
, mLayoutLegend( layout )
|
||||
{
|
||||
setFlag( QgsLayerTreeModel::AllowLegendChangeState, false );
|
||||
setFlag( QgsLayerTreeModel::AllowNodeReorder, true );
|
||||
}
|
||||
|
||||
QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QgsLayoutItemLegend *layout )
|
||||
: QgsLayerTreeModel( rootNode )
|
||||
, mLayoutLegend( layout )
|
||||
{
|
||||
setFlag( QgsLayerTreeModel::AllowLegendChangeState, false );
|
||||
setFlag( QgsLayerTreeModel::AllowNodeReorder, true );
|
||||
@ -880,22 +890,62 @@ QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent )
|
||||
QVariant QgsLegendModel::data( const QModelIndex &index, int role ) const
|
||||
{
|
||||
// handle custom layer node labels
|
||||
if ( QgsLayerTreeNode *node = index2node( index ) )
|
||||
{
|
||||
if ( QgsLayerTree::isLayer( node ) && ( role == Qt::DisplayRole || role == Qt::EditRole ) && !node->customProperty( QStringLiteral( "legend/title-label" ) ).isNull() )
|
||||
{
|
||||
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
|
||||
QString name = node->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
|
||||
if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() && role == Qt::DisplayRole )
|
||||
{
|
||||
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
|
||||
if ( vlayer && vlayer->featureCount() >= 0 )
|
||||
name += QStringLiteral( " [%1]" ).arg( vlayer->featureCount() );
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
QgsLayerTreeNode *node = index2node( index );
|
||||
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::isLayer( node ) ? QgsLayerTree::toLayer( node ) : nullptr;
|
||||
if ( nodeLayer && ( role == Qt::DisplayRole || role == Qt::EditRole ) )
|
||||
{
|
||||
QString name;
|
||||
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
|
||||
|
||||
//finding the first label that is stored
|
||||
name = nodeLayer->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
|
||||
if ( name.isEmpty() )
|
||||
name = nodeLayer->name();
|
||||
if ( name.isEmpty() )
|
||||
name = node->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
|
||||
if ( name.isEmpty() )
|
||||
name = node->name();
|
||||
if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() )
|
||||
{
|
||||
if ( vlayer && vlayer->featureCount() >= 0 )
|
||||
{
|
||||
name += QStringLiteral( " [%1]" ).arg( vlayer->featureCount() );
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
bool evaluate = vlayer ? !nodeLayer->labelExpression().isEmpty() : false;
|
||||
|
||||
if ( evaluate || name.contains( "[%" ) )
|
||||
{
|
||||
QgsExpressionContext expressionContext;
|
||||
if ( vlayer )
|
||||
{
|
||||
connect( vlayer, &QgsVectorLayer::symbolFeatureCountMapChanged, this, &QgsLegendModel::forceRefresh, Qt::UniqueConnection );
|
||||
// counting is done here to ensure that a valid vector layer needs to be evaluated, count is used to validate previous count or update the count if invalidated
|
||||
vlayer->countSymbolFeatures();
|
||||
}
|
||||
|
||||
if ( mLayoutLegend )
|
||||
expressionContext = mLayoutLegend->createExpressionContext();
|
||||
else
|
||||
expressionContext = QgsExpressionContext();
|
||||
|
||||
const QList<QgsLayerTreeModelLegendNode *> legendnodes = layerLegendNodes( nodeLayer, false );
|
||||
if ( legendnodes.count() > 1 ) // evaluate all existing legend nodes but leave the name for the legend evaluator
|
||||
{
|
||||
for ( QgsLayerTreeModelLegendNode *treenode : legendnodes )
|
||||
{
|
||||
if ( QgsSymbolLegendNode *symnode = qobject_cast<QgsSymbolLegendNode *>( treenode ) )
|
||||
symnode->evaluateLabel( expressionContext );
|
||||
}
|
||||
}
|
||||
else if ( QgsSymbolLegendNode *symnode = qobject_cast<QgsSymbolLegendNode *>( legendnodes.first() ) )
|
||||
name = symnode->evaluateLabel( expressionContext, name );
|
||||
}
|
||||
return name;
|
||||
}
|
||||
return QgsLayerTreeModel::data( index, role );
|
||||
}
|
||||
|
||||
@ -907,3 +957,22 @@ Qt::ItemFlags QgsLegendModel::flags( const QModelIndex &index ) const
|
||||
|
||||
return QgsLayerTreeModel::flags( index );
|
||||
}
|
||||
|
||||
QList<QgsLayerTreeModelLegendNode *> QgsLegendModel::layerLegendNodes( QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent ) const
|
||||
{
|
||||
if ( !mLegend.contains( nodeLayer ) )
|
||||
return QList<QgsLayerTreeModelLegendNode *>();
|
||||
|
||||
const LayerLegendData &data = mLegend[nodeLayer];
|
||||
QList<QgsLayerTreeModelLegendNode *> lst( data.activeNodes );
|
||||
if ( !skipNodeEmbeddedInParent && data.embeddedNodeInParent )
|
||||
lst.prepend( data.embeddedNodeInParent );
|
||||
return lst;
|
||||
}
|
||||
|
||||
void QgsLegendModel::forceRefresh()
|
||||
{
|
||||
emit refreshLegend();
|
||||
}
|
||||
|
||||
|
||||
|
@ -24,11 +24,13 @@
|
||||
#include "qgslayertreemodel.h"
|
||||
#include "qgslegendsettings.h"
|
||||
#include "qgslayertreegroup.h"
|
||||
#include "qgsexpressioncontext.h"
|
||||
|
||||
class QgsLayerTreeModel;
|
||||
class QgsSymbol;
|
||||
class QgsLayoutItemMap;
|
||||
class QgsLegendRenderer;
|
||||
class QgsLayoutItemLegend;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -44,11 +46,48 @@ class CORE_EXPORT QgsLegendModel : public QgsLayerTreeModel
|
||||
|
||||
public:
|
||||
//! Construct the model based on the given layer tree
|
||||
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent SIP_TRANSFERTHIS = nullptr );
|
||||
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent SIP_TRANSFERTHIS = nullptr, QgsLayoutItemLegend *layout = nullptr );
|
||||
|
||||
//! Alternative constructor.
|
||||
QgsLegendModel( QgsLayerTree *rootNode, QgsLayoutItemLegend *layout );
|
||||
|
||||
QVariant data( const QModelIndex &index, int role ) const override;
|
||||
|
||||
Qt::ItemFlags flags( const QModelIndex &index ) const override;
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
* Emitted to refresh the legend.
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
void refreshLegend();
|
||||
|
||||
private slots:
|
||||
|
||||
/**
|
||||
* Handle incoming signal to refresh the legend.
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
void forceRefresh();
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Returns filtered list of active legend nodes attached to a particular layer node
|
||||
* (by default it returns also legend node embedded in parent layer node (if any) unless skipNodeEmbeddedInParent is true)
|
||||
* \note Parameter skipNodeEmbeddedInParent added in QGIS 2.18
|
||||
* \see layerOriginalLegendNodes()
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QList<QgsLayerTreeModelLegendNode *> layerLegendNodes( QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent = false ) const;
|
||||
|
||||
/**
|
||||
* Pointer to the QgsLayoutItemLegend class that made the model.
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QgsLayoutItemLegend *mLayoutLegend = nullptr;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -458,7 +497,6 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
|
||||
|
||||
QgsExpressionContext createExpressionContext() const override;
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
void refresh() override;
|
||||
@ -486,6 +524,7 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
|
||||
|
||||
void nodeCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key );
|
||||
|
||||
|
||||
private:
|
||||
QgsLayoutItemLegend() = delete;
|
||||
|
||||
|
@ -2243,3 +2243,4 @@ QgsRectangle QgsLayoutItemMap::computeAtlasRectangle()
|
||||
return g.boundingBox();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ class QTextCodec;
|
||||
#include "qgsrelation.h"
|
||||
#include "qgsfeaturesink.h"
|
||||
#include "qgsfeaturesource.h"
|
||||
#include "qgsfeaturerequest.h"
|
||||
|
||||
typedef QList<int> QgsAttributeList SIP_SKIP;
|
||||
typedef QSet<int> QgsAttributeIds SIP_SKIP;
|
||||
@ -43,7 +44,6 @@ class QgsFeedback;
|
||||
class QgsFeatureRenderer;
|
||||
class QgsAbstractVectorLayerLabeling;
|
||||
|
||||
#include "qgsfeaturerequest.h"
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
|
@ -27,7 +27,7 @@
|
||||
#include <QFont>
|
||||
#include <QMutex>
|
||||
|
||||
#include "qgis_sip.h"
|
||||
#include "qgis.h"
|
||||
#include "qgsmaplayer.h"
|
||||
#include "qgsfeature.h"
|
||||
#include "qgsfeaturerequest.h"
|
||||
@ -41,6 +41,7 @@
|
||||
#include "qgsfeatureiterator.h"
|
||||
#include "qgsexpressioncontextgenerator.h"
|
||||
#include "qgsexpressioncontextscopegenerator.h"
|
||||
#include "qgsexpressioncontext.h"
|
||||
|
||||
class QPainter;
|
||||
class QImage;
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "qgsvectorlayerfeaturecounter.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgsfeatureid.h"
|
||||
|
||||
QgsVectorLayerFeatureCounter::QgsVectorLayerFeatureCounter( QgsVectorLayer *layer, const QgsExpressionContext &context )
|
||||
: QgsTask( tr( "Counting features in %1" ).arg( layer->name() ), QgsTask::CanCancel )
|
||||
@ -31,12 +32,15 @@ QgsVectorLayerFeatureCounter::QgsVectorLayerFeatureCounter( QgsVectorLayer *laye
|
||||
|
||||
bool QgsVectorLayerFeatureCounter::run()
|
||||
{
|
||||
mSymbolFeatureCountMap.clear();
|
||||
mSymbolFeatureIdMap.clear();
|
||||
QgsLegendSymbolList symbolList = mRenderer->legendSymbolItems();
|
||||
QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin();
|
||||
|
||||
for ( ; symbolIt != symbolList.constEnd(); ++symbolIt )
|
||||
{
|
||||
mSymbolFeatureCountMap.insert( symbolIt->label(), 0 );
|
||||
mSymbolFeatureIdMap.insert( symbolIt->label(), QgsFeatureIds() );
|
||||
}
|
||||
|
||||
// If there are no features to be counted, we can spare us the trouble
|
||||
@ -65,11 +69,12 @@ bool QgsVectorLayerFeatureCounter::run()
|
||||
while ( fit.nextFeature( f ) )
|
||||
{
|
||||
renderContext.expressionContext().setFeature( f );
|
||||
QSet<QString> featureKeyList = mRenderer->legendKeysForFeature( f, renderContext );
|
||||
const auto constFeatureKeyList = featureKeyList;
|
||||
for ( const QString &key : constFeatureKeyList )
|
||||
|
||||
const QSet<QString> featureKeyList = mRenderer->legendKeysForFeature( f, renderContext );
|
||||
for ( const QString &key : featureKeyList )
|
||||
{
|
||||
mSymbolFeatureCountMap[key] += 1;
|
||||
mSymbolFeatureIdMap[key].insert( f.id() );
|
||||
}
|
||||
++featuresCounted;
|
||||
|
||||
@ -88,9 +93,7 @@ bool QgsVectorLayerFeatureCounter::run()
|
||||
}
|
||||
mRenderer->stopRender( renderContext );
|
||||
}
|
||||
|
||||
setProgress( 100 );
|
||||
|
||||
emit symbolsCounted();
|
||||
return true;
|
||||
}
|
||||
@ -104,3 +107,13 @@ long QgsVectorLayerFeatureCounter::featureCount( const QString &legendKey ) cons
|
||||
{
|
||||
return mSymbolFeatureCountMap.value( legendKey, -1 );
|
||||
}
|
||||
|
||||
QHash<QString, QgsFeatureIds> QgsVectorLayerFeatureCounter::symbolFeatureIdMap() const
|
||||
{
|
||||
return mSymbolFeatureIdMap;
|
||||
}
|
||||
|
||||
QgsFeatureIds QgsVectorLayerFeatureCounter::featureIds( const QString &symbolkey ) const
|
||||
{
|
||||
return mSymbolFeatureIdMap.value( symbolkey, QgsFeatureIds() );
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "qgsvectorlayerfeatureiterator.h"
|
||||
#include "qgsrenderer.h"
|
||||
#include "qgstaskmanager.h"
|
||||
#include "qgsfeatureid.h"
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -40,10 +41,14 @@ class CORE_EXPORT QgsVectorLayerFeatureCounter : public QgsTask
|
||||
*/
|
||||
QgsVectorLayerFeatureCounter( QgsVectorLayer *layer, const QgsExpressionContext &context = QgsExpressionContext() );
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the feature count and Ids per symbol
|
||||
*/
|
||||
bool run() override;
|
||||
|
||||
/**
|
||||
* Gets the count for each symbol. Only valid after the symbolsCounted()
|
||||
* Returns the count for each symbol. Only valid after the symbolsCounted()
|
||||
* signal has been emitted.
|
||||
*
|
||||
* \note Not available in Python bindings.
|
||||
@ -51,11 +56,29 @@ class CORE_EXPORT QgsVectorLayerFeatureCounter : public QgsTask
|
||||
QHash<QString, long> symbolFeatureCountMap() const SIP_SKIP;
|
||||
|
||||
/**
|
||||
* Gets the feature count for a particular \a legendKey.
|
||||
* Returns the feature count for a particular \a legendKey.
|
||||
* If the key has not been found, -1 will be returned.
|
||||
*/
|
||||
long featureCount( const QString &legendKey ) const;
|
||||
|
||||
/**
|
||||
* Returns the QgsFeatureIds for each symbol. Only valid after the symbolsCounted()
|
||||
* signal has been emitted.
|
||||
*
|
||||
* \see symbolFeatureCountMap
|
||||
* \note Not available in Python bindings.
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QHash<QString, QgsFeatureIds> symbolFeatureIdMap() const SIP_SKIP;
|
||||
|
||||
/**
|
||||
* Returns the feature Ids for a particular \a legendKey.
|
||||
* If the key has not been found an empty QSet will be returned.
|
||||
*
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QgsFeatureIds featureIds( const QString &symbolkey ) const;
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
@ -68,6 +91,7 @@ class CORE_EXPORT QgsVectorLayerFeatureCounter : public QgsTask
|
||||
std::unique_ptr<QgsFeatureRenderer> mRenderer;
|
||||
QgsExpressionContext mExpressionContext;
|
||||
QHash<QString, long> mSymbolFeatureCountMap;
|
||||
QHash<QString, QgsFeatureIds> mSymbolFeatureIdMap;
|
||||
int mFeatureCount;
|
||||
|
||||
};
|
||||
|
@ -401,6 +401,23 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="mLayerExpressionButton">
|
||||
<property name="toolTip">
|
||||
<string> Add an expression to the vector layer and each child symbol's label</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionAddExpression.svg</normaloff>:/images/themes/default/mActionAddExpression.svg</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
|
@ -32,14 +32,15 @@ from qgis.core import (QgsPrintLayout,
|
||||
QgsExpression,
|
||||
QgsMapLayerLegendUtils,
|
||||
QgsLegendStyle,
|
||||
QgsFontUtils)
|
||||
QgsFontUtils,
|
||||
QgsApplication)
|
||||
from qgis.testing import (start_app,
|
||||
unittest
|
||||
)
|
||||
from utilities import unitTestDataPath
|
||||
from qgslayoutchecker import QgsLayoutChecker
|
||||
import os
|
||||
|
||||
from time import sleep
|
||||
from test_qgslayoutitem import LayoutItemTestCase
|
||||
|
||||
start_app()
|
||||
@ -66,6 +67,7 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
||||
|
||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||
QgsProject.instance().clear()
|
||||
QgsProject.instance().addMapLayers([point_layer])
|
||||
|
||||
marker_symbol = QgsMarkerSymbol.createSimple({'color': '#ff0000', 'outline_style': 'no', 'size': '5', 'size_unit': 'MapUnit'})
|
||||
@ -123,7 +125,6 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
||||
|
||||
def testResizeWithMapContent(self):
|
||||
"""Test test legend resizes to match map content"""
|
||||
|
||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||
QgsProject.instance().addMapLayers([point_layer])
|
||||
@ -164,7 +165,6 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
||||
|
||||
def testResizeDisabled(self):
|
||||
"""Test that test legend does not resize if auto size is disabled"""
|
||||
|
||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||
QgsProject.instance().addMapLayers([point_layer])
|
||||
@ -209,7 +209,6 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
||||
|
||||
def testResizeDisabledCrop(self):
|
||||
"""Test that if legend resizing is disabled, and legend is too small, then content is cropped"""
|
||||
|
||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||
QgsProject.instance().addMapLayers([point_layer])
|
||||
@ -322,10 +321,8 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
||||
|
||||
def testExpressionInText(self):
|
||||
"""Test expressions embedded in legend node text"""
|
||||
|
||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||
|
||||
layout = QgsPrintLayout(QgsProject.instance())
|
||||
layout.setName('LAYOUT')
|
||||
layout.initializeDefaults()
|
||||
@ -378,6 +375,121 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
||||
|
||||
QgsProject.instance().removeMapLayers([point_layer.id()])
|
||||
|
||||
def testSymbolExpressions(self):
|
||||
"Test expressions embedded in legend node text"
|
||||
QgsProject.instance().clear()
|
||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||
|
||||
layout = QgsPrintLayout(QgsProject.instance())
|
||||
layout.initializeDefaults()
|
||||
|
||||
map = QgsLayoutItemMap(layout)
|
||||
map.setLayers([point_layer])
|
||||
layout.addLayoutItem(map)
|
||||
map.setExtent(point_layer.extent())
|
||||
|
||||
legend = QgsLayoutItemLegend(layout)
|
||||
|
||||
layer = QgsProject.instance().addMapLayer(point_layer)
|
||||
legendlayer = legend.model().rootGroup().addLayer(point_layer)
|
||||
|
||||
counterTask = point_layer.countSymbolFeatures()
|
||||
counterTask.waitForFinished()
|
||||
TM = QgsApplication.taskManager()
|
||||
actask = TM.activeTasks()
|
||||
print(TM.tasks(), actask)
|
||||
count = actask[0]
|
||||
legend.model().refreshLayerLegend(legendlayer)
|
||||
legendnodes = legend.model().layerLegendNodes(legendlayer)
|
||||
legendnodes[0].setUserLabel('[% @symbol_id %]')
|
||||
legendnodes[1].setUserLabel('[% @symbol_count %]')
|
||||
legendnodes[2].setUserLabel('[% sum("Pilots") %]')
|
||||
label1 = legendnodes[0].evaluateLabel()
|
||||
label2 = legendnodes[1].evaluateLabel()
|
||||
label3 = legendnodes[2].evaluateLabel()
|
||||
count.waitForFinished()
|
||||
self.assertEqual(label1, '0')
|
||||
#self.assertEqual(label2, '5')
|
||||
#self.assertEqual(label3, '12')
|
||||
|
||||
legendlayer.setLabelExpression("Concat(@symbol_label, @symbol_id)")
|
||||
|
||||
label1 = legendnodes[0].evaluateLabel()
|
||||
label2 = legendnodes[1].evaluateLabel()
|
||||
label3 = legendnodes[2].evaluateLabel()
|
||||
|
||||
self.assertEqual(label1, ' @symbol_id 0')
|
||||
#self.assertEqual(label2, '@symbol_count 1')
|
||||
#self.assertEqual(label3, 'sum("Pilots") 2')
|
||||
|
||||
QgsProject.instance().clear()
|
||||
|
||||
def testSymbolExpressionRender(self):
|
||||
"""Test expressions embedded in legend node text"""
|
||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||
layout = QgsPrintLayout(QgsProject.instance())
|
||||
layout.setName('LAYOUT')
|
||||
layout.initializeDefaults()
|
||||
|
||||
map = QgsLayoutItemMap(layout)
|
||||
map.attemptSetSceneRect(QRectF(20, 20, 80, 80))
|
||||
map.setFrameEnabled(True)
|
||||
map.setLayers([point_layer])
|
||||
layout.addLayoutItem(map)
|
||||
map.setExtent(point_layer.extent())
|
||||
|
||||
legend = QgsLayoutItemLegend(layout)
|
||||
legend.setTitle("Legend")
|
||||
legend.attemptSetSceneRect(QRectF(120, 20, 100, 100))
|
||||
legend.setFrameEnabled(True)
|
||||
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
|
||||
legend.setBackgroundColor(QColor(200, 200, 200))
|
||||
legend.setTitle('')
|
||||
legend.setLegendFilterByMapEnabled(False)
|
||||
legend.setStyleFont(QgsLegendStyle.Title, QgsFontUtils.getStandardTestFont('Bold', 16))
|
||||
legend.setStyleFont(QgsLegendStyle.Group, QgsFontUtils.getStandardTestFont('Bold', 16))
|
||||
legend.setStyleFont(QgsLegendStyle.Subgroup, QgsFontUtils.getStandardTestFont('Bold', 16))
|
||||
legend.setStyleFont(QgsLegendStyle.Symbol, QgsFontUtils.getStandardTestFont('Bold', 16))
|
||||
legend.setStyleFont(QgsLegendStyle.SymbolLabel, QgsFontUtils.getStandardTestFont('Bold', 16))
|
||||
|
||||
legend.setAutoUpdateModel(False)
|
||||
|
||||
QgsProject.instance().addMapLayers([point_layer])
|
||||
s = QgsMapSettings()
|
||||
s.setLayers([point_layer])
|
||||
|
||||
group = legend.model().rootGroup().addGroup("Group [% 1 + 5 %] [% @layout_name %]")
|
||||
layer_tree_layer = group.addLayer(point_layer)
|
||||
counterTask = point_layer.countSymbolFeatures()
|
||||
counterTask.waitForFinished() # does this even work?
|
||||
layer_tree_layer.setCustomProperty("legend/title-label", 'bbbb [% 1+2 %] xx [% @layout_name %] [% @layer_name %]')
|
||||
QgsMapLayerLegendUtils.setLegendNodeUserLabel(layer_tree_layer, 0, 'xxxx')
|
||||
legend.model().refreshLayerLegend(layer_tree_layer)
|
||||
layer_tree_layer.setLabelExpression('Concat(@symbol_id, @symbol_label, count("Class"))')
|
||||
legend.model().layerLegendNodes(layer_tree_layer)[0].setUserLabel(' sym 1')
|
||||
legend.model().layerLegendNodes(layer_tree_layer)[1].setUserLabel('[%@symbol_count %]')
|
||||
legend.model().layerLegendNodes(layer_tree_layer)[2].setUserLabel('[% count("Class") %]')
|
||||
layout.addLayoutItem(legend)
|
||||
legend.setLinkedMap(map)
|
||||
legend.updateLegend()
|
||||
print(layer_tree_layer.labelExpression())
|
||||
TM = QgsApplication.taskManager()
|
||||
actask = TM.activeTasks()
|
||||
print(TM.tasks(), actask)
|
||||
count = actask[0]
|
||||
count.waitForFinished()
|
||||
map.setExtent(QgsRectangle(-102.51, 41.16, -102.36, 41.30))
|
||||
checker = QgsLayoutChecker(
|
||||
'composer_legend_symbol_expression', layout)
|
||||
checker.setControlPathPrefix("composer_legend")
|
||||
sleep(4)
|
||||
result, message = checker.testLayout()
|
||||
self.assertTrue(result, message)
|
||||
|
||||
QgsProject.instance().removeMapLayers([point_layer.id()])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Loading…
x
Reference in New Issue
Block a user