Skip to content
Permalink
Browse files

[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
  • Loading branch information
nyalldawson committed Jan 5, 2016
1 parent 1340afd commit 5e4c14cd402009037d8d82e8a6d75d495c24d55a
Showing with 926 additions and 237 deletions.
  1. +4 −0 python/core/qgsdiagramrendererv2.sip
  2. +4 −0 python/core/qgspallabeling.sip
  3. +2 −0 src/app/qgsdiagramproperties.cpp
  4. +7 −0 src/app/qgslabelinggui.cpp
  5. +3 −0 src/core/qgsdiagramrendererv2.cpp
  6. +4 −0 src/core/qgsdiagramrendererv2.h
  7. +1 −0 src/core/qgslabelfeature.cpp
  8. +17 −0 src/core/qgslabelfeature.h
  9. +37 −1 src/core/qgslabelingenginev2.cpp
  10. +6 −1 src/core/qgslabelingenginev2.h
  11. +20 −0 src/core/qgspallabeling.cpp
  12. +4 −0 src/core/qgspallabeling.h
  13. +5 −2 src/core/qgsvectorlayerdiagramprovider.cpp
  14. +5 −4 src/core/qgsvectorlayerlabelprovider.cpp
  15. +0 −2 src/core/qgsvectorlayerlabelprovider.h
  16. +179 −141 src/ui/qgsdiagrampropertiesbase.ui
  17. +512 −86 src/ui/qgslabelingguibase.ui
  18. +116 −0 tests/src/core/testqgslabelingenginev2.cpp
  19. BIN tests/testdata/control_images/expected_piediagram/expected_piediagram.png
  20. BIN tests/testdata/control_images/expected_piediagram_expression/expected_piediagram_expression.png
  21. BIN ...tdata/control_images/labelingenginev2/expected_label_order_layer1/expected_label_order_layer1.png
  22. BIN .../control_images/labelingenginev2/expected_label_order_layer1/expected_label_order_layer1_mask.png
  23. BIN ...tdata/control_images/labelingenginev2/expected_label_order_layer2/expected_label_order_layer2.png
  24. BIN .../control_images/labelingenginev2/expected_label_order_layer2/expected_label_order_layer2_mask.png
  25. BIN ...estdata/control_images/labelingenginev2/expected_label_order_mixed/expected_label_order_mixed.png
  26. BIN ...ta/control_images/labelingenginev2/expected_label_order_mixed/expected_label_order_mixed_mask.png
  27. BIN .../testdata/control_images/labelingenginev2/expected_label_order_size/expected_label_order_size.png
  28. BIN ...data/control_images/labelingenginev2/expected_label_order_size/expected_label_order_size_mask.png
  29. BIN ...tdata/control_images/labelingenginev2/expected_label_order_zindex/expected_label_order_zindex.png
  30. BIN .../control_images/labelingenginev2/expected_label_order_zindex/expected_label_order_zindex_mask.png
@@ -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
@@ -314,6 +314,7 @@ class QgsPalLayerSettings
FontMaxPixel,
IsObstacle,
ObstacleFactor,
ZIndex,

// (data defined only)
Show,
@@ -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
@@ -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 )
@@ -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() )
{
@@ -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 );
@@ -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 );
@@ -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;
}
@@ -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 )
@@ -28,6 +28,7 @@ QgsDiagramLayerSettings::QgsDiagramLayerSettings()
: placement( AroundPoint )
, placementFlags( OnLine )
, priority( 5 )
, zIndex( 0.0 )
, obstacle( false )
, dist( 0.0 )
, renderer( nullptr )
@@ -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();
@@ -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 );
@@ -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
@@ -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 )
@@ -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
@@ -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)
@@ -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 )
@@ -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 )
@@ -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 )
@@ -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() {}

@@ -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; }

@@ -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
@@ -202,6 +202,7 @@ QgsPalLayerSettings::QgsPalLayerSettings()
obstacle = true;
obstacleFactor = 1.0;
obstacleType = PolygonInterior;
zIndex = 0.0;

// scale factors
vectorScaleFactor = 1.0;
@@ -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 ) );
@@ -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;
@@ -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 );
}
@@ -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 );
}
@@ -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 );
@@ -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 );
@@ -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 ) )
{
@@ -301,6 +301,7 @@ class CORE_EXPORT QgsPalLayerSettings
FontMaxPixel = 26,
IsObstacle = 88,
ObstacleFactor = 89,
ZIndex = 90,

// (data defined only)
Show = 15,
@@ -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
@@ -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 )
@@ -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() )
@@ -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 );
@@ -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() )
@@ -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 )
@@ -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() );
}

0 comments on commit 5e4c14c

Please sign in to comment.
You can’t perform that action at this time.