[FEATURE][layouts] Add setting for label margin for map items

This setting allows per-map control of how close labels are permitted
to be placed to the map item's edges.

Sizes can be set using mm/inches/pixels/etc, and data defined
label margins are allowed.

Fixes #10314
This commit is contained in:
Nyall Dawson 2018-12-11 12:30:14 +10:00
parent 35855b88e1
commit ed25a3e2ee
11 changed files with 288 additions and 24 deletions

View File

@ -135,6 +135,7 @@ A base class for objects which belong to a layout.
MapAtlasMargin,
MapLayers,
MapStylePreset,
MapLabelMargin,
//composer picture
PictureSource,
PictureSvgBackgroundColor,

View File

@ -75,6 +75,8 @@ QgsLayoutMapWidget::QgsLayoutMapWidget( QgsLayoutItemMap *item )
connect( mOverviewCheckBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutMapWidget::mOverviewCheckBox_toggled );
connect( mOverviewListWidget, &QListWidget::currentItemChanged, this, &QgsLayoutMapWidget::mOverviewListWidget_currentItemChanged );
connect( mOverviewListWidget, &QListWidget::itemChanged, this, &QgsLayoutMapWidget::mOverviewListWidget_itemChanged );
connect( mLabelSettingsButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::showLabelSettings );
setPanelTitle( tr( "Map Properties" ) );
mMapRotationSpinBox->setClearValue( 0 );
@ -344,6 +346,12 @@ void QgsLayoutMapWidget::overviewSymbolChanged()
mMapItem->update();
}
void QgsLayoutMapWidget::showLabelSettings()
{
QgsLayoutMapLabelingWidget *w = new QgsLayoutMapLabelingWidget( mMapItem );
openPanel( w );
}
void QgsLayoutMapWidget::mAtlasCheckBox_toggled( bool checked )
{
if ( !mMapItem )
@ -1635,3 +1643,53 @@ void QgsLayoutMapWidget::mOverviewCenterCheckbox_toggled( bool state )
mMapItem->update();
mMapItem->endCommand();
}
//
// QgsLayoutMapLabelingWidget
//
QgsLayoutMapLabelingWidget::QgsLayoutMapLabelingWidget( QgsLayoutItemMap *map )
: QgsLayoutItemBaseWidget( nullptr, map )
, mMapItem( map )
{
setupUi( this );
setPanelTitle( tr( "Label Settings" ) );
mLabelBoundarySpinBox->setClearValue( 0 );
mLabelBoundarySpinBox->setShowClearButton( true );
mLabelBoundaryUnitsCombo->linkToWidget( mLabelBoundarySpinBox );
mLabelBoundaryUnitsCombo->setConverter( &mMapItem->layout()->renderContext().measurementConverter() );
mLabelBoundarySpinBox->setValue( mMapItem->labelMargin().length() );
mLabelBoundaryUnitsCombo->setUnit( mMapItem->labelMargin().units() );
connect( mLabelBoundaryUnitsCombo, &QgsLayoutUnitsComboBox::changed, this, &QgsLayoutMapLabelingWidget::labelMarginUnitsChanged );
connect( mLabelBoundarySpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutMapLabelingWidget::labelMarginChanged );
registerDataDefinedButton( mLabelMarginDDBtn, QgsLayoutObject::MapLabelMargin );
updateDataDefinedButton( mLabelMarginDDBtn );
}
void QgsLayoutMapLabelingWidget::labelMarginChanged( double val )
{
if ( !mMapItem )
return;
mMapItem->layout()->undoStack()->beginCommand( mMapItem, tr( "Change Label Margin" ), QgsLayoutItem::UndoMapLabelMargin );
mMapItem->setLabelMargin( QgsLayoutMeasurement( val, mLabelBoundaryUnitsCombo->unit() ) );
mMapItem->layout()->undoStack()->endCommand();
mMapItem->invalidateCache();
}
void QgsLayoutMapLabelingWidget::labelMarginUnitsChanged()
{
if ( !mMapItem )
return;
mMapItem->layout()->undoStack()->beginCommand( mMapItem, tr( "Change Label Margin" ), QgsLayoutItem::UndoMapLabelMargin );
mMapItem->setLabelMargin( QgsLayoutMeasurement( mLabelBoundarySpinBox->value(), mLabelBoundaryUnitsCombo->unit() ) );
mMapItem->layout()->undoStack()->endCommand();
mMapItem->invalidateCache();
}

View File

@ -19,6 +19,7 @@
#define QGSLAYOUTMAPWIDGET_H
#include "ui_qgslayoutmapwidgetbase.h"
#include "ui_qgslayoutmaplabelingwidgetbase.h"
#include "qgslayoutitemwidget.h"
#include "qgslayoutitemmapgrid.h"
@ -116,6 +117,7 @@ class QgsLayoutMapWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayoutM
void mapCrsChanged( const QgsCoordinateReferenceSystem &crs );
void overviewSymbolChanged();
void showLabelSettings();
private:
QPointer< QgsLayoutItemMap > mMapItem;
QgsLayoutItemPropertiesWidget *mItemPropertiesWidget = nullptr;
@ -164,4 +166,24 @@ class QgsLayoutMapWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayoutM
};
/**
* \ingroup app
* Allows configuration of layout map labeling settings.
* */
class QgsLayoutMapLabelingWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayoutMapLabelingWidgetBase
{
Q_OBJECT
public:
explicit QgsLayoutMapLabelingWidget( QgsLayoutItemMap *map );
private slots:
void labelMarginChanged( double val );
void labelMarginUnitsChanged();
private:
QPointer< QgsLayoutItemMap > mMapItem;
};
#endif

View File

@ -742,7 +742,7 @@ bool QgsLayoutItemMap::readPropertiesFromElement( const QDomElement &itemElem, c
mAtlasMargin = atlasElem.attribute( QStringLiteral( "margin" ), QStringLiteral( "0.1" ) ).toDouble();
}
mLabelMargin = QgsLayoutMeasurement::decodeMeasurement( itemElem.attribute( QStringLiteral( "labelMargin" ), QStringLiteral( "0" ) ) );
setLabelMargin( QgsLayoutMeasurement::decodeMeasurement( itemElem.attribute( QStringLiteral( "labelMargin" ), QStringLiteral( "0" ) ) ) );
updateBoundingRect();
@ -1131,11 +1131,11 @@ QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF
// override the default text render format inherited from the labeling engine settings using the layout's render context setting
jobMapSettings.setTextRenderFormat( mLayout->renderContext().textRenderFormat() );
if ( mLabelMargin.length() > 0 )
if ( mEvaluatedLabelMargin.length() > 0 )
{
QPolygonF visiblePoly = jobMapSettings.visiblePolygon();
visiblePoly.append( visiblePoly.at( 0 ) ); //close polygon
const double layoutLabelMargin = mLayout->convertToLayoutUnits( mLabelMargin );
const double layoutLabelMargin = mLayout->convertToLayoutUnits( mEvaluatedLabelMargin );
const double layoutLabelMarginInMapUnits = layoutLabelMargin / rect().width() * jobMapSettings.extent().width();
QgsGeometry mapBoundaryGeom = QgsGeometry::fromQPolygonF( visiblePoly );
mapBoundaryGeom = mapBoundaryGeom.buffer( -layoutLabelMarginInMapUnits, 0 );
@ -1335,6 +1335,10 @@ void QgsLayoutItemMap::refreshDataDefinedProperty( const QgsLayoutObject::DataDe
emit extentChanged();
}
}
if ( property == QgsLayoutObject::MapLabelMargin || property == QgsLayoutObject::AllProperties )
{
refreshLabelMargin( false );
}
//force redraw
mCacheInvalidated = true;
@ -1436,6 +1440,7 @@ QgsLayoutMeasurement QgsLayoutItemMap::labelMargin() const
void QgsLayoutItemMap::setLabelMargin( const QgsLayoutMeasurement &margin )
{
mLabelMargin = margin;
refreshLabelMargin( false );
}
void QgsLayoutItemMap::updateToolTip()
@ -1900,6 +1905,19 @@ void QgsLayoutItemMap::refreshMapExtents( const QgsExpressionContext *context )
}
}
void QgsLayoutItemMap::refreshLabelMargin( bool updateItem )
{
//data defined label margin set?
double labelMargin = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapLabelMargin, createExpressionContext(), mLabelMargin.length() );
mEvaluatedLabelMargin.setLength( labelMargin );
mEvaluatedLabelMargin.setUnits( mLabelMargin.units() );
if ( updateItem )
{
update();
}
}
void QgsLayoutItemMap::updateAtlasFeature()
{
if ( !atlasDriven() || !mLayout->reportContext().layer() )

View File

@ -646,6 +646,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
bool mPainterCancelWait = false;
QgsLayoutMeasurement mLabelMargin{ 0 };
QgsLayoutMeasurement mEvaluatedLabelMargin{ 0 };
void init();
@ -694,6 +695,8 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
*/
void refreshMapExtents( const QgsExpressionContext *context = nullptr );
void refreshLabelMargin( bool updateItem );
void updateAtlasFeature();
QgsRectangle computeAtlasRectangle();

View File

@ -63,6 +63,7 @@ void QgsLayoutObject::initPropertyDefinitions()
{ QgsLayoutObject::MapYMin, QgsPropertyDefinition( "dataDefinedMapYMin", QObject::tr( "Extent minimum Y" ), QgsPropertyDefinition::Double ) },
{ QgsLayoutObject::MapXMax, QgsPropertyDefinition( "dataDefinedMapXMax", QObject::tr( "Extent maximum X" ), QgsPropertyDefinition::Double ) },
{ QgsLayoutObject::MapYMax, QgsPropertyDefinition( "dataDefinedMapYMax", QObject::tr( "Extent maximum Y" ), QgsPropertyDefinition::Double ) },
{ QgsLayoutObject::MapLabelMargin, QgsPropertyDefinition( "dataDefinedMapLabelMargin", QObject::tr( "Label margin" ), QgsPropertyDefinition::DoublePositive ) },
{ QgsLayoutObject::MapAtlasMargin, QgsPropertyDefinition( "dataDefinedMapAtlasMargin", QObject::tr( "Atlas margin" ), QgsPropertyDefinition::DoublePositive ) },
{ QgsLayoutObject::MapLayers, QgsPropertyDefinition( "dataDefinedMapLayers", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map Layers" ), tr( "list of map layer names separated by | characters" ) ) },
{ QgsLayoutObject::MapStylePreset, QgsPropertyDefinition( "dataDefinedMapStylePreset", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map theme" ), tr( "name of an existing map theme (case-sensitive)" ) ) },

View File

@ -163,6 +163,7 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe
MapAtlasMargin, //!< Map atlas margin
MapLayers, //!< Map layer set
MapStylePreset, //!< Layer and style map theme
MapLabelMargin, //!< Map label margin
//composer picture
PictureSource, //!< Picture source url
PictureSvgBackgroundColor, //!< SVG background color

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsLayoutMapLabelingWidgetBase</class>
<widget class="QWidget" name="QgsLayoutMapLabelingWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>326</width>
<height>424</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Map Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QgsCollapsibleGroupBoxBasic" name="qgsCollapsibleGroupBoxBasic">
<property name="title">
<string>Label Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_6" columnstretch="0,0,0,0">
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_10">
<property name="text">
<string>of map edges</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QgsDoubleSpinBox" name="mLabelBoundarySpinBox">
<property name="prefix">
<string/>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>9999.000000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Avoid placing labels within</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QgsLayoutUnitsComboBox" name="mLabelBoundaryUnitsCombo"/>
</item>
<item row="2" column="3">
<widget class="QgsPropertyOverrideButton" name="mLabelMarginDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>QgsCollapsibleGroupBoxBasic</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsDoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>qgsdoublespinbox.h</header>
</customwidget>
<customwidget>
<class>QgsPropertyOverrideButton</class>
<extends>QToolButton</extends>
<header>qgspropertyoverridebutton.h</header>
</customwidget>
<customwidget>
<class>QgsLayoutUnitsComboBox</class>
<extends>QComboBox</extends>
<header>qgslayoutunitscombobox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -63,9 +63,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-1556</y>
<width>627</width>
<height>2265</height>
<y>0</y>
<width>368</width>
<height>1373</height>
</rect>
</property>
<property name="sizePolicy">
@ -396,20 +396,27 @@
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="mSetToMapCanvasExtentButton">
<property name="text">
<string>Set to map canvas extent</string>
<string>Set to Map Canvas Extent</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QPushButton" name="mViewExtentInCanvasButton">
<property name="text">
<string>View extent in map canvas</string>
<string>View Extent in Map Canvas</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="mLabelSettingsButton">
<property name="text">
<string>Label Settings…</string>
</property>
</widget>
</item>
<item>
<widget class="QgsCollapsibleGroupBoxBasic" name="mAtlasCheckBox">
<property name="title">
@ -600,7 +607,7 @@
<item>
<widget class="QPushButton" name="mGridPropertiesButton">
<property name="text">
<string>Modify grid…</string>
<string>Modify Grid…</string>
</property>
</widget>
</item>
@ -793,9 +800,10 @@
<container>1</container>
</customwidget>
<customwidget>
<class>QgsPropertyOverrideButton</class>
<extends>QToolButton</extends>
<header>qgspropertyoverridebutton.h</header>
<class>QgsCollapsibleGroupBoxBasic</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsDoubleSpinBox</class>
@ -807,17 +815,21 @@
<extends>QSpinBox</extends>
<header>qgsspinbox.h</header>
</customwidget>
<customwidget>
<class>QgsCollapsibleGroupBoxBasic</class>
<extends>QGroupBox</extends>
<header location="global">qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsBlendModeComboBox</class>
<extends>QComboBox</extends>
<header>qgsblendmodecombobox.h</header>
</customwidget>
<customwidget>
<class>QgsSymbolButton</class>
<extends>QToolButton</extends>
<header>qgssymbolbutton.h</header>
</customwidget>
<customwidget>
<class>QgsPropertyOverrideButton</class>
<extends>QToolButton</extends>
<header>qgspropertyoverridebutton.h</header>
</customwidget>
<customwidget>
<class>QgsProjectionSelectionWidget</class>
<extends>QWidget</extends>
@ -829,11 +841,6 @@
<extends>QComboBox</extends>
<header>qgslayoutitemcombobox.h</header>
</customwidget>
<customwidget>
<class>QgsSymbolButton</class>
<extends>QToolButton</extends>
<header>qgssymbolbutton.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>scrollArea</tabstop>
@ -863,6 +870,7 @@
<tabstop>mYMaxDDBtn</tabstop>
<tabstop>mSetToMapCanvasExtentButton</tabstop>
<tabstop>mViewExtentInCanvasButton</tabstop>
<tabstop>mLabelSettingsButton</tabstop>
<tabstop>mAtlasCheckBox</tabstop>
<tabstop>mAtlasMarginRadio</tabstop>
<tabstop>mAtlasMarginSpinBox</tabstop>
@ -920,6 +928,11 @@
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -38,7 +38,9 @@ from qgis.core import (QgsLayoutItemMap,
QgsVectorLayerSimpleLabeling,
QgsLabelingEngineSettings,
QgsLayoutMeasurement,
QgsUnitTypes)
QgsUnitTypes,
QgsLayoutObject,
QgsProperty)
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
@ -326,6 +328,17 @@ class TestQgsLayoutMap(unittest.TestCase, LayoutItemTestCase):
self.report += checker.report()
self.assertTrue(result, message)
# data defined
map.setMapRotation(0)
map.zoomToExtent(vl.extent())
map.dataDefinedProperties().setProperty(QgsLayoutObject.MapLabelMargin, QgsProperty.fromExpression('1+3'))
map.refresh()
checker = QgsLayoutChecker('composermap_dd_label_margin', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)
if __name__ == '__main__':
unittest.main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB