mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-30 00:04:26 -04:00
Symbol aware legend expression (#9648)
This commit is contained in:
parent
eb73702982
commit
248af94ba9
images
python/core/auto_generated
src
app/layout
core
expression
layertree
qgslayertreelayer.cppqgslayertreelayer.hqgslayertreemodellegendnode.cppqgslayertreemodellegendnode.h
layout
qgsvectordataprovider.hqgsvectorlayer.hqgsvectorlayerfeaturecounter.cppqgsvectorlayerfeaturecounter.hui/layout
tests
src/python
testdata/control_images/composer_legend/expected_composer_legend_symbol_expression
@ -177,6 +177,7 @@
|
|||||||
<file>themes/default/mActionDistributeTop.svg</file>
|
<file>themes/default/mActionDistributeTop.svg</file>
|
||||||
<file>themes/default/mActionDistributeVCenter.svg</file>
|
<file>themes/default/mActionDistributeVCenter.svg</file>
|
||||||
<file>themes/default/mActionDistributeVSpace.svg</file>
|
<file>themes/default/mActionDistributeVSpace.svg</file>
|
||||||
|
<file>themes/default/mActionAddExpression.svg</file>
|
||||||
<file>themes/default/mActionAddLayer.svg</file>
|
<file>themes/default/mActionAddLayer.svg</file>
|
||||||
<file>themes/default/mActionAddMeshLayer.svg</file>
|
<file>themes/default/mActionAddMeshLayer.svg</file>
|
||||||
<file>themes/default/mActionAddAllToOverview.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 (image error) 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)
|
Resolves reference to layer from stored layer ID (if it has not been resolved already)
|
||||||
|
|
||||||
.. versionadded:: 3.0
|
.. 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
|
%End
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QgsLayerTreeModelLegendNode : QObject
|
class QgsLayerTreeModelLegendNode : QObject
|
||||||
{
|
{
|
||||||
%Docstring
|
%Docstring
|
||||||
@ -26,6 +27,12 @@ and customized look.
|
|||||||
|
|
||||||
%TypeHeaderCode
|
%TypeHeaderCode
|
||||||
#include "qgslayertreemodellegendnode.h"
|
#include "qgslayertreemodellegendnode.h"
|
||||||
|
%End
|
||||||
|
%ConvertToSubClassCode
|
||||||
|
if ( qobject_cast<QgsSymbolLegendNode *> ( sipCpp ) )
|
||||||
|
sipType = sipType_QgsSymbolLegendNode;
|
||||||
|
else
|
||||||
|
sipType = 0;
|
||||||
%End
|
%End
|
||||||
public:
|
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.
|
Sets format of text to be shown on top of the symbol.
|
||||||
|
|
||||||
.. versionadded:: 3.2
|
.. 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
|
%End
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -24,9 +24,14 @@ Overrides some functionality of QgsLayerTreeModel to better fit the needs of lay
|
|||||||
#include "qgslayoutitemlegend.h"
|
#include "qgslayoutitemlegend.h"
|
||||||
%End
|
%End
|
||||||
public:
|
public:
|
||||||
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent /TransferThis/ = 0 );
|
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent /TransferThis/ = 0, QgsLayoutItemLegend *layout = 0 );
|
||||||
%Docstring
|
%Docstring
|
||||||
Construct the model based on the given layer tree
|
Construct the model based on the given layer tree
|
||||||
|
%End
|
||||||
|
|
||||||
|
QgsLegendModel( QgsLayerTree *rootNode, QgsLayoutItemLegend *layout );
|
||||||
|
%Docstring
|
||||||
|
Alternative constructor.
|
||||||
%End
|
%End
|
||||||
|
|
||||||
virtual QVariant data( const QModelIndex &index, int role ) const;
|
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;
|
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;
|
virtual QgsExpressionContext createExpressionContext() const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
virtual void refresh();
|
virtual void refresh();
|
||||||
|
@ -2279,7 +2279,6 @@ Configuration and logic to apply automatically on any edit happening on this lay
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
void select( QgsFeatureId featureId );
|
void select( QgsFeatureId featureId );
|
||||||
@ -2653,6 +2652,7 @@ Emitted when the feature count for symbols on this layer has been recalculated.
|
|||||||
.. versionadded:: 3.0
|
.. versionadded:: 3.0
|
||||||
%End
|
%End
|
||||||
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void setExtent( const QgsRectangle &rect ) ${SIP_FINAL};
|
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``.
|
Create a new feature counter for ``layer``.
|
||||||
%End
|
%End
|
||||||
|
|
||||||
|
|
||||||
virtual bool run();
|
virtual bool run();
|
||||||
|
|
||||||
|
%Docstring
|
||||||
|
Calculates the feature count and Ids per symbol
|
||||||
|
%End
|
||||||
|
|
||||||
|
|
||||||
long featureCount( const QString &legendKey ) const;
|
long featureCount( const QString &legendKey ) const;
|
||||||
%Docstring
|
%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.
|
If the key has not been found, -1 will be returned.
|
||||||
%End
|
%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:
|
signals:
|
||||||
|
|
||||||
void symbolsCounted();
|
void symbolsCounted();
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include "qgslayoutitemlegend.h"
|
#include "qgslayoutitemlegend.h"
|
||||||
#include "qgslayoutmeasurementconverter.h"
|
#include "qgslayoutmeasurementconverter.h"
|
||||||
#include "qgsunittypes.h"
|
#include "qgsunittypes.h"
|
||||||
|
#include "qgsexpressionbuilderdialog.h"
|
||||||
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
@ -104,6 +105,7 @@ QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend )
|
|||||||
connect( mEditPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mEditPushButton_clicked );
|
connect( mEditPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mEditPushButton_clicked );
|
||||||
connect( mCountToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mCountToolButton_clicked );
|
connect( mCountToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mCountToolButton_clicked );
|
||||||
connect( mExpressionFilterButton, &QgsLegendFilterButton::toggled, this, &QgsLayoutLegendWidget::mExpressionFilterButton_toggled );
|
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( mFilterByMapToolButton, &QToolButton::toggled, this, &QgsLayoutLegendWidget::mFilterByMapToolButton_toggled );
|
||||||
connect( mUpdateAllPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mUpdateAllPushButton_clicked );
|
connect( mUpdateAllPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mUpdateAllPushButton_clicked );
|
||||||
connect( mAddGroupToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mAddGroupToolButton_clicked );
|
connect( mAddGroupToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mAddGroupToolButton_clicked );
|
||||||
@ -136,6 +138,7 @@ QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend )
|
|||||||
mMoveUpToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowUp.svg" ) ) );
|
mMoveUpToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowUp.svg" ) ) );
|
||||||
mMoveDownToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowDown.svg" ) ) );
|
mMoveDownToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowDown.svg" ) ) );
|
||||||
mCountToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionSum.svg" ) ) );
|
mCountToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionSum.svg" ) ) );
|
||||||
|
mLayerExpressionButton->setIcon( QIcon( QgsApplication::iconPath( "mActionAddExpression.svg" ) ) );
|
||||||
|
|
||||||
mFontColorButton->setColorDialogTitle( tr( "Select Font Color" ) );
|
mFontColorButton->setColorDialogTitle( tr( "Select Font Color" ) );
|
||||||
mFontColorButton->setContext( QStringLiteral( "composer" ) );
|
mFontColorButton->setContext( QStringLiteral( "composer" ) );
|
||||||
@ -994,6 +997,45 @@ void QgsLayoutLegendWidget::mExpressionFilterButton_toggled( bool checked )
|
|||||||
mLegend->endCommand();
|
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()
|
void QgsLayoutLegendWidget::mUpdateAllPushButton_clicked()
|
||||||
{
|
{
|
||||||
updateLegend();
|
updateLegend();
|
||||||
@ -1105,12 +1147,27 @@ void QgsLayoutLegendWidget::selectedChanged( const QModelIndex ¤t, const Q
|
|||||||
Q_UNUSED( current )
|
Q_UNUSED( current )
|
||||||
Q_UNUSED( previous )
|
Q_UNUSED( previous )
|
||||||
|
|
||||||
|
mLayerExpressionButton->setEnabled( false );
|
||||||
|
|
||||||
if ( mLegend && mLegend->autoUpdateModel() )
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mCountToolButton->setChecked( false );
|
mCountToolButton->setChecked( false );
|
||||||
mCountToolButton->setEnabled( false );
|
mCountToolButton->setEnabled( false );
|
||||||
|
|
||||||
|
|
||||||
mExpressionFilterButton->blockSignals( true );
|
mExpressionFilterButton->blockSignals( true );
|
||||||
mExpressionFilterButton->setChecked( false );
|
mExpressionFilterButton->setChecked( false );
|
||||||
mExpressionFilterButton->setEnabled( 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->setChecked( currentNode->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() );
|
||||||
mCountToolButton->setEnabled( true );
|
mCountToolButton->setEnabled( true );
|
||||||
|
mLayerExpressionButton->setEnabled( true );
|
||||||
|
|
||||||
bool exprEnabled;
|
bool exprEnabled;
|
||||||
QString expr = QgsLayerTreeUtils::legendFilterByExpression( *qobject_cast<QgsLayerTreeLayer *>( currentNode ), &exprEnabled );
|
QString expr = QgsLayerTreeUtils::legendFilterByExpression( *qobject_cast<QgsLayerTreeLayer *>( currentNode ), &exprEnabled );
|
||||||
|
@ -85,6 +85,7 @@ class QgsLayoutLegendWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayo
|
|||||||
void resetLayerNodeToDefaults();
|
void resetLayerNodeToDefaults();
|
||||||
void mUpdateAllPushButton_clicked();
|
void mUpdateAllPushButton_clicked();
|
||||||
void mAddGroupToolButton_clicked();
|
void mAddGroupToolButton_clicked();
|
||||||
|
void mLayerExpressionButton_clicked();
|
||||||
|
|
||||||
void mFilterLegendByAtlasCheckBox_toggled( bool checked );
|
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_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_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
|
//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_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." ) );
|
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 );
|
Q_ASSERT( mRef );
|
||||||
emit nameChanged( this, mRef->name() );
|
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;
|
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:
|
signals:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -148,6 +162,8 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
|
|||||||
QgsMapLayerRef mRef;
|
QgsMapLayerRef mRef;
|
||||||
//! Layer name - only used if layer does not exist or if mUseLayerName is false
|
//! Layer name - only used if layer does not exist or if mUseLayerName is false
|
||||||
QString mLayerName;
|
QString mLayerName;
|
||||||
|
//! Expression to evaluate in the legend
|
||||||
|
QString mLabelExpression;
|
||||||
|
|
||||||
//!
|
//!
|
||||||
bool mUseLayerName = true;
|
bool mUseLayerName = true;
|
||||||
|
@ -29,6 +29,11 @@
|
|||||||
#include "qgsvectorlayer.h"
|
#include "qgsvectorlayer.h"
|
||||||
#include "qgsrasterrenderer.h"
|
#include "qgsrasterrenderer.h"
|
||||||
#include "qgsexpressioncontextutils.h"
|
#include "qgsexpressioncontextutils.h"
|
||||||
|
#include "qgsfeatureid.h"
|
||||||
|
#include "qgslayoutitem.h"
|
||||||
|
#include "qgsvectorlayerfeaturecounter.h"
|
||||||
|
#include "qgsexpression.h"
|
||||||
|
|
||||||
|
|
||||||
QgsLayerTreeModelLegendNode::QgsLayerTreeModelLegendNode( QgsLayerTreeLayer *nodeL, QObject *parent )
|
QgsLayerTreeModelLegendNode::QgsLayerTreeModelLegendNode( QgsLayerTreeLayer *nodeL, QObject *parent )
|
||||||
: QObject( parent )
|
: QObject( parent )
|
||||||
@ -284,6 +289,20 @@ const QgsSymbol *QgsSymbolLegendNode::symbol() const
|
|||||||
return mItem.symbol();
|
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 )
|
void QgsSymbolLegendNode::setSymbol( QgsSymbol *symbol )
|
||||||
{
|
{
|
||||||
if ( !symbol )
|
if ( !symbol )
|
||||||
@ -648,31 +667,68 @@ void QgsSymbolLegendNode::updateLabel()
|
|||||||
|
|
||||||
bool showFeatureCount = mLayerNode->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toBool();
|
bool showFeatureCount = mLayerNode->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toBool();
|
||||||
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
|
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
|
||||||
|
mLabel = symbolLabel();
|
||||||
|
|
||||||
if ( mEmbeddedInParent )
|
if ( showFeatureCount && vl )
|
||||||
{
|
{
|
||||||
QString layerName = mLayerNode->name();
|
qlonglong count = mEmbeddedInParent ? vl->featureCount() : vl->featureCount( mItem.ruleKey() ) ;
|
||||||
if ( !mLayerNode->customProperty( QStringLiteral( "legend/title-label" ) ).isNull() )
|
mLabel += QStringLiteral( " [%1]" ).arg( count != -1 ? QLocale().toString( count ) : tr( "N/A" ) );
|
||||||
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" ) );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit dataChanged();
|
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 "qgis_sip.h"
|
||||||
|
|
||||||
#include "qgsrasterdataprovider.h" // for QgsImageFetcher dtor visibility
|
#include "qgsrasterdataprovider.h" // for QgsImageFetcher dtor visibility
|
||||||
|
#include "qgsexpressioncontext.h"
|
||||||
|
|
||||||
class QgsLayerTreeLayer;
|
class QgsLayerTreeLayer;
|
||||||
class QgsLayerTreeModel;
|
class QgsLayerTreeModel;
|
||||||
@ -48,6 +49,14 @@ class QgsRenderContext;
|
|||||||
class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
|
class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
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:
|
public:
|
||||||
|
|
||||||
enum LegendNodeRoles
|
enum LegendNodeRoles
|
||||||
@ -228,6 +237,7 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -319,6 +329,20 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
|
|||||||
*/
|
*/
|
||||||
void setTextOnSymbolTextFormat( const QgsTextFormat &format ) { mTextOnSymbolTextFormat = format; }
|
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:
|
public slots:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -361,6 +385,12 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
|
|||||||
// ident the symbol icon to make it look like a tree structure
|
// ident the symbol icon to make it look like a tree structure
|
||||||
static const int INDENT_SIZE = 20;
|
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.
|
* Sets all items belonging to the same layer as this node to the same check state.
|
||||||
* \param state check state
|
* \param state check state
|
||||||
|
@ -34,10 +34,11 @@
|
|||||||
#include <QDomDocument>
|
#include <QDomDocument>
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
#include "qgsexpressioncontext.h"
|
||||||
|
|
||||||
QgsLayoutItemLegend::QgsLayoutItemLegend( QgsLayout *layout )
|
QgsLayoutItemLegend::QgsLayoutItemLegend( QgsLayout *layout )
|
||||||
: QgsLayoutItem( layout )
|
: QgsLayoutItem( layout )
|
||||||
, mLegendModel( new QgsLegendModel( layout->project()->layerTreeRoot() ) )
|
, mLegendModel( new QgsLegendModel( layout->project()->layerTreeRoot(), this ) )
|
||||||
{
|
{
|
||||||
#if 0 //no longer required?
|
#if 0 //no longer required?
|
||||||
connect( &layout->atlasComposition(), &QgsAtlasComposition::renderEnded, this, &QgsLayoutItemLegend::onAtlasEnded );
|
connect( &layout->atlasComposition(), &QgsAtlasComposition::renderEnded, this, &QgsLayoutItemLegend::onAtlasEnded );
|
||||||
@ -55,6 +56,7 @@ QgsLayoutItemLegend::QgsLayoutItemLegend( QgsLayout *layout )
|
|||||||
invalidateCache();
|
invalidateCache();
|
||||||
update();
|
update();
|
||||||
} );
|
} );
|
||||||
|
connect( mLegendModel.get(), &QgsLegendModel::refreshLegend, this, &QgsLayoutItemLegend::refresh );
|
||||||
}
|
}
|
||||||
|
|
||||||
QgsLayoutItemLegend *QgsLayoutItemLegend::create( QgsLayout *layout )
|
QgsLayoutItemLegend *QgsLayoutItemLegend::create( QgsLayout *layout )
|
||||||
@ -225,6 +227,7 @@ void QgsLayoutItemLegend::setCustomLayerTree( QgsLayerTree *rootGroup )
|
|||||||
mCustomLayerTree.reset( rootGroup );
|
mCustomLayerTree.reset( rootGroup );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void QgsLayoutItemLegend::setAutoUpdateModel( bool autoUpdate )
|
void QgsLayoutItemLegend::setAutoUpdateModel( bool autoUpdate )
|
||||||
{
|
{
|
||||||
if ( autoUpdate == autoUpdateModel() )
|
if ( autoUpdate == autoUpdateModel() )
|
||||||
@ -697,6 +700,7 @@ void QgsLayoutItemLegend::setLinkedMap( QgsLayoutItemMap *map )
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateFilterByMap();
|
updateFilterByMap();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsLayoutItemLegend::invalidateCurrentMap()
|
void QgsLayoutItemLegend::invalidateCurrentMap()
|
||||||
@ -760,9 +764,8 @@ void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
|
mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
|
||||||
|
const QList< QgsLayerTreeLayer * > layers = mLegendModel->rootGroup()->findLayers();
|
||||||
const auto constFindLayers = mLegendModel->rootGroup()->findLayers();
|
for ( QgsLayerTreeLayer *nodeLayer : layers )
|
||||||
for ( QgsLayerTreeLayer *nodeLayer : constFindLayers )
|
|
||||||
mLegendModel->refreshLayerLegend( nodeLayer );
|
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
|
// 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
|
// 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.
|
// will contain duplicate global, project, layout, etc scopes.
|
||||||
|
|
||||||
if ( mMap )
|
if ( mMap )
|
||||||
{
|
|
||||||
context.appendScope( mMap->createExpressionContext().popScope() );
|
context.appendScope( mMap->createExpressionContext().popScope() );
|
||||||
}
|
|
||||||
|
|
||||||
QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Legend Settings" ) );
|
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 ) );
|
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_out_atlas" ), legendFilterOutAtlas(), true ) );
|
||||||
|
|
||||||
context.appendScope( scope );
|
context.appendScope( scope );
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
#include "qgslayertreemodellegendnode.h"
|
#include "qgslayertreemodellegendnode.h"
|
||||||
#include "qgsvectorlayer.h"
|
#include "qgsvectorlayer.h"
|
||||||
|
#include "qgsmaplayerlegend.h"
|
||||||
|
|
||||||
QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent )
|
QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent, QgsLayoutItemLegend *layout )
|
||||||
: QgsLayerTreeModel( rootNode, parent )
|
: 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::AllowLegendChangeState, false );
|
||||||
setFlag( QgsLayerTreeModel::AllowNodeReorder, true );
|
setFlag( QgsLayerTreeModel::AllowNodeReorder, true );
|
||||||
@ -880,22 +890,62 @@ QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent )
|
|||||||
QVariant QgsLegendModel::data( const QModelIndex &index, int role ) const
|
QVariant QgsLegendModel::data( const QModelIndex &index, int role ) const
|
||||||
{
|
{
|
||||||
// handle custom layer node labels
|
// 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 );
|
return QgsLayerTreeModel::data( index, role );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -907,3 +957,22 @@ Qt::ItemFlags QgsLegendModel::flags( const QModelIndex &index ) const
|
|||||||
|
|
||||||
return QgsLayerTreeModel::flags( index );
|
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 "qgslayertreemodel.h"
|
||||||
#include "qgslegendsettings.h"
|
#include "qgslegendsettings.h"
|
||||||
#include "qgslayertreegroup.h"
|
#include "qgslayertreegroup.h"
|
||||||
|
#include "qgsexpressioncontext.h"
|
||||||
|
|
||||||
class QgsLayerTreeModel;
|
class QgsLayerTreeModel;
|
||||||
class QgsSymbol;
|
class QgsSymbol;
|
||||||
class QgsLayoutItemMap;
|
class QgsLayoutItemMap;
|
||||||
class QgsLegendRenderer;
|
class QgsLegendRenderer;
|
||||||
|
class QgsLayoutItemLegend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \ingroup core
|
* \ingroup core
|
||||||
@ -44,11 +46,48 @@ class CORE_EXPORT QgsLegendModel : public QgsLayerTreeModel
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
//! Construct the model based on the given layer tree
|
//! 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;
|
QVariant data( const QModelIndex &index, int role ) const override;
|
||||||
|
|
||||||
Qt::ItemFlags flags( const QModelIndex &index ) 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;
|
QgsExpressionContext createExpressionContext() const override;
|
||||||
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
void refresh() override;
|
void refresh() override;
|
||||||
@ -486,6 +524,7 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
|
|||||||
|
|
||||||
void nodeCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key );
|
void nodeCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key );
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QgsLayoutItemLegend() = delete;
|
QgsLayoutItemLegend() = delete;
|
||||||
|
|
||||||
|
@ -2243,3 +2243,4 @@ QgsRectangle QgsLayoutItemMap::computeAtlasRectangle()
|
|||||||
return g.boundingBox();
|
return g.boundingBox();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ class QTextCodec;
|
|||||||
#include "qgsrelation.h"
|
#include "qgsrelation.h"
|
||||||
#include "qgsfeaturesink.h"
|
#include "qgsfeaturesink.h"
|
||||||
#include "qgsfeaturesource.h"
|
#include "qgsfeaturesource.h"
|
||||||
|
#include "qgsfeaturerequest.h"
|
||||||
|
|
||||||
typedef QList<int> QgsAttributeList SIP_SKIP;
|
typedef QList<int> QgsAttributeList SIP_SKIP;
|
||||||
typedef QSet<int> QgsAttributeIds SIP_SKIP;
|
typedef QSet<int> QgsAttributeIds SIP_SKIP;
|
||||||
@ -43,7 +44,6 @@ class QgsFeedback;
|
|||||||
class QgsFeatureRenderer;
|
class QgsFeatureRenderer;
|
||||||
class QgsAbstractVectorLayerLabeling;
|
class QgsAbstractVectorLayerLabeling;
|
||||||
|
|
||||||
#include "qgsfeaturerequest.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \ingroup core
|
* \ingroup core
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
|
||||||
#include "qgis_sip.h"
|
#include "qgis.h"
|
||||||
#include "qgsmaplayer.h"
|
#include "qgsmaplayer.h"
|
||||||
#include "qgsfeature.h"
|
#include "qgsfeature.h"
|
||||||
#include "qgsfeaturerequest.h"
|
#include "qgsfeaturerequest.h"
|
||||||
@ -41,6 +41,7 @@
|
|||||||
#include "qgsfeatureiterator.h"
|
#include "qgsfeatureiterator.h"
|
||||||
#include "qgsexpressioncontextgenerator.h"
|
#include "qgsexpressioncontextgenerator.h"
|
||||||
#include "qgsexpressioncontextscopegenerator.h"
|
#include "qgsexpressioncontextscopegenerator.h"
|
||||||
|
#include "qgsexpressioncontext.h"
|
||||||
|
|
||||||
class QPainter;
|
class QPainter;
|
||||||
class QImage;
|
class QImage;
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
#include "qgsvectorlayerfeaturecounter.h"
|
#include "qgsvectorlayerfeaturecounter.h"
|
||||||
#include "qgsvectorlayer.h"
|
#include "qgsvectorlayer.h"
|
||||||
|
#include "qgsfeatureid.h"
|
||||||
|
|
||||||
QgsVectorLayerFeatureCounter::QgsVectorLayerFeatureCounter( QgsVectorLayer *layer, const QgsExpressionContext &context )
|
QgsVectorLayerFeatureCounter::QgsVectorLayerFeatureCounter( QgsVectorLayer *layer, const QgsExpressionContext &context )
|
||||||
: QgsTask( tr( "Counting features in %1" ).arg( layer->name() ), QgsTask::CanCancel )
|
: QgsTask( tr( "Counting features in %1" ).arg( layer->name() ), QgsTask::CanCancel )
|
||||||
@ -31,12 +32,15 @@ QgsVectorLayerFeatureCounter::QgsVectorLayerFeatureCounter( QgsVectorLayer *laye
|
|||||||
|
|
||||||
bool QgsVectorLayerFeatureCounter::run()
|
bool QgsVectorLayerFeatureCounter::run()
|
||||||
{
|
{
|
||||||
|
mSymbolFeatureCountMap.clear();
|
||||||
|
mSymbolFeatureIdMap.clear();
|
||||||
QgsLegendSymbolList symbolList = mRenderer->legendSymbolItems();
|
QgsLegendSymbolList symbolList = mRenderer->legendSymbolItems();
|
||||||
QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin();
|
QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin();
|
||||||
|
|
||||||
for ( ; symbolIt != symbolList.constEnd(); ++symbolIt )
|
for ( ; symbolIt != symbolList.constEnd(); ++symbolIt )
|
||||||
{
|
{
|
||||||
mSymbolFeatureCountMap.insert( symbolIt->label(), 0 );
|
mSymbolFeatureCountMap.insert( symbolIt->label(), 0 );
|
||||||
|
mSymbolFeatureIdMap.insert( symbolIt->label(), QgsFeatureIds() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no features to be counted, we can spare us the trouble
|
// 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 ) )
|
while ( fit.nextFeature( f ) )
|
||||||
{
|
{
|
||||||
renderContext.expressionContext().setFeature( f );
|
renderContext.expressionContext().setFeature( f );
|
||||||
QSet<QString> featureKeyList = mRenderer->legendKeysForFeature( f, renderContext );
|
|
||||||
const auto constFeatureKeyList = featureKeyList;
|
const QSet<QString> featureKeyList = mRenderer->legendKeysForFeature( f, renderContext );
|
||||||
for ( const QString &key : constFeatureKeyList )
|
for ( const QString &key : featureKeyList )
|
||||||
{
|
{
|
||||||
mSymbolFeatureCountMap[key] += 1;
|
mSymbolFeatureCountMap[key] += 1;
|
||||||
|
mSymbolFeatureIdMap[key].insert( f.id() );
|
||||||
}
|
}
|
||||||
++featuresCounted;
|
++featuresCounted;
|
||||||
|
|
||||||
@ -88,9 +93,7 @@ bool QgsVectorLayerFeatureCounter::run()
|
|||||||
}
|
}
|
||||||
mRenderer->stopRender( renderContext );
|
mRenderer->stopRender( renderContext );
|
||||||
}
|
}
|
||||||
|
|
||||||
setProgress( 100 );
|
setProgress( 100 );
|
||||||
|
|
||||||
emit symbolsCounted();
|
emit symbolsCounted();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -104,3 +107,13 @@ long QgsVectorLayerFeatureCounter::featureCount( const QString &legendKey ) cons
|
|||||||
{
|
{
|
||||||
return mSymbolFeatureCountMap.value( legendKey, -1 );
|
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 "qgsvectorlayerfeatureiterator.h"
|
||||||
#include "qgsrenderer.h"
|
#include "qgsrenderer.h"
|
||||||
#include "qgstaskmanager.h"
|
#include "qgstaskmanager.h"
|
||||||
|
#include "qgsfeatureid.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \ingroup core
|
* \ingroup core
|
||||||
@ -40,10 +41,14 @@ class CORE_EXPORT QgsVectorLayerFeatureCounter : public QgsTask
|
|||||||
*/
|
*/
|
||||||
QgsVectorLayerFeatureCounter( QgsVectorLayer *layer, const QgsExpressionContext &context = QgsExpressionContext() );
|
QgsVectorLayerFeatureCounter( QgsVectorLayer *layer, const QgsExpressionContext &context = QgsExpressionContext() );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the feature count and Ids per symbol
|
||||||
|
*/
|
||||||
bool run() override;
|
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.
|
* signal has been emitted.
|
||||||
*
|
*
|
||||||
* \note Not available in Python bindings.
|
* \note Not available in Python bindings.
|
||||||
@ -51,11 +56,29 @@ class CORE_EXPORT QgsVectorLayerFeatureCounter : public QgsTask
|
|||||||
QHash<QString, long> symbolFeatureCountMap() const SIP_SKIP;
|
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.
|
* If the key has not been found, -1 will be returned.
|
||||||
*/
|
*/
|
||||||
long featureCount( const QString &legendKey ) const;
|
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:
|
signals:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,6 +91,7 @@ class CORE_EXPORT QgsVectorLayerFeatureCounter : public QgsTask
|
|||||||
std::unique_ptr<QgsFeatureRenderer> mRenderer;
|
std::unique_ptr<QgsFeatureRenderer> mRenderer;
|
||||||
QgsExpressionContext mExpressionContext;
|
QgsExpressionContext mExpressionContext;
|
||||||
QHash<QString, long> mSymbolFeatureCountMap;
|
QHash<QString, long> mSymbolFeatureCountMap;
|
||||||
|
QHash<QString, QgsFeatureIds> mSymbolFeatureIdMap;
|
||||||
int mFeatureCount;
|
int mFeatureCount;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -401,6 +401,23 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<spacer name="horizontalSpacer_4">
|
<spacer name="horizontalSpacer_4">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
@ -32,14 +32,15 @@ from qgis.core import (QgsPrintLayout,
|
|||||||
QgsExpression,
|
QgsExpression,
|
||||||
QgsMapLayerLegendUtils,
|
QgsMapLayerLegendUtils,
|
||||||
QgsLegendStyle,
|
QgsLegendStyle,
|
||||||
QgsFontUtils)
|
QgsFontUtils,
|
||||||
|
QgsApplication)
|
||||||
from qgis.testing import (start_app,
|
from qgis.testing import (start_app,
|
||||||
unittest
|
unittest
|
||||||
)
|
)
|
||||||
from utilities import unitTestDataPath
|
from utilities import unitTestDataPath
|
||||||
from qgslayoutchecker import QgsLayoutChecker
|
from qgslayoutchecker import QgsLayoutChecker
|
||||||
import os
|
import os
|
||||||
|
from time import sleep
|
||||||
from test_qgslayoutitem import LayoutItemTestCase
|
from test_qgslayoutitem import LayoutItemTestCase
|
||||||
|
|
||||||
start_app()
|
start_app()
|
||||||
@ -66,6 +67,7 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
|||||||
|
|
||||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||||
|
QgsProject.instance().clear()
|
||||||
QgsProject.instance().addMapLayers([point_layer])
|
QgsProject.instance().addMapLayers([point_layer])
|
||||||
|
|
||||||
marker_symbol = QgsMarkerSymbol.createSimple({'color': '#ff0000', 'outline_style': 'no', 'size': '5', 'size_unit': 'MapUnit'})
|
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):
|
def testResizeWithMapContent(self):
|
||||||
"""Test test legend resizes to match map content"""
|
"""Test test legend resizes to match map content"""
|
||||||
|
|
||||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||||
QgsProject.instance().addMapLayers([point_layer])
|
QgsProject.instance().addMapLayers([point_layer])
|
||||||
@ -164,7 +165,6 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
|||||||
|
|
||||||
def testResizeDisabled(self):
|
def testResizeDisabled(self):
|
||||||
"""Test that test legend does not resize if auto size is disabled"""
|
"""Test that test legend does not resize if auto size is disabled"""
|
||||||
|
|
||||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||||
QgsProject.instance().addMapLayers([point_layer])
|
QgsProject.instance().addMapLayers([point_layer])
|
||||||
@ -209,7 +209,6 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
|||||||
|
|
||||||
def testResizeDisabledCrop(self):
|
def testResizeDisabledCrop(self):
|
||||||
"""Test that if legend resizing is disabled, and legend is too small, then content is cropped"""
|
"""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_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||||
QgsProject.instance().addMapLayers([point_layer])
|
QgsProject.instance().addMapLayers([point_layer])
|
||||||
@ -322,10 +321,8 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
|||||||
|
|
||||||
def testExpressionInText(self):
|
def testExpressionInText(self):
|
||||||
"""Test expressions embedded in legend node text"""
|
"""Test expressions embedded in legend node text"""
|
||||||
|
|
||||||
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
|
||||||
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
|
||||||
|
|
||||||
layout = QgsPrintLayout(QgsProject.instance())
|
layout = QgsPrintLayout(QgsProject.instance())
|
||||||
layout.setName('LAYOUT')
|
layout.setName('LAYOUT')
|
||||||
layout.initializeDefaults()
|
layout.initializeDefaults()
|
||||||
@ -378,6 +375,121 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
|
|||||||
|
|
||||||
QgsProject.instance().removeMapLayers([point_layer.id()])
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
BIN
tests/testdata/control_images/composer_legend/expected_composer_legend_symbol_expression/expected_composer_legend_symbol_expression.png
vendored
Normal file
BIN
tests/testdata/control_images/composer_legend/expected_composer_legend_symbol_expression/expected_composer_legend_symbol_expression.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 27 KiB |
Loading…
x
Reference in New Issue
Block a user