Skip to content

Commit

Permalink
[FEATURE] Control over label rendering order
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nyalldawson committed Jan 5, 2016
1 parent 1340afd commit 5e4c14c
Show file tree
Hide file tree
Showing 30 changed files with 926 additions and 237 deletions.
4 changes: 4 additions & 0 deletions python/core/qgsdiagramrendererv2.sip
Expand Up @@ -31,6 +31,10 @@ struct QgsDiagramLayerSettings
Placement placement;
unsigned int placementFlags;
int priority; // 0 = low, 10 = high

//! Z-index of diagrams, where diagrams with a higher z-index are drawn on top of diagrams with a lower z-index
double zIndex;

bool obstacle; // whether it's an obstacle
double dist; // distance from the feature (in mm)
QgsDiagramRendererV2* renderer; // if any renderer is assigned, it is owned by this class
Expand Down
4 changes: 4 additions & 0 deletions python/core/qgspallabeling.sip
Expand Up @@ -314,6 +314,7 @@ class QgsPalLayerSettings
FontMaxPixel,
IsObstacle,
ObstacleFactor,
ZIndex,

// (data defined only)
Show,
Expand Down Expand Up @@ -494,6 +495,9 @@ class QgsPalLayerSettings
*/
ObstacleType obstacleType;

//! Z-Index of label, where labels with a higher z-index are rendered on top of labels with a lower z-index
double zIndex;

//-- scale factors
double vectorScaleFactor; //scale factor painter units->pixels
double rasterCompressFactor; //pixel resolution scale factor
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgsdiagramproperties.cpp
Expand Up @@ -368,6 +368,7 @@ QgsDiagramProperties::QgsDiagramProperties( QgsVectorLayer* layer, QWidget* pare
{
mDiagramDistanceSpinBox->setValue( dls->dist );
mPrioritySlider->setValue( dls->priority );
mZIndexSpinBox->setValue( dls->zIndex );
mDataDefinedXComboBox->setCurrentIndex( mDataDefinedXComboBox->findData( dls->xPosColumn ) );
mDataDefinedYComboBox->setCurrentIndex( mDataDefinedYComboBox->findData( dls->yPosColumn ) );
if ( dls->xPosColumn != -1 || dls->yPosColumn != -1 )
Expand Down Expand Up @@ -763,6 +764,7 @@ void QgsDiagramProperties::apply()
QgsDiagramLayerSettings dls;
dls.dist = mDiagramDistanceSpinBox->value();
dls.priority = mPrioritySlider->value();
dls.zIndex = mZIndexSpinBox->value();
dls.showAll = mShowAllCheckBox->isChecked();
if ( mDataDefinedPositionGroupBox->isChecked() )
{
Expand Down
7 changes: 7 additions & 0 deletions src/app/qgslabelinggui.cpp
Expand Up @@ -442,6 +442,8 @@ void QgsLabelingGui::init()
mFontSizeUnitWidget->setUnit( lyr.fontSizeInMapUnits ? 1 : 0 );
mFontSizeUnitWidget->setMapUnitScale( lyr.fontSizeMapUnitScale );

mZIndexSpinBox->setValue( lyr.zIndex );

mRefFont = lyr.textFont;
mFontSizeSpinBox->setValue( lyr.textFont.pointSizeF() );
btnTextColor->setColor( lyr.textColor );
Expand Down Expand Up @@ -786,6 +788,8 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
lyr.multilineAlign = ( QgsPalLayerSettings::MultiLineAlign ) mFontMultiLineAlignComboBox->currentIndex();
lyr.preserveRotation = chkPreserveRotation->isChecked();

lyr.zIndex = mZIndexSpinBox->value();

// data defined labeling
// text style
setDataDefinedProperty( mFontDDBtn, QgsPalLayerSettings::Family, lyr );
Expand Down Expand Up @@ -892,6 +896,7 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
setDataDefinedProperty( mAlwaysShowDDBtn, QgsPalLayerSettings::AlwaysShow, lyr );
setDataDefinedProperty( mIsObstacleDDBtn, QgsPalLayerSettings::IsObstacle, lyr );
setDataDefinedProperty( mObstacleFactorDDBtn, QgsPalLayerSettings::ObstacleFactor, lyr );
setDataDefinedProperty( mZIndexDDBtn, QgsPalLayerSettings::ZIndex, lyr );

return lyr;
}
Expand Down Expand Up @@ -1168,6 +1173,8 @@ void QgsLabelingGui::populateDataDefinedButtons( QgsPalLayerSettings& s )
QgsDataDefinedButton::AnyType, QgsDataDefinedButton::boolDesc() );
mObstacleFactorDDBtn->init( mLayer, s.dataDefinedProperty( QgsPalLayerSettings::ObstacleFactor ),
QgsDataDefinedButton::AnyType, tr( "double [0.0-10.0]" ) );
mZIndexDDBtn->init( mLayer, s.dataDefinedProperty( QgsPalLayerSettings::ZIndex ),
QgsDataDefinedButton::AnyType, QgsDataDefinedButton::doubleDesc() );
}

void QgsLabelingGui::changeTextColor( const QColor &color )
Expand Down
3 changes: 3 additions & 0 deletions src/core/qgsdiagramrendererv2.cpp
Expand Up @@ -28,6 +28,7 @@ QgsDiagramLayerSettings::QgsDiagramLayerSettings()
: placement( AroundPoint )
, placementFlags( OnLine )
, priority( 5 )
, zIndex( 0.0 )
, obstacle( false )
, dist( 0.0 )
, renderer( nullptr )
Expand All @@ -51,6 +52,7 @@ void QgsDiagramLayerSettings::readXML( const QDomElement& elem, const QgsVectorL
placement = static_cast< Placement >( elem.attribute( "placement" ).toInt() );
placementFlags = static_cast< LinePlacementFlags >( elem.attribute( "linePlacementFlags" ).toInt() );
priority = elem.attribute( "priority" ).toInt();
zIndex = elem.attribute( "zIndex" ).toDouble();
obstacle = elem.attribute( "obstacle" ).toInt();
dist = elem.attribute( "dist" ).toDouble();
xPosColumn = elem.attribute( "xPosColumn" ).toInt();
Expand All @@ -66,6 +68,7 @@ void QgsDiagramLayerSettings::writeXML( QDomElement& layerElem, QDomDocument& do
diagramLayerElem.setAttribute( "placement", placement );
diagramLayerElem.setAttribute( "linePlacementFlags", placementFlags );
diagramLayerElem.setAttribute( "priority", priority );
diagramLayerElem.setAttribute( "zIndex", zIndex );
diagramLayerElem.setAttribute( "obstacle", obstacle );
diagramLayerElem.setAttribute( "dist", QString::number( dist ) );
diagramLayerElem.setAttribute( "xPosColumn", xPosColumn );
Expand Down
4 changes: 4 additions & 0 deletions src/core/qgsdiagramrendererv2.h
Expand Up @@ -67,6 +67,10 @@ class CORE_EXPORT QgsDiagramLayerSettings
Placement placement;
unsigned int placementFlags;
int priority; // 0 = low, 10 = high

//! Z-index of diagrams, where diagrams with a higher z-index are drawn on top of diagrams with a lower z-index
double zIndex;

bool obstacle; // whether it's an obstacle
double dist; // distance from the feature (in mm)
QgsDiagramRendererV2* renderer; // if any renderer is assigned, it is owned by this class
Expand Down
1 change: 1 addition & 0 deletions src/core/qgslabelfeature.cpp
Expand Up @@ -10,6 +10,7 @@ QgsLabelFeature::QgsLabelFeature( QgsFeatureId id, GEOSGeometry* geometry, const
, mObstacleGeometry( nullptr )
, mSize( size )
, mPriority( -1 )
, mZIndex( 0 )
, mHasFixedPosition( false )
, mHasFixedAngle( false )
, mFixedAngle( 0 )
Expand Down
17 changes: 17 additions & 0 deletions src/core/qgslabelfeature.h
Expand Up @@ -78,6 +78,21 @@ class CORE_EXPORT QgsLabelFeature
*/
void setPriority( double priority ) { mPriority = priority; }

/** Returns the label's z-index. Higher z-index labels are rendered on top of lower
* z-index labels.
* @see setZIndex()
* @note added in QGIS 2.14
*/
double zIndex() const { return mZIndex; }

/** Sets the label's z-index. Higher z-index labels are rendered on top of lower
* z-index labels.
* @param zIndex z-index for label
* @see zIndex()
* @note added in QGIS 2.14
*/
void setZIndex( double zIndex ) { mZIndex = zIndex; }

//! Whether the label should use a fixed position instead of being automatically placed
bool hasFixedPosition() const { return mHasFixedPosition; }
//! Set whether the label should use a fixed position instead of being automatically placed
Expand Down Expand Up @@ -201,6 +216,8 @@ class CORE_EXPORT QgsLabelFeature
QSizeF mSize;
//! Priority of the label
double mPriority;
//! Z-index of label (higher z-index labels are rendered on top of lower z-index labels)
double mZIndex;
//! whether mFixedPosition should be respected
bool mHasFixedPosition;
//! fixed position for the label (instead of automatic placement)
Expand Down
38 changes: 37 additions & 1 deletion src/core/qgslabelingenginev2.cpp
Expand Up @@ -32,6 +32,38 @@ static bool _palIsCancelled( void* ctx )
return ( reinterpret_cast< QgsRenderContext* >( ctx ) )->renderingStopped();
}

// helper class for sorting labels into correct draw order
class QgsLabelSorter
{
public:

QgsLabelSorter( const QgsMapSettings& mapSettings )
: mMapSettings( mapSettings )
{}

bool operator()( pal::LabelPosition* lp1, pal::LabelPosition* lp2 ) const
{
QgsLabelFeature* lf1 = lp1->getFeaturePart()->feature();
QgsLabelFeature* lf2 = lp2->getFeaturePart()->feature();

if ( !qgsDoubleNear( lf1->zIndex(), lf2->zIndex() ) )
return lf1->zIndex() < lf2->zIndex();

//equal z-index, so fallback to respecting layer render order
int layer1Pos = mMapSettings.layers().indexOf( lf1->provider()->layerId() );
int layer2Pos = mMapSettings.layers().indexOf( lf2->provider()->layerId() );
if ( layer1Pos != layer2Pos && layer1Pos >= 0 && layer2Pos >= 0 )
return layer1Pos > layer2Pos; //higher positions are rendered first

//same layer, so render larger labels first
return lf1->size().width() * lf1->size().height() > lf2->size().width() * lf2->size().height();
}

private:

const QgsMapSettings& mMapSettings;
};


QgsLabelingEngineV2::QgsLabelingEngineV2()
: mFlags( RenderOutlineLabels | UsePartialCandidates )
Expand Down Expand Up @@ -268,6 +300,9 @@ void QgsLabelingEngineV2::run( QgsRenderContext& context )
}
painter->setRenderHint( QPainter::Antialiasing );

// sort labels
qSort( labels->begin(), labels->end(), QgsLabelSorter( mMapSettings ) );

// draw the labels
QList<pal::LabelPosition*>::iterator it = labels->begin();
for ( ; it != labels->end(); ++it )
Expand Down Expand Up @@ -346,8 +381,9 @@ QgsAbstractLabelProvider*QgsLabelFeature::provider() const

}

QgsAbstractLabelProvider::QgsAbstractLabelProvider()
QgsAbstractLabelProvider::QgsAbstractLabelProvider( const QString& layerId )
: mEngine( nullptr )
, mLayerId( layerId )
, mFlags( DrawLabels )
, mPlacement( QgsPalLayerSettings::AroundPoint )
, mLinePlacementFlags( 0 )
Expand Down
7 changes: 6 additions & 1 deletion src/core/qgslabelingenginev2.h
Expand Up @@ -44,7 +44,7 @@ class CORE_EXPORT QgsAbstractLabelProvider

public:
//! Construct the provider with default values
QgsAbstractLabelProvider();
QgsAbstractLabelProvider( const QString& layerId = QString() );
//! Vritual destructor
virtual ~QgsAbstractLabelProvider() {}

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

//! Returns ID of associated layer, or empty string if no layer is associated with the provider.
QString layerId() const { return mLayerId; }

//! Flags associated with the provider
Flags flags() const { return mFlags; }

Expand All @@ -98,6 +101,8 @@ class CORE_EXPORT QgsAbstractLabelProvider

//! Name of the layer
QString mName;
//! Associated layer's ID, if applicable
QString mLayerId;
//! Flags altering drawing and registration of features
Flags mFlags;
//! Placement strategy
Expand Down
20 changes: 20 additions & 0 deletions src/core/qgspallabeling.cpp
Expand Up @@ -202,6 +202,7 @@ QgsPalLayerSettings::QgsPalLayerSettings()
obstacle = true;
obstacleFactor = 1.0;
obstacleType = PolygonInterior;
zIndex = 0.0;

// scale factors
vectorScaleFactor = 1.0;
Expand Down Expand Up @@ -312,6 +313,7 @@ QgsPalLayerSettings::QgsPalLayerSettings()
mDataDefinedNames.insert( FontLimitPixel, QPair<QString, int>( "FontLimitPixel", -1 ) );
mDataDefinedNames.insert( FontMinPixel, QPair<QString, int>( "FontMinPixel", -1 ) );
mDataDefinedNames.insert( FontMaxPixel, QPair<QString, int>( "FontMaxPixel", -1 ) );
mDataDefinedNames.insert( ZIndex, QPair<QString, int>( "ZIndex", -1 ) );
// (data defined only)
mDataDefinedNames.insert( Show, QPair<QString, int>( "Show", 15 ) );
mDataDefinedNames.insert( AlwaysShow, QPair<QString, int>( "AlwaysShow", 20 ) );
Expand Down Expand Up @@ -425,6 +427,7 @@ QgsPalLayerSettings& QgsPalLayerSettings::operator=( const QgsPalLayerSettings &
obstacle = s.obstacle;
obstacleFactor = s.obstacleFactor;
obstacleType = s.obstacleType;
zIndex = s.zIndex;

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

readDataDefinedPropertyMap( layer, nullptr, dataDefinedProperties );
}
Expand Down Expand Up @@ -1124,6 +1128,7 @@ void QgsPalLayerSettings::writeToLayer( QgsVectorLayer* layer )
layer->setCustomProperty( "labeling/obstacle", obstacle );
layer->setCustomProperty( "labeling/obstacleFactor", obstacleFactor );
layer->setCustomProperty( "labeling/obstacleType", static_cast< unsigned int >( obstacleType ) );
layer->setCustomProperty( "labeling/zIndex", zIndex );

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

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

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

// data defined z-index?
double z = zIndex;
if ( dataDefinedEvaluate( QgsPalLayerSettings::ZIndex, exprVal, &context.expressionContext(), zIndex ) )
{
bool ok;
double zIndexD = exprVal.toDouble( &ok );
if ( ok )
{
z = zIndexD;
}
}
( *labelFeature )->setZIndex( z );

// data defined priority?
if ( dataDefinedEvaluate( QgsPalLayerSettings::Priority, exprVal, &context.expressionContext(), priority ) )
{
Expand Down
4 changes: 4 additions & 0 deletions src/core/qgspallabeling.h
Expand Up @@ -301,6 +301,7 @@ class CORE_EXPORT QgsPalLayerSettings
FontMaxPixel = 26,
IsObstacle = 88,
ObstacleFactor = 89,
ZIndex = 90,

// (data defined only)
Show = 15,
Expand Down Expand Up @@ -481,6 +482,9 @@ class CORE_EXPORT QgsPalLayerSettings
*/
ObstacleType obstacleType;

//! Z-Index of label, where labels with a higher z-index are rendered on top of labels with a lower z-index
double zIndex;

//-- scale factors
double vectorScaleFactor; //scale factor painter units->pixels
double rasterCompressFactor; //pixel resolution scale factor
Expand Down
7 changes: 5 additions & 2 deletions src/core/qgsvectorlayerdiagramprovider.cpp
Expand Up @@ -32,7 +32,8 @@ QgsVectorLayerDiagramProvider::QgsVectorLayerDiagramProvider(
const QgsCoordinateReferenceSystem& crs,
QgsAbstractFeatureSource* source,
bool ownsSource )
: mSettings( *diagSettings )
: QgsAbstractLabelProvider( layerId )
, mSettings( *diagSettings )
, mDiagRenderer( diagRenderer->clone() )
, mLayerId( layerId )
, mFields( fields )
Expand All @@ -45,7 +46,8 @@ QgsVectorLayerDiagramProvider::QgsVectorLayerDiagramProvider(


QgsVectorLayerDiagramProvider::QgsVectorLayerDiagramProvider( QgsVectorLayer* layer, bool ownFeatureLoop )
: mSettings( *layer->diagramLayerSettings() )
: QgsAbstractLabelProvider( layer->id() )
, mSettings( *layer->diagramLayerSettings() )
, mDiagRenderer( layer->diagramRenderer()->clone() )
, mLayerId( layer->id() )
, mFields( layer->fields() )
Expand Down Expand Up @@ -352,6 +354,7 @@ QgsLabelFeature* QgsVectorLayerDiagramProvider::registerDiagram( QgsFeature& fea
lf->setFixedAngle( 0 );
lf->setAlwaysShow( alwaysShow );
lf->setIsObstacle( mSettings.obstacle );
lf->setZIndex( mSettings.zIndex );
if ( geosObstacleGeomClone )
{
lf->setObstacleGeometry( geosObstacleGeomClone );
Expand Down
9 changes: 5 additions & 4 deletions src/core/qgsvectorlayerlabelprovider.cpp
Expand Up @@ -50,8 +50,8 @@ static void _fixQPictureDPI( QPainter* p )


QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( QgsVectorLayer* layer, bool withFeatureLoop, const QgsPalLayerSettings* settings, const QString& layerName )
: mSettings( settings ? *settings : QgsPalLayerSettings::fromLayer( layer ) )
, mLayerId( layer->id() )
: QgsAbstractLabelProvider( layer->id() )
, mSettings( settings ? *settings : QgsPalLayerSettings::fromLayer( layer ) )
, mLayerGeometryType( layer->geometryType() )
, mRenderer( layer->rendererV2() )
, mFields( layer->fields() )
Expand Down Expand Up @@ -79,8 +79,8 @@ QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( const QgsPalLayerSetti
const QgsCoordinateReferenceSystem& crs,
QgsAbstractFeatureSource* source,
bool ownsSource, QgsFeatureRendererV2* renderer )
: mSettings( settings )
, mLayerId( layerId )
: QgsAbstractLabelProvider( layerId )
, mSettings( settings )
, mLayerGeometryType( QGis::UnknownGeometry )
, mRenderer( renderer )
, mFields( fields )
Expand Down Expand Up @@ -290,6 +290,7 @@ QList<QgsLabelFeature*> QgsVectorLayerLabelProvider::labelFeatures( QgsRenderCon
//point feature, use symbol bounds as obstacle
obstacleGeometry.reset( getPointObstacleGeometry( fet, ctx, mRenderer ) );
}
ctx.expressionContext().setFeature( fet );
registerFeature( fet, ctx, obstacleGeometry.data() );
}

Expand Down

0 comments on commit 5e4c14c

Please sign in to comment.