Skip to content

Commit 5e4c14c

Browse files
committed
[FEATURE] Control over label rendering order
A new control for setting a label's "z-index" has been added to the labeling properties dialog. This control (which also accepts data-defined overrides for individual features) determines the order in which label are rendered. Label layers with a higher z-index are rendered on top of labels from a layer with lower z-index. Additionally, the logic has been tweaks so that if 2 labels have matching z-indexes, then: - if they are from the same layer, a smaller label will be drawn above a larger label - if they are from different layers, the labels will be drawn in the same order as the layers themselves (ie respecting the order set in the legend) Diagrams can also have their z-index set (but not data defined) so that the order of labels and diagrams can be controlled. Note that this does *NOT* allow labels to be drawn below the features from other layers, it just controls the order in which labels are drawn on top of all the layer's features. Fix #13888, #13559
1 parent 1340afd commit 5e4c14c

30 files changed

+926
-237
lines changed

python/core/qgsdiagramrendererv2.sip

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ struct QgsDiagramLayerSettings
3131
Placement placement;
3232
unsigned int placementFlags;
3333
int priority; // 0 = low, 10 = high
34+
35+
//! Z-index of diagrams, where diagrams with a higher z-index are drawn on top of diagrams with a lower z-index
36+
double zIndex;
37+
3438
bool obstacle; // whether it's an obstacle
3539
double dist; // distance from the feature (in mm)
3640
QgsDiagramRendererV2* renderer; // if any renderer is assigned, it is owned by this class

python/core/qgspallabeling.sip

+4
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ class QgsPalLayerSettings
314314
FontMaxPixel,
315315
IsObstacle,
316316
ObstacleFactor,
317+
ZIndex,
317318

318319
// (data defined only)
319320
Show,
@@ -494,6 +495,9 @@ class QgsPalLayerSettings
494495
*/
495496
ObstacleType obstacleType;
496497

498+
//! Z-Index of label, where labels with a higher z-index are rendered on top of labels with a lower z-index
499+
double zIndex;
500+
497501
//-- scale factors
498502
double vectorScaleFactor; //scale factor painter units->pixels
499503
double rasterCompressFactor; //pixel resolution scale factor

src/app/qgsdiagramproperties.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ QgsDiagramProperties::QgsDiagramProperties( QgsVectorLayer* layer, QWidget* pare
368368
{
369369
mDiagramDistanceSpinBox->setValue( dls->dist );
370370
mPrioritySlider->setValue( dls->priority );
371+
mZIndexSpinBox->setValue( dls->zIndex );
371372
mDataDefinedXComboBox->setCurrentIndex( mDataDefinedXComboBox->findData( dls->xPosColumn ) );
372373
mDataDefinedYComboBox->setCurrentIndex( mDataDefinedYComboBox->findData( dls->yPosColumn ) );
373374
if ( dls->xPosColumn != -1 || dls->yPosColumn != -1 )
@@ -763,6 +764,7 @@ void QgsDiagramProperties::apply()
763764
QgsDiagramLayerSettings dls;
764765
dls.dist = mDiagramDistanceSpinBox->value();
765766
dls.priority = mPrioritySlider->value();
767+
dls.zIndex = mZIndexSpinBox->value();
766768
dls.showAll = mShowAllCheckBox->isChecked();
767769
if ( mDataDefinedPositionGroupBox->isChecked() )
768770
{

src/app/qgslabelinggui.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,8 @@ void QgsLabelingGui::init()
442442
mFontSizeUnitWidget->setUnit( lyr.fontSizeInMapUnits ? 1 : 0 );
443443
mFontSizeUnitWidget->setMapUnitScale( lyr.fontSizeMapUnitScale );
444444

445+
mZIndexSpinBox->setValue( lyr.zIndex );
446+
445447
mRefFont = lyr.textFont;
446448
mFontSizeSpinBox->setValue( lyr.textFont.pointSizeF() );
447449
btnTextColor->setColor( lyr.textColor );
@@ -786,6 +788,8 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
786788
lyr.multilineAlign = ( QgsPalLayerSettings::MultiLineAlign ) mFontMultiLineAlignComboBox->currentIndex();
787789
lyr.preserveRotation = chkPreserveRotation->isChecked();
788790

791+
lyr.zIndex = mZIndexSpinBox->value();
792+
789793
// data defined labeling
790794
// text style
791795
setDataDefinedProperty( mFontDDBtn, QgsPalLayerSettings::Family, lyr );
@@ -892,6 +896,7 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
892896
setDataDefinedProperty( mAlwaysShowDDBtn, QgsPalLayerSettings::AlwaysShow, lyr );
893897
setDataDefinedProperty( mIsObstacleDDBtn, QgsPalLayerSettings::IsObstacle, lyr );
894898
setDataDefinedProperty( mObstacleFactorDDBtn, QgsPalLayerSettings::ObstacleFactor, lyr );
899+
setDataDefinedProperty( mZIndexDDBtn, QgsPalLayerSettings::ZIndex, lyr );
895900

896901
return lyr;
897902
}
@@ -1168,6 +1173,8 @@ void QgsLabelingGui::populateDataDefinedButtons( QgsPalLayerSettings& s )
11681173
QgsDataDefinedButton::AnyType, QgsDataDefinedButton::boolDesc() );
11691174
mObstacleFactorDDBtn->init( mLayer, s.dataDefinedProperty( QgsPalLayerSettings::ObstacleFactor ),
11701175
QgsDataDefinedButton::AnyType, tr( "double [0.0-10.0]" ) );
1176+
mZIndexDDBtn->init( mLayer, s.dataDefinedProperty( QgsPalLayerSettings::ZIndex ),
1177+
QgsDataDefinedButton::AnyType, QgsDataDefinedButton::doubleDesc() );
11711178
}
11721179

11731180
void QgsLabelingGui::changeTextColor( const QColor &color )

src/core/qgsdiagramrendererv2.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ QgsDiagramLayerSettings::QgsDiagramLayerSettings()
2828
: placement( AroundPoint )
2929
, placementFlags( OnLine )
3030
, priority( 5 )
31+
, zIndex( 0.0 )
3132
, obstacle( false )
3233
, dist( 0.0 )
3334
, renderer( nullptr )
@@ -51,6 +52,7 @@ void QgsDiagramLayerSettings::readXML( const QDomElement& elem, const QgsVectorL
5152
placement = static_cast< Placement >( elem.attribute( "placement" ).toInt() );
5253
placementFlags = static_cast< LinePlacementFlags >( elem.attribute( "linePlacementFlags" ).toInt() );
5354
priority = elem.attribute( "priority" ).toInt();
55+
zIndex = elem.attribute( "zIndex" ).toDouble();
5456
obstacle = elem.attribute( "obstacle" ).toInt();
5557
dist = elem.attribute( "dist" ).toDouble();
5658
xPosColumn = elem.attribute( "xPosColumn" ).toInt();
@@ -66,6 +68,7 @@ void QgsDiagramLayerSettings::writeXML( QDomElement& layerElem, QDomDocument& do
6668
diagramLayerElem.setAttribute( "placement", placement );
6769
diagramLayerElem.setAttribute( "linePlacementFlags", placementFlags );
6870
diagramLayerElem.setAttribute( "priority", priority );
71+
diagramLayerElem.setAttribute( "zIndex", zIndex );
6972
diagramLayerElem.setAttribute( "obstacle", obstacle );
7073
diagramLayerElem.setAttribute( "dist", QString::number( dist ) );
7174
diagramLayerElem.setAttribute( "xPosColumn", xPosColumn );

src/core/qgsdiagramrendererv2.h

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ class CORE_EXPORT QgsDiagramLayerSettings
6767
Placement placement;
6868
unsigned int placementFlags;
6969
int priority; // 0 = low, 10 = high
70+
71+
//! Z-index of diagrams, where diagrams with a higher z-index are drawn on top of diagrams with a lower z-index
72+
double zIndex;
73+
7074
bool obstacle; // whether it's an obstacle
7175
double dist; // distance from the feature (in mm)
7276
QgsDiagramRendererV2* renderer; // if any renderer is assigned, it is owned by this class

src/core/qgslabelfeature.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ QgsLabelFeature::QgsLabelFeature( QgsFeatureId id, GEOSGeometry* geometry, const
1010
, mObstacleGeometry( nullptr )
1111
, mSize( size )
1212
, mPriority( -1 )
13+
, mZIndex( 0 )
1314
, mHasFixedPosition( false )
1415
, mHasFixedAngle( false )
1516
, mFixedAngle( 0 )

src/core/qgslabelfeature.h

+17
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,21 @@ class CORE_EXPORT QgsLabelFeature
7878
*/
7979
void setPriority( double priority ) { mPriority = priority; }
8080

81+
/** Returns the label's z-index. Higher z-index labels are rendered on top of lower
82+
* z-index labels.
83+
* @see setZIndex()
84+
* @note added in QGIS 2.14
85+
*/
86+
double zIndex() const { return mZIndex; }
87+
88+
/** Sets the label's z-index. Higher z-index labels are rendered on top of lower
89+
* z-index labels.
90+
* @param zIndex z-index for label
91+
* @see zIndex()
92+
* @note added in QGIS 2.14
93+
*/
94+
void setZIndex( double zIndex ) { mZIndex = zIndex; }
95+
8196
//! Whether the label should use a fixed position instead of being automatically placed
8297
bool hasFixedPosition() const { return mHasFixedPosition; }
8398
//! Set whether the label should use a fixed position instead of being automatically placed
@@ -201,6 +216,8 @@ class CORE_EXPORT QgsLabelFeature
201216
QSizeF mSize;
202217
//! Priority of the label
203218
double mPriority;
219+
//! Z-index of label (higher z-index labels are rendered on top of lower z-index labels)
220+
double mZIndex;
204221
//! whether mFixedPosition should be respected
205222
bool mHasFixedPosition;
206223
//! fixed position for the label (instead of automatic placement)

src/core/qgslabelingenginev2.cpp

+37-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,38 @@ static bool _palIsCancelled( void* ctx )
3232
return ( reinterpret_cast< QgsRenderContext* >( ctx ) )->renderingStopped();
3333
}
3434

35+
// helper class for sorting labels into correct draw order
36+
class QgsLabelSorter
37+
{
38+
public:
39+
40+
QgsLabelSorter( const QgsMapSettings& mapSettings )
41+
: mMapSettings( mapSettings )
42+
{}
43+
44+
bool operator()( pal::LabelPosition* lp1, pal::LabelPosition* lp2 ) const
45+
{
46+
QgsLabelFeature* lf1 = lp1->getFeaturePart()->feature();
47+
QgsLabelFeature* lf2 = lp2->getFeaturePart()->feature();
48+
49+
if ( !qgsDoubleNear( lf1->zIndex(), lf2->zIndex() ) )
50+
return lf1->zIndex() < lf2->zIndex();
51+
52+
//equal z-index, so fallback to respecting layer render order
53+
int layer1Pos = mMapSettings.layers().indexOf( lf1->provider()->layerId() );
54+
int layer2Pos = mMapSettings.layers().indexOf( lf2->provider()->layerId() );
55+
if ( layer1Pos != layer2Pos && layer1Pos >= 0 && layer2Pos >= 0 )
56+
return layer1Pos > layer2Pos; //higher positions are rendered first
57+
58+
//same layer, so render larger labels first
59+
return lf1->size().width() * lf1->size().height() > lf2->size().width() * lf2->size().height();
60+
}
61+
62+
private:
63+
64+
const QgsMapSettings& mMapSettings;
65+
};
66+
3567

3668
QgsLabelingEngineV2::QgsLabelingEngineV2()
3769
: mFlags( RenderOutlineLabels | UsePartialCandidates )
@@ -268,6 +300,9 @@ void QgsLabelingEngineV2::run( QgsRenderContext& context )
268300
}
269301
painter->setRenderHint( QPainter::Antialiasing );
270302

303+
// sort labels
304+
qSort( labels->begin(), labels->end(), QgsLabelSorter( mMapSettings ) );
305+
271306
// draw the labels
272307
QList<pal::LabelPosition*>::iterator it = labels->begin();
273308
for ( ; it != labels->end(); ++it )
@@ -346,8 +381,9 @@ QgsAbstractLabelProvider*QgsLabelFeature::provider() const
346381

347382
}
348383

349-
QgsAbstractLabelProvider::QgsAbstractLabelProvider()
384+
QgsAbstractLabelProvider::QgsAbstractLabelProvider( const QString& layerId )
350385
: mEngine( nullptr )
386+
, mLayerId( layerId )
351387
, mFlags( DrawLabels )
352388
, mPlacement( QgsPalLayerSettings::AroundPoint )
353389
, mLinePlacementFlags( 0 )

src/core/qgslabelingenginev2.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class CORE_EXPORT QgsAbstractLabelProvider
4444

4545
public:
4646
//! Construct the provider with default values
47-
QgsAbstractLabelProvider();
47+
QgsAbstractLabelProvider( const QString& layerId = QString() );
4848
//! Vritual destructor
4949
virtual ~QgsAbstractLabelProvider() {}
5050

@@ -74,6 +74,9 @@ class CORE_EXPORT QgsAbstractLabelProvider
7474
//! Name of the layer (for statistics, debugging etc.) - does not need to be unique
7575
QString name() const { return mName; }
7676

77+
//! Returns ID of associated layer, or empty string if no layer is associated with the provider.
78+
QString layerId() const { return mLayerId; }
79+
7780
//! Flags associated with the provider
7881
Flags flags() const { return mFlags; }
7982

@@ -98,6 +101,8 @@ class CORE_EXPORT QgsAbstractLabelProvider
98101

99102
//! Name of the layer
100103
QString mName;
104+
//! Associated layer's ID, if applicable
105+
QString mLayerId;
101106
//! Flags altering drawing and registration of features
102107
Flags mFlags;
103108
//! Placement strategy

src/core/qgspallabeling.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ QgsPalLayerSettings::QgsPalLayerSettings()
202202
obstacle = true;
203203
obstacleFactor = 1.0;
204204
obstacleType = PolygonInterior;
205+
zIndex = 0.0;
205206

206207
// scale factors
207208
vectorScaleFactor = 1.0;
@@ -312,6 +313,7 @@ QgsPalLayerSettings::QgsPalLayerSettings()
312313
mDataDefinedNames.insert( FontLimitPixel, QPair<QString, int>( "FontLimitPixel", -1 ) );
313314
mDataDefinedNames.insert( FontMinPixel, QPair<QString, int>( "FontMinPixel", -1 ) );
314315
mDataDefinedNames.insert( FontMaxPixel, QPair<QString, int>( "FontMaxPixel", -1 ) );
316+
mDataDefinedNames.insert( ZIndex, QPair<QString, int>( "ZIndex", -1 ) );
315317
// (data defined only)
316318
mDataDefinedNames.insert( Show, QPair<QString, int>( "Show", 15 ) );
317319
mDataDefinedNames.insert( AlwaysShow, QPair<QString, int>( "AlwaysShow", 20 ) );
@@ -425,6 +427,7 @@ QgsPalLayerSettings& QgsPalLayerSettings::operator=( const QgsPalLayerSettings &
425427
obstacle = s.obstacle;
426428
obstacleFactor = s.obstacleFactor;
427429
obstacleType = s.obstacleType;
430+
zIndex = s.zIndex;
428431

429432
// shape background
430433
shapeDraw = s.shapeDraw;
@@ -971,6 +974,7 @@ void QgsPalLayerSettings::readFromLayer( QgsVectorLayer* layer )
971974
obstacle = layer->customProperty( "labeling/obstacle", QVariant( true ) ).toBool();
972975
obstacleFactor = layer->customProperty( "labeling/obstacleFactor", QVariant( 1.0 ) ).toDouble();
973976
obstacleType = static_cast< ObstacleType >( layer->customProperty( "labeling/obstacleType", QVariant( PolygonInterior ) ).toUInt() );
977+
zIndex = layer->customProperty( "labeling/zIndex", QVariant( 0.0 ) ).toDouble();
974978

975979
readDataDefinedPropertyMap( layer, nullptr, dataDefinedProperties );
976980
}
@@ -1124,6 +1128,7 @@ void QgsPalLayerSettings::writeToLayer( QgsVectorLayer* layer )
11241128
layer->setCustomProperty( "labeling/obstacle", obstacle );
11251129
layer->setCustomProperty( "labeling/obstacleFactor", obstacleFactor );
11261130
layer->setCustomProperty( "labeling/obstacleType", static_cast< unsigned int >( obstacleType ) );
1131+
layer->setCustomProperty( "labeling/zIndex", zIndex );
11271132

11281133
writeDataDefinedPropertyMap( layer, nullptr, dataDefinedProperties );
11291134
}
@@ -1324,6 +1329,7 @@ void QgsPalLayerSettings::readXml( QDomElement& elem )
13241329
obstacle = renderingElem.attribute( "obstacle", "1" ).toInt();
13251330
obstacleFactor = renderingElem.attribute( "obstacleFactor", "1" ).toDouble();
13261331
obstacleType = static_cast< ObstacleType >( renderingElem.attribute( "obstacleType", QString::number( PolygonInterior ) ).toUInt() );
1332+
zIndex = renderingElem.attribute( "zIndex", "0.0" ).toDouble();
13271333

13281334
QDomElement ddElem = elem.firstChildElement( "data-defined" );
13291335
readDataDefinedPropertyMap( nullptr, &ddElem, dataDefinedProperties );
@@ -1483,6 +1489,7 @@ QDomElement QgsPalLayerSettings::writeXml( QDomDocument& doc )
14831489
renderingElem.setAttribute( "obstacle", obstacle );
14841490
renderingElem.setAttribute( "obstacleFactor", obstacleFactor );
14851491
renderingElem.setAttribute( "obstacleType", static_cast< unsigned int >( obstacleType ) );
1492+
renderingElem.setAttribute( "zIndex", zIndex );
14861493

14871494
QDomElement ddElem = doc.createElement( "data-defined" );
14881495
writeDataDefinedPropertyMap( nullptr, &ddElem, dataDefinedProperties );
@@ -2691,6 +2698,19 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
26912698
( *labelFeature )->setHasFixedQuadrant( true );
26922699
}
26932700

2701+
// data defined z-index?
2702+
double z = zIndex;
2703+
if ( dataDefinedEvaluate( QgsPalLayerSettings::ZIndex, exprVal, &context.expressionContext(), zIndex ) )
2704+
{
2705+
bool ok;
2706+
double zIndexD = exprVal.toDouble( &ok );
2707+
if ( ok )
2708+
{
2709+
z = zIndexD;
2710+
}
2711+
}
2712+
( *labelFeature )->setZIndex( z );
2713+
26942714
// data defined priority?
26952715
if ( dataDefinedEvaluate( QgsPalLayerSettings::Priority, exprVal, &context.expressionContext(), priority ) )
26962716
{

src/core/qgspallabeling.h

+4
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ class CORE_EXPORT QgsPalLayerSettings
301301
FontMaxPixel = 26,
302302
IsObstacle = 88,
303303
ObstacleFactor = 89,
304+
ZIndex = 90,
304305

305306
// (data defined only)
306307
Show = 15,
@@ -481,6 +482,9 @@ class CORE_EXPORT QgsPalLayerSettings
481482
*/
482483
ObstacleType obstacleType;
483484

485+
//! Z-Index of label, where labels with a higher z-index are rendered on top of labels with a lower z-index
486+
double zIndex;
487+
484488
//-- scale factors
485489
double vectorScaleFactor; //scale factor painter units->pixels
486490
double rasterCompressFactor; //pixel resolution scale factor

src/core/qgsvectorlayerdiagramprovider.cpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ QgsVectorLayerDiagramProvider::QgsVectorLayerDiagramProvider(
3232
const QgsCoordinateReferenceSystem& crs,
3333
QgsAbstractFeatureSource* source,
3434
bool ownsSource )
35-
: mSettings( *diagSettings )
35+
: QgsAbstractLabelProvider( layerId )
36+
, mSettings( *diagSettings )
3637
, mDiagRenderer( diagRenderer->clone() )
3738
, mLayerId( layerId )
3839
, mFields( fields )
@@ -45,7 +46,8 @@ QgsVectorLayerDiagramProvider::QgsVectorLayerDiagramProvider(
4546

4647

4748
QgsVectorLayerDiagramProvider::QgsVectorLayerDiagramProvider( QgsVectorLayer* layer, bool ownFeatureLoop )
48-
: mSettings( *layer->diagramLayerSettings() )
49+
: QgsAbstractLabelProvider( layer->id() )
50+
, mSettings( *layer->diagramLayerSettings() )
4951
, mDiagRenderer( layer->diagramRenderer()->clone() )
5052
, mLayerId( layer->id() )
5153
, mFields( layer->fields() )
@@ -352,6 +354,7 @@ QgsLabelFeature* QgsVectorLayerDiagramProvider::registerDiagram( QgsFeature& fea
352354
lf->setFixedAngle( 0 );
353355
lf->setAlwaysShow( alwaysShow );
354356
lf->setIsObstacle( mSettings.obstacle );
357+
lf->setZIndex( mSettings.zIndex );
355358
if ( geosObstacleGeomClone )
356359
{
357360
lf->setObstacleGeometry( geosObstacleGeomClone );

src/core/qgsvectorlayerlabelprovider.cpp

+5-4
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ static void _fixQPictureDPI( QPainter* p )
5050

5151

5252
QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( QgsVectorLayer* layer, bool withFeatureLoop, const QgsPalLayerSettings* settings, const QString& layerName )
53-
: mSettings( settings ? *settings : QgsPalLayerSettings::fromLayer( layer ) )
54-
, mLayerId( layer->id() )
53+
: QgsAbstractLabelProvider( layer->id() )
54+
, mSettings( settings ? *settings : QgsPalLayerSettings::fromLayer( layer ) )
5555
, mLayerGeometryType( layer->geometryType() )
5656
, mRenderer( layer->rendererV2() )
5757
, mFields( layer->fields() )
@@ -79,8 +79,8 @@ QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( const QgsPalLayerSetti
7979
const QgsCoordinateReferenceSystem& crs,
8080
QgsAbstractFeatureSource* source,
8181
bool ownsSource, QgsFeatureRendererV2* renderer )
82-
: mSettings( settings )
83-
, mLayerId( layerId )
82+
: QgsAbstractLabelProvider( layerId )
83+
, mSettings( settings )
8484
, mLayerGeometryType( QGis::UnknownGeometry )
8585
, mRenderer( renderer )
8686
, mFields( fields )
@@ -290,6 +290,7 @@ QList<QgsLabelFeature*> QgsVectorLayerLabelProvider::labelFeatures( QgsRenderCon
290290
//point feature, use symbol bounds as obstacle
291291
obstacleGeometry.reset( getPointObstacleGeometry( fet, ctx, mRenderer ) );
292292
}
293+
ctx.expressionContext().setFeature( fet );
293294
registerFeature( fet, ctx, obstacleGeometry.data() );
294295
}
295296

0 commit comments

Comments
 (0)