[FEATURE][callouts] Add control over anchor point for callout on label

This gives users control over where a callout should join to the label
text. (Previously, you only had control over where the callout would
join to the corresponding feature geometry).

Choices include:
- Closest point (previous behavior)
- Label Centroid
- Fixed corners: Top left/top right/bottom left/bottom right/etc

Data defined control over the label anchor is also possible
This commit is contained in:
Nyall Dawson 2020-03-09 10:01:23 +10:00
parent 78bd73eb87
commit bcf9c828f9
17 changed files with 923 additions and 109 deletions

View File

@ -48,6 +48,7 @@ relevant symbology elements to render them.
OffsetFromLabel,
DrawCalloutToAllParts,
AnchorPointPosition,
LabelAnchorPointPosition,
};
enum DrawOrder
@ -64,6 +65,20 @@ relevant symbology elements to render them.
Centroid,
};
enum LabelAnchorPoint
{
LabelPointOnExterior,
LabelCentroid,
LabelTopLeft,
LabelTopMiddle,
LabelTopRight,
LabelMiddleLeft,
LabelMiddleRight,
LabelBottomLeft,
LabelBottomMiddle,
LabelBottomRight,
};
QgsCallout();
%Docstring
Constructor for QgsCallout.
@ -272,6 +287,51 @@ anchor point.
.. seealso:: :py:func:`encodeAnchorPoint`
%End
LabelAnchorPoint labelAnchorPoint() const;
%Docstring
Returns the label's anchor point position.
.. seealso:: :py:func:`setLabelAnchorPoint`
.. versionadded:: 3.14
%End
void setLabelAnchorPoint( LabelAnchorPoint anchor );
%Docstring
Sets the label's ``anchor`` point position.
.. seealso:: :py:func:`labelAnchorPoint`
.. versionadded:: 3.14
%End
static QString encodeLabelAnchorPoint( LabelAnchorPoint anchor );
%Docstring
Encodes a label ``anchor`` point to its string representation.
:return: encoded string
.. seealso:: :py:func:`decodeLabelAnchorPoint`
.. versionadded:: 3.14
%End
static QgsCallout::LabelAnchorPoint decodeLabelAnchorPoint( const QString &name, bool *ok = 0 );
%Docstring
Attempts to decode a string representation of a label anchor point name to the corresponding
anchor point.
:param name: encoded label anchor point name
:param ok: if specified, will be set to ``True`` if the anchor point was successfully decoded
:return: decoded name
.. seealso:: :py:func:`encodeLabelAnchorPoint`
.. versionadded:: 3.14
%End
protected:
virtual void draw( QgsRenderContext &context, QRectF bodyBoundingBox, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext ) = 0;
@ -292,6 +352,13 @@ Both ``rect`` and ``anchor`` are specified in painter coordinates (i.e. pixels).
The ``calloutContext`` argument is used to specify additional contextual information about
how a callout is being rendered.
%End
QgsGeometry labelAnchorGeometry( QRectF bodyBoundingBox, const double angle, LabelAnchorPoint anchor ) const;
%Docstring
Returns the anchor point geometry for a label with the given bounding box and ``anchor`` point mode.
.. versionadded:: 3.14
%End
};

View File

@ -39,6 +39,13 @@ void QgsCallout::initPropertyDefinitions()
{ QgsCallout::OffsetFromLabel, QgsPropertyDefinition( "OffsetFromLabel", QObject::tr( "Offset from label" ), QgsPropertyDefinition::DoublePositive, origin ) },
{ QgsCallout::DrawCalloutToAllParts, QgsPropertyDefinition( "DrawCalloutToAllParts", QObject::tr( "Draw lines to all feature parts" ), QgsPropertyDefinition::Boolean, origin ) },
{ QgsCallout::AnchorPointPosition, QgsPropertyDefinition( "AnchorPointPosition", QgsPropertyDefinition::DataTypeString, QObject::tr( "Feature's anchor point position" ), QObject::tr( "string " ) + "[<b>pole_of_inaccessibility</b>|<b>point_on_exterior</b>|<b>point_on_surface</b>|<b>centroid</b>]", origin ) },
{
QgsCallout::LabelAnchorPointPosition, QgsPropertyDefinition( "LabelAnchorPointPosition", QgsPropertyDefinition::DataTypeString, QObject::tr( "Label's anchor point position" ), QObject::tr( "string " ) + "[<b>point_on_exterior</b>|<b>centroid</b>|<b>TL</b>=Top left|<b>T</b>=Top middle|"
"<b>TR</b>=Top right|<br>"
"<b>L</b>=Left|<b>R</b>=Right|<br>"
"<b>BL</b>=Bottom left|<b>B</b>=Bottom middle|"
"<b>BR</b>=Bottom right]", origin )
},
};
}
@ -52,6 +59,7 @@ QVariantMap QgsCallout::properties( const QgsReadWriteContext & ) const
QVariantMap props;
props.insert( QStringLiteral( "enabled" ), mEnabled ? "1" : "0" );
props.insert( QStringLiteral( "anchorPoint" ), encodeAnchorPoint( mAnchorPoint ) );
props.insert( QStringLiteral( "labelAnchorPoint" ), encodeLabelAnchorPoint( mLabelAnchorPoint ) );
props.insert( QStringLiteral( "ddProperties" ), mDataDefinedProperties.toVariant( propertyDefinitions() ) );
return props;
}
@ -59,7 +67,8 @@ QVariantMap QgsCallout::properties( const QgsReadWriteContext & ) const
void QgsCallout::readProperties( const QVariantMap &props, const QgsReadWriteContext & )
{
mEnabled = props.value( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
mAnchorPoint = decodeAnchorPoint( props.value( QStringLiteral( "anchorPoint" ), QString( "" ) ).toString() );
mAnchorPoint = decodeAnchorPoint( props.value( QStringLiteral( "anchorPoint" ), QString() ).toString() );
mLabelAnchorPoint = decodeLabelAnchorPoint( props.value( QStringLiteral( "labelAnchorPoint" ), QString() ).toString() );
mDataDefinedProperties.loadVariant( props.value( QStringLiteral( "ddProperties" ) ), propertyDefinitions() );
}
@ -184,6 +193,96 @@ QString QgsCallout::encodeAnchorPoint( AnchorPoint anchor )
return QString();
}
QString QgsCallout::encodeLabelAnchorPoint( QgsCallout::LabelAnchorPoint anchor )
{
switch ( anchor )
{
case LabelPointOnExterior:
return QStringLiteral( "point_on_exterior" );
case LabelCentroid:
return QStringLiteral( "centroid" );
case LabelTopLeft:
return QStringLiteral( "tl" );
case LabelTopMiddle:
return QStringLiteral( "t" );
case LabelTopRight:
return QStringLiteral( "tr" );
case LabelMiddleLeft:
return QStringLiteral( "l" );
case LabelMiddleRight:
return QStringLiteral( "r" );
case LabelBottomLeft:
return QStringLiteral( "bl" );
case LabelBottomMiddle:
return QStringLiteral( "b" );
case LabelBottomRight:
return QStringLiteral( "br" );
}
return QString();
}
QgsCallout::LabelAnchorPoint QgsCallout::decodeLabelAnchorPoint( const QString &name, bool *ok )
{
if ( ok )
*ok = true;
QString cleaned = name.toLower().trimmed();
if ( cleaned == QLatin1String( "point_on_exterior" ) )
return LabelPointOnExterior;
else if ( cleaned == QLatin1String( "centroid" ) )
return LabelCentroid;
else if ( cleaned == QLatin1String( "tl" ) )
return LabelTopLeft;
else if ( cleaned == QLatin1String( "t" ) )
return LabelTopMiddle;
else if ( cleaned == QLatin1String( "tr" ) )
return LabelTopRight;
else if ( cleaned == QLatin1String( "l" ) )
return LabelMiddleLeft;
else if ( cleaned == QLatin1String( "r" ) )
return LabelMiddleRight;
else if ( cleaned == QLatin1String( "bl" ) )
return LabelBottomLeft;
else if ( cleaned == QLatin1String( "b" ) )
return LabelBottomMiddle;
else if ( cleaned == QLatin1String( "br" ) )
return LabelBottomRight;
if ( ok )
*ok = false;
return LabelPointOnExterior;
}
QgsGeometry QgsCallout::labelAnchorGeometry( QRectF rect, const double, LabelAnchorPoint anchor ) const
{
QgsGeometry label( QgsGeometry::fromRect( rect ) );
switch ( anchor )
{
case LabelPointOnExterior:
return label;
case LabelCentroid:
return label.centroid();
case LabelTopLeft:
return QgsGeometry::fromPointXY( QgsPointXY( rect.bottomLeft() ) );
case LabelTopMiddle:
return QgsGeometry::fromPointXY( QgsPointXY( ( rect.left() + rect.right() ) / 2.0, rect.bottom() ) );
case LabelTopRight:
return QgsGeometry::fromPointXY( QgsPointXY( rect.bottomRight() ) );
case LabelMiddleLeft:
return QgsGeometry::fromPointXY( QgsPointXY( rect.left(), ( rect.top() + rect.bottom() ) / 2.0 ) );
case LabelMiddleRight:
return QgsGeometry::fromPointXY( QgsPointXY( rect.right(), ( rect.top() + rect.bottom() ) / 2.0 ) );
case LabelBottomLeft:
return QgsGeometry::fromPointXY( QgsPointXY( rect.topLeft() ) );
case LabelBottomMiddle:
return QgsGeometry::fromPointXY( QgsPointXY( ( rect.left() + rect.right() ) / 2.0, rect.top() ) );
case LabelBottomRight:
return QgsGeometry::fromPointXY( QgsPointXY( rect.topRight() ) );
}
return label;
}
//
// QgsSimpleLineCallout
//
@ -312,9 +411,17 @@ void QgsSimpleLineCallout::setLineSymbol( QgsLineSymbol *symbol )
mLineSymbol.reset( symbol );
}
void QgsSimpleLineCallout::draw( QgsRenderContext &context, QRectF rect, const double, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
void QgsSimpleLineCallout::draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
{
QgsGeometry label( QgsGeometry::fromRect( rect ) );
LabelAnchorPoint labelAnchor = labelAnchorPoint();
if ( dataDefinedProperties().isActive( QgsCallout::LabelAnchorPointPosition ) )
{
QString encodedAnchor = encodeLabelAnchorPoint( labelAnchor );
context.expressionContext().setOriginalValueVariable( encodedAnchor );
labelAnchor = decodeLabelAnchorPoint( dataDefinedProperties().valueAsString( QgsCallout::LabelAnchorPointPosition, context.expressionContext(), encodedAnchor ) );
}
QgsGeometry label = labelAnchorGeometry( rect, angle, labelAnchor );
auto drawCalloutLine = [this, &context, &label]( const QgsGeometry & partAnchor )
{
QgsGeometry line;
@ -451,9 +558,17 @@ QgsManhattanLineCallout *QgsManhattanLineCallout::clone() const
return new QgsManhattanLineCallout( *this );
}
void QgsManhattanLineCallout::draw( QgsRenderContext &context, QRectF rect, const double, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
void QgsManhattanLineCallout::draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
{
QgsGeometry label( QgsGeometry::fromRect( rect ) );
LabelAnchorPoint labelAnchor = labelAnchorPoint();
if ( dataDefinedProperties().isActive( QgsCallout::LabelAnchorPointPosition ) )
{
QString encodedAnchor = encodeLabelAnchorPoint( labelAnchor );
context.expressionContext().setOriginalValueVariable( encodedAnchor );
labelAnchor = decodeLabelAnchorPoint( dataDefinedProperties().valueAsString( QgsCallout::LabelAnchorPointPosition, context.expressionContext(), encodedAnchor ) );
}
QgsGeometry label = labelAnchorGeometry( rect, angle, labelAnchor );
auto drawCalloutLine = [this, &context, &label]( const QgsGeometry & partAnchor )
{
QgsGeometry line;

View File

@ -73,6 +73,7 @@ class CORE_EXPORT QgsCallout
OffsetFromLabel, //!< Distance to offset lines from label area
DrawCalloutToAllParts, //!< Whether callout lines should be drawn to all feature parts
AnchorPointPosition, //!< Feature's anchor point position
LabelAnchorPointPosition, //!< Label's anchor point position
};
//! Options for draw order (stacking) of callouts
@ -91,6 +92,24 @@ class CORE_EXPORT QgsCallout
Centroid, //!< The surface's centroid is used as anchor for polygon geometries
};
/**
* Label's anchor point position.
* \since QGIS 3.14
*/
enum LabelAnchorPoint
{
LabelPointOnExterior, //!< The point on the label's boundary closest to the feature
LabelCentroid, //!< The labe's centroid
LabelTopLeft, //!< Top left corner of the label's boundary
LabelTopMiddle, //!< Top middle of the label's boundary
LabelTopRight, //!< Top right corner of the label's boundary
LabelMiddleLeft, //!< Middle left of the label's boundary
LabelMiddleRight, //!< Middle right of the label's boundary
LabelBottomLeft, //!< Bottom left corner of the label's boundary
LabelBottomMiddle, //!< Bottom middle of the label's boundary
LabelBottomRight, //!< Bottom right corner of the label's boundary
};
/**
* Constructor for QgsCallout.
*/
@ -293,6 +312,42 @@ class CORE_EXPORT QgsCallout
*/
static QgsCallout::AnchorPoint decodeAnchorPoint( const QString &name, bool *ok = nullptr );
/**
* Returns the label's anchor point position.
*
* \see setLabelAnchorPoint()
* \since QGIS 3.14
*/
LabelAnchorPoint labelAnchorPoint() const { return mLabelAnchorPoint; }
/**
* Sets the label's \a anchor point position.
*
* \see labelAnchorPoint()
* \since QGIS 3.14
*/
void setLabelAnchorPoint( LabelAnchorPoint anchor ) { mLabelAnchorPoint = anchor; }
/**
* Encodes a label \a anchor point to its string representation.
* \returns encoded string
* \see decodeLabelAnchorPoint()
* \since QGIS 3.14
*/
static QString encodeLabelAnchorPoint( LabelAnchorPoint anchor );
/**
* Attempts to decode a string representation of a label anchor point name to the corresponding
* anchor point.
* \param name encoded label anchor point name
* \param ok if specified, will be set to TRUE if the anchor point was successfully decoded
* \returns decoded name
* \see encodeLabelAnchorPoint()
* \since QGIS 3.14
*/
static QgsCallout::LabelAnchorPoint decodeLabelAnchorPoint( const QString &name, bool *ok = nullptr );
protected:
/**
@ -315,11 +370,18 @@ class CORE_EXPORT QgsCallout
*/
virtual void draw( QgsRenderContext &context, QRectF bodyBoundingBox, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext ) = 0;
/**
* Returns the anchor point geometry for a label with the given bounding box and \a anchor point mode.
* \since QGIS 3.14
*/
QgsGeometry labelAnchorGeometry( QRectF bodyBoundingBox, const double angle, LabelAnchorPoint anchor ) const;
private:
bool mEnabled = false;
AnchorPoint mAnchorPoint = PoleOfInaccessibility;
LabelAnchorPoint mLabelAnchorPoint = LabelPointOnExterior;
//! Property collection for data defined callout settings
QgsPropertyCollection mDataDefinedProperties;

View File

@ -167,6 +167,18 @@ QgsSimpleLineCalloutWidget::QgsSimpleLineCalloutWidget( QgsVectorLayer *vl, QWid
mAnchorPointComboBox->addItem( tr( "Centroid" ), static_cast< int >( QgsCallout::Centroid ) );
connect( mAnchorPointComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsSimpleLineCalloutWidget::mAnchorPointComboBox_currentIndexChanged );
mLabelAnchorPointComboBox->addItem( tr( "Closest Point" ), static_cast< int >( QgsCallout::LabelPointOnExterior ) );
mLabelAnchorPointComboBox->addItem( tr( "Centroid" ), static_cast< int >( QgsCallout::LabelCentroid ) );
mLabelAnchorPointComboBox->addItem( tr( "Top Left" ), static_cast< int >( QgsCallout::LabelTopLeft ) );
mLabelAnchorPointComboBox->addItem( tr( "Top Center" ), static_cast< int >( QgsCallout::LabelTopMiddle ) );
mLabelAnchorPointComboBox->addItem( tr( "Top Right" ), static_cast< int >( QgsCallout::LabelTopRight ) );
mLabelAnchorPointComboBox->addItem( tr( "Left Middle" ), static_cast< int >( QgsCallout::LabelMiddleLeft ) );
mLabelAnchorPointComboBox->addItem( tr( "Right Middle" ), static_cast< int >( QgsCallout::LabelMiddleRight ) );
mLabelAnchorPointComboBox->addItem( tr( "Bottom Left" ), static_cast< int >( QgsCallout::LabelBottomLeft ) );
mLabelAnchorPointComboBox->addItem( tr( "Bottom Center" ), static_cast< int >( QgsCallout::LabelBottomMiddle ) );
mLabelAnchorPointComboBox->addItem( tr( "Bottom Right" ), static_cast< int >( QgsCallout::LabelBottomRight ) );
connect( mLabelAnchorPointComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsSimpleLineCalloutWidget::mLabelAnchorPointComboBox_currentIndexChanged );
connect( mCalloutLineStyleButton, &QgsSymbolButton::changed, this, &QgsSimpleLineCalloutWidget::lineSymbolChanged );
}
@ -202,12 +214,14 @@ void QgsSimpleLineCalloutWidget::setCallout( QgsCallout *callout )
whileBlocking( mDrawToAllPartsCheck )->setChecked( mCallout->drawCalloutToAllParts() );
whileBlocking( mAnchorPointComboBox )->setCurrentIndex( mAnchorPointComboBox->findData( static_cast< int >( callout->anchorPoint() ) ) );
whileBlocking( mLabelAnchorPointComboBox )->setCurrentIndex( mLabelAnchorPointComboBox->findData( static_cast< int >( callout->labelAnchorPoint() ) ) );
registerDataDefinedButton( mMinCalloutLengthDDBtn, QgsCallout::MinimumCalloutLength );
registerDataDefinedButton( mOffsetFromAnchorDDBtn, QgsCallout::OffsetFromAnchor );
registerDataDefinedButton( mOffsetFromLabelDDBtn, QgsCallout::OffsetFromLabel );
registerDataDefinedButton( mDrawToAllPartsDDBtn, QgsCallout::DrawCalloutToAllParts );
registerDataDefinedButton( mAnchorPointDDBtn, QgsCallout::AnchorPointPosition );
registerDataDefinedButton( mLabelAnchorPointDDBtn, QgsCallout::LabelAnchorPointPosition );
}
void QgsSimpleLineCalloutWidget::setGeometryType( QgsWkbTypes::GeometryType type )
@ -277,6 +291,12 @@ void QgsSimpleLineCalloutWidget::mAnchorPointComboBox_currentIndexChanged( int i
emit changed();
}
void QgsSimpleLineCalloutWidget::mLabelAnchorPointComboBox_currentIndexChanged( int index )
{
mCallout->setLabelAnchorPoint( static_cast<QgsCallout::LabelAnchorPoint>( mLabelAnchorPointComboBox->itemData( index ).toInt() ) );
emit changed();
}
void QgsSimpleLineCalloutWidget::drawToAllPartsToggled( bool active )
{
mCallout->setDrawCalloutToAllParts( active );
@ -296,3 +316,4 @@ QgsManhattanLineCalloutWidget::QgsManhattanLineCalloutWidget( QgsVectorLayer *vl
///@endcond

View File

@ -152,6 +152,7 @@ class GUI_EXPORT QgsSimpleLineCalloutWidget : public QgsCalloutWidget, private U
void offsetFromLabelChanged();
void lineSymbolChanged();
void mAnchorPointComboBox_currentIndexChanged( int index );
void mLabelAnchorPointComboBox_currentIndexChanged( int index );
void drawToAllPartsToggled( bool active );
private:

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>337</width>
<height>184</height>
<width>341</width>
<height>201</height>
</rect>
</property>
<property name="windowTitle">
@ -34,13 +34,70 @@
<property name="topMargin">
<number>0</number>
</property>
<item row="2" column="3">
<widget class="QgsPropertyOverrideButton" name="mOffsetFromAnchorDDBtn">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Line style</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="mAnchorPointLbl">
<property name="text">
<string>Anchor point</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QgsUnitSelectionWidget" name="mOffsetFromAnchorUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>10</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QgsPropertyOverrideButton" name="mMinCalloutLengthDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_47">
<property name="text">
<string>Offset from label area</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QComboBox" name="mAnchorPointComboBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_46">
<property name="text">
<string>Offset from feature</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QgsSymbolButton" name="mCalloutLineStyleButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Symbol…</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QgsUnitSelectionWidget" name="mOffsetFromLabelUnitWidget" native="true">
<property name="minimumSize">
@ -54,16 +111,30 @@
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QgsSymbolButton" name="mCalloutLineStyleButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item row="4" column="3">
<widget class="QgsPropertyOverrideButton" name="mDrawToAllPartsDDBtn">
<property name="text">
<string>Symbol…</string>
<string>…</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QgsPropertyOverrideButton" name="mOffsetFromLabelDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QgsUnitSelectionWidget" name="mMinCalloutWidthUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>10</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
@ -92,65 +163,20 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_46">
<item row="4" column="0" colspan="3">
<widget class="QCheckBox" name="mDrawToAllPartsCheck">
<property name="text">
<string>Offset from feature</string>
<string>Draw lines to all feature parts</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QgsUnitSelectionWidget" name="mOffsetFromAnchorUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>10</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QgsPropertyOverrideButton" name="mMinCalloutLengthDDBtn">
<item row="2" column="3">
<widget class="QgsPropertyOverrideButton" name="mOffsetFromAnchorDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QgsPropertyOverrideButton" name="mOffsetFromLabelDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QgsDoubleSpinBox" name="mOffsetFromLabelSpin">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>100000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
<property name="showClearButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QgsDoubleSpinBox" name="mMinCalloutLengthSpin">
<property name="sizePolicy">
@ -176,23 +202,10 @@
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QgsUnitSelectionWidget" name="mMinCalloutWidthUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>10</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_47">
<item row="5" column="3">
<widget class="QgsPropertyOverrideButton" name="mAnchorPointDDBtn">
<property name="text">
<string>Offset from label area</string>
<string>…</string>
</property>
</widget>
</item>
@ -203,39 +216,43 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Line style</string>
<item row="3" column="1">
<widget class="QgsDoubleSpinBox" name="mOffsetFromLabelSpin">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>100000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
<property name="showClearButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QCheckBox" name="mDrawToAllPartsCheck">
<item row="6" column="0">
<widget class="QLabel" name="mAnchorPointLbl_2">
<property name="text">
<string>Draw lines to all feature parts</string>
<string>Label anchor point</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QgsPropertyOverrideButton" name="mDrawToAllPartsDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
<item row="6" column="1" colspan="2">
<widget class="QComboBox" name="mLabelAnchorPointComboBox"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="mAnchorPointLbl">
<property name="text">
<string>Anchor point</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QComboBox" name="mAnchorPointComboBox"></widget>
</item>
<item row="5" column="3">
<widget class="QgsPropertyOverrideButton" name="mAnchorPointDDBtn">
<item row="6" column="3">
<widget class="QgsPropertyOverrideButton" name="mLabelAnchorPointDDBtn">
<property name="text">
<string>…</string>
</property>

View File

@ -133,6 +133,16 @@ class TestQgsCallout: public QObject
void calloutDataDefinedOffsetFromAnchor();
void calloutOffsetFromLabel();
void calloutDataDefinedOffsetFromLabel();
void calloutLabelAnchorTopRight();
void calloutLabelAnchorTopLeft();
void calloutLabelAnchorTop();
void calloutLabelAnchorBottomLeft();
void calloutLabelAnchorBottom();
void calloutLabelAnchorBottomRight();
void calloutLabelAnchorLeft();
void calloutLabelAnchorRight();
void calloutLabelAnchorCentroid();
void calloutLabelDataDefinedAnchor();
void calloutBehindLabel();
void calloutBehindIndividualLabels();
void calloutNoDrawToAllParts();
@ -939,6 +949,527 @@ void TestQgsCallout::calloutDataDefinedOffsetFromLabel()
QVERIFY( imageCheck( "callout_data_defined_offset_from_label", img, 20 ) );
}
void TestQgsCallout::calloutLabelAnchorTopRight()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelTopRight );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_anchor_top_right", img, 20 ) );
}
void TestQgsCallout::calloutLabelAnchorTopLeft()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelTopLeft );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_anchor_top_left", img, 20 ) );
}
void TestQgsCallout::calloutLabelAnchorTop()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelTopMiddle );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_anchor_top_middle", img, 20 ) );
}
void TestQgsCallout::calloutLabelAnchorBottomLeft()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelBottomLeft );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_anchor_bottom_left", img, 20 ) );
}
void TestQgsCallout::calloutLabelAnchorBottom()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelBottomMiddle );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_anchor_bottom_middle", img, 20 ) );
}
void TestQgsCallout::calloutLabelAnchorBottomRight()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelBottomRight );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_anchor_bottom_right", img, 20 ) );
}
void TestQgsCallout::calloutLabelAnchorLeft()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelMiddleLeft );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_anchor_left", img, 20 ) );
}
void TestQgsCallout::calloutLabelAnchorRight()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelMiddleRight );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_anchor_right", img, 20 ) );
}
void TestQgsCallout::calloutLabelAnchorCentroid()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelCentroid );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_anchor_centroid", img, 20 ) );
}
void TestQgsCallout::calloutLabelDataDefinedAnchor()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
mapSettings.setOutputDpi( 96 );
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
settings.placement = QgsPalLayerSettings::AroundPoint;
settings.dist = 10;
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
callout->setEnabled( true );
callout->lineSymbol()->setWidth( 1 );
callout->setLabelAnchorPoint( QgsCallout::LabelCentroid );
callout->dataDefinedProperties().setProperty( QgsCallout::LabelAnchorPointPosition, QgsProperty::fromExpression( QStringLiteral( "'TL'" ) ) );
settings.setCallout( callout );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl->setLabelsEnabled( true );
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
//engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
engine.run( context );
p.end();
QVERIFY( imageCheck( "callout_label_datadefined_anchor", img, 20 ) );
}
void TestQgsCallout::calloutBehindLabel()
{
// test that callouts are rendered below labels

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB