Skip to content
Permalink
Browse files

Data-defined size legend: configurable labels + few fixes

This may break projects that started to use data-defined size legend with manually defined rules.
  • Loading branch information
wonder-sk committed Jun 26, 2017
1 parent a2d00d5 commit 81653d69786865d2b95d3736260b0deef9aeefee
@@ -48,7 +48,7 @@ Copy constructor
{
SizeClass( double size, const QString &label );

double size; //!< Marker size in units used by the symbol (usually millimeters)
double size; //!< Marker size in units used by the symbol (usually millimeters). May be further scaled before rendering if size scale transformer is enabled.
QString label; //!< Label to be shown with the particular symbol size
};

@@ -72,6 +72,16 @@ Returns marker symbol that will be used to draw markers in legend
:rtype: QgsMarkerSymbol
%End

void setSizeScaleTransformer( QgsSizeScaleTransformer *transformer /Transfer/ );
%Docstring
Sets transformer for scaling of symbol sizes. Takes ownership of the object. Accepts null pointer to set no transformer.
%End
QgsSizeScaleTransformer *sizeScaleTransformer() const;
%Docstring
Returns transformer for scaling of symbol sizes. Returns null if no transformer is defined.
:rtype: QgsSizeScaleTransformer
%End

void setClasses( const QList<QgsDataDefinedSizeLegend::SizeClass> &classes );
%Docstring
Sets list of classes: each class is a pair of symbol size (in units used by the symbol) and label
@@ -205,9 +205,9 @@ class QgsPropertyTransformer
virtual bool loadVariant( const QVariant &transformer );
%Docstring
Loads this transformer from a QVariantMap, wrapped in a QVariant.
You can use QgsXmlUtils.writeVariant to save it to an XML document.
You can use QgsXmlUtils.readVariant to read it from an XML document.

.. seealso:: loadVariant()
.. seealso:: toVariant()
:rtype: bool
%End

@@ -216,7 +216,7 @@ class QgsPropertyTransformer
Saves this transformer to a QVariantMap, wrapped in a QVariant.
You can use QgsXmlUtils.writeVariant to save it to an XML document.

.. seealso:: toVariant()
.. seealso:: loadVariant()
:rtype: QVariant
%End

@@ -945,7 +945,9 @@ void QgsDiagramProperties::showSizeLegendDialog()
bool isExpression;
QString sizeFieldNameOrExp = mSizeFieldExpressionWidget->currentField( &isExpression );
QgsProperty ddSize = isExpression ? QgsProperty::fromExpression( sizeFieldNameOrExp ) : QgsProperty::fromField( sizeFieldNameOrExp );
ddSize.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Linear, 0.0, mMaxValueSpinBox->value(), 0.0, mSizeSpinBox->value() ) );
bool scaleByArea = mScaleDependencyComboBox->currentData().toBool();
ddSize.setTransformer( new QgsSizeScaleTransformer( scaleByArea ? QgsSizeScaleTransformer::Area : QgsSizeScaleTransformer::Linear,
0.0, mMaxValueSpinBox->value(), 0.0, mSizeSpinBox->value() ) );

QgsDataDefinedSizeLegendWidget *panel = new QgsDataDefinedSizeLegendWidget( mSizeLegend.get(), ddSize, nullptr, mMapCanvas );

@@ -18,6 +18,7 @@
#include "qgsproperty.h"
#include "qgspropertytransformer.h"
#include "qgssymbollayerutils.h"
#include "qgsxmlutils.h"

QgsDataDefinedSizeLegend::QgsDataDefinedSizeLegend()
{
@@ -28,6 +29,7 @@ QgsDataDefinedSizeLegend::QgsDataDefinedSizeLegend( const QgsDataDefinedSizeLege
, mTitleLabel( other.mTitleLabel )
, mSizeClasses( other.mSizeClasses )
, mSymbol( other.mSymbol.get() ? other.mSymbol->clone() : nullptr )
, mSizeScaleTransformer( other.mSizeScaleTransformer.get() ? new QgsSizeScaleTransformer( *other.mSizeScaleTransformer ) : nullptr )
, mVAlign( other.mVAlign )
, mFont( other.mFont )
, mTextColor( other.mTextColor )
@@ -43,6 +45,7 @@ QgsDataDefinedSizeLegend &QgsDataDefinedSizeLegend::operator=( const QgsDataDefi
mTitleLabel = other.mTitleLabel;
mSizeClasses = other.mSizeClasses;
mSymbol.reset( other.mSymbol.get() ? other.mSymbol->clone() : nullptr );
mSizeScaleTransformer.reset( other.mSizeScaleTransformer.get() ? new QgsSizeScaleTransformer( *other.mSizeScaleTransformer ) : nullptr );
mVAlign = other.mVAlign;
mFont = other.mFont;
mTextColor = other.mTextColor;
@@ -61,24 +64,35 @@ QgsMarkerSymbol *QgsDataDefinedSizeLegend::symbol() const
return mSymbol.get();
}

void QgsDataDefinedSizeLegend::setSizeScaleTransformer( QgsSizeScaleTransformer *transformer )
{
mSizeScaleTransformer.reset( transformer );
}

QgsSizeScaleTransformer *QgsDataDefinedSizeLegend::sizeScaleTransformer() const
{
return mSizeScaleTransformer.get();
}


void QgsDataDefinedSizeLegend::updateFromSymbolAndProperty( const QgsMarkerSymbol *symbol, const QgsProperty &ddSize )
{
mSymbol.reset( symbol->clone() );
mSymbol->setDataDefinedSize( QgsProperty() ); // original symbol may have had data-defined size associated

mTitleLabel = ddSize.propertyType() == QgsProperty::ExpressionBasedProperty ? ddSize.expressionString() : ddSize.field();
const QgsSizeScaleTransformer *sizeTransformer = dynamic_cast< const QgsSizeScaleTransformer * >( ddSize.transformer() );
mSizeScaleTransformer.reset( sizeTransformer ? sizeTransformer->clone() : nullptr );

if ( !mSizeClasses.isEmpty() )
return; // manually generated classes
if ( mTitleLabel.isEmpty() )
mTitleLabel = ddSize.propertyType() == QgsProperty::ExpressionBasedProperty ? ddSize.expressionString() : ddSize.field();

// automatically generated classes
if ( const QgsSizeScaleTransformer *sizeTransformer = dynamic_cast< const QgsSizeScaleTransformer * >( ddSize.transformer() ) )
// automatically generate classes if no classes are defined
if ( sizeTransformer && mSizeClasses.isEmpty() )
{
mSizeClasses.clear();
Q_FOREACH ( double v, QgsSymbolLayerUtils::prettyBreaks( sizeTransformer->minValue(), sizeTransformer->maxValue(), 4 ) )
{
mSizeClasses << SizeClass( sizeTransformer->size( v ), QString::number( v ) );
mSizeClasses << SizeClass( v, QString::number( v ) );
}
}
}
@@ -130,8 +144,16 @@ void QgsDataDefinedSizeLegend::drawCollapsedLegend( QgsRenderContext &context, Q

std::unique_ptr<QgsMarkerSymbol> s( mSymbol->clone() );

// make sure we draw bigger symbols first
QList<SizeClass> classes = mSizeClasses;

// optionally scale size values if transformer is defined
if ( mSizeScaleTransformer )
{
for ( auto it = classes.begin(); it != classes.end(); ++it )
it->size = mSizeScaleTransformer->size( it->size );
}

// make sure we draw bigger symbols first
std::sort( classes.begin(), classes.end(), []( const SizeClass & a, const SizeClass & b ) { return a.size > b.size; } );

int hLengthLine = qRound( context.convertToPainterUnits( hLengthLineMM, QgsUnitTypes::RenderMillimeters ) );
@@ -308,6 +330,15 @@ QgsDataDefinedSizeLegend *QgsDataDefinedSizeLegend::readXml( const QDomElement &
ddsLegend->setSymbol( QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( elemSymbol, context ) );
}

QgsSizeScaleTransformer *transformer = nullptr;
QDomElement elemTransformer = elem.firstChildElement( "transformer" );
if ( !elemTransformer.isNull() )
{
transformer = new QgsSizeScaleTransformer;
transformer->loadVariant( QgsXmlUtils::readVariant( elemTransformer ) );
}
ddsLegend->setSizeScaleTransformer( transformer );

QDomElement elemTextStyle = elem.firstChildElement( "text-style" );
if ( !elemTextStyle.isNull() )
{
@@ -351,6 +382,13 @@ void QgsDataDefinedSizeLegend::writeXml( QDomElement &elem, const QgsReadWriteCo
elem.appendChild( elemSymbol );
}

if ( mSizeScaleTransformer )
{
QDomElement elemTransformer = QgsXmlUtils::writeVariant( mSizeScaleTransformer->toVariant(), doc );
elemTransformer.setTagName( "transformer" );
elem.appendChild( elemTransformer );
}

QDomElement elemFont = doc.createElement( "font" );
elemFont.setAttribute( "family", mFont.family() );
elemFont.setAttribute( "size", mFont.pointSize() );
@@ -26,6 +26,7 @@ class QgsMarkerSymbol;
class QgsProperty;
class QgsReadWriteContext;
class QgsRenderContext;
class QgsSizeScaleTransformer;


/** \ingroup core
@@ -63,7 +64,7 @@ class CORE_EXPORT QgsDataDefinedSizeLegend
{
SizeClass( double size, const QString &label ): size( size ), label( label ) {}

double size; //!< Marker size in units used by the symbol (usually millimeters)
double size; //!< Marker size in units used by the symbol (usually millimeters). May be further scaled before rendering if size scale transformer is enabled.
QString label; //!< Label to be shown with the particular symbol size
};

@@ -77,6 +78,11 @@ class CORE_EXPORT QgsDataDefinedSizeLegend
//! Returns marker symbol that will be used to draw markers in legend
QgsMarkerSymbol *symbol() const;

//! Sets transformer for scaling of symbol sizes. Takes ownership of the object. Accepts null pointer to set no transformer.
void setSizeScaleTransformer( QgsSizeScaleTransformer *transformer SIP_TRANSFER );
//! Returns transformer for scaling of symbol sizes. Returns null if no transformer is defined.
QgsSizeScaleTransformer *sizeScaleTransformer() const;

//! Sets list of classes: each class is a pair of symbol size (in units used by the symbol) and label
void setClasses( const QList<QgsDataDefinedSizeLegend::SizeClass> &classes ) { mSizeClasses = classes; }
//! Returns list of classes: each class is a pair of symbol size (in units used by the symbol) and label
@@ -134,6 +140,7 @@ class CORE_EXPORT QgsDataDefinedSizeLegend
QString mTitleLabel; //!< Title label for the following size-based item(s)
QList<SizeClass> mSizeClasses; //!< List of classes: symbol size (in whatever units symbol uses) + label
std::unique_ptr<QgsMarkerSymbol> mSymbol;
std::unique_ptr<QgsSizeScaleTransformer> mSizeScaleTransformer; //!< Optional transformer for classes
VerticalAlignment mVAlign = AlignBottom;
QFont mFont;
QColor mTextColor = Qt::black;
@@ -789,17 +789,26 @@ QList< QgsLayerTreeModelLegendNode * > QgsLinearlyInterpolatedDiagramRenderer::l
QgsDataDefinedSizeLegend ddSizeLegend( *mDataDefinedSizeLegend );
ddSizeLegend.setSymbol( legendSymbol ); // transfers ownership

QList<QgsDataDefinedSizeLegend::SizeClass> sizeClasses;
if ( ddSizeLegend.classes().isEmpty() )
{
// automatic class creation if the classes are not defined manually
QList<QgsDataDefinedSizeLegend::SizeClass> sizeClasses;
Q_FOREACH ( double v, QgsSymbolLayerUtils::prettyBreaks( mInterpolationSettings.lowerValue, mInterpolationSettings.upperValue, 4 ) )
{
double size = mDiagram->legendSize( v, mSettings, mInterpolationSettings );
sizeClasses << QgsDataDefinedSizeLegend::SizeClass( size, QString::number( v ) );
}
ddSizeLegend.setClasses( sizeClasses );
}
else
{
// manual classes need to get size scaled because the QgsSizeScaleTransformer is not used in diagrams :-(
Q_FOREACH ( const QgsDataDefinedSizeLegend::SizeClass &sc, ddSizeLegend.classes() )
{
double size = mDiagram->legendSize( sc.size, mSettings, mInterpolationSettings );
sizeClasses << QgsDataDefinedSizeLegend::SizeClass( size, sc.label );
}
}
ddSizeLegend.setClasses( sizeClasses );

Q_FOREACH ( const QgsLegendSymbolItem &si, ddSizeLegend.legendSymbolList() )
{
@@ -226,17 +226,17 @@ class CORE_EXPORT QgsPropertyTransformer

/**
* Loads this transformer from a QVariantMap, wrapped in a QVariant.
* You can use QgsXmlUtils::writeVariant to save it to an XML document.
* You can use QgsXmlUtils::readVariant to read it from an XML document.
*
* \see loadVariant()
* \see toVariant()
*/
virtual bool loadVariant( const QVariant &transformer );

/**
* Saves this transformer to a QVariantMap, wrapped in a QVariant.
* You can use QgsXmlUtils::writeVariant to save it to an XML document.
*
* \see toVariant()
* \see loadVariant()
*/
virtual QVariant toVariant() const;

@@ -16,6 +16,7 @@
#include "qgsdatadefinedsizelegendwidget.h"

#include <QInputDialog>
#include <QStyledItemDelegate>

#include "qgsdatadefinedsizelegend.h"
#include "qgslayertree.h"
@@ -28,6 +29,26 @@
#include "qgssymbolselectordialog.h"
#include "qgsvectorlayer.h"


//! Simple delegate to allow only numeric values
class SizeClassDelegate : public QStyledItemDelegate
{
public:
SizeClassDelegate( QObject *parent )
: QStyledItemDelegate( parent )
{
}

QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const
{
QLineEdit *lineEdit = new QLineEdit( parent );
QDoubleValidator *validator = new QDoubleValidator( 0, 1e6, 1, lineEdit );
lineEdit->setValidator( validator );
return lineEdit;
}
};


QgsDataDefinedSizeLegendWidget::QgsDataDefinedSizeLegendWidget( const QgsDataDefinedSizeLegend *ddsLegend, const QgsProperty &ddSize, QgsMarkerSymbol *overrideSymbol, QgsMapCanvas *canvas, QWidget *parent )
: QgsPanelWidget( parent )
, mSizeProperty( ddSize )
@@ -74,26 +95,30 @@ QgsDataDefinedSizeLegendWidget::QgsDataDefinedSizeLegendWidget( const QgsDataDef
QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mSourceSymbol.get(), btnChangeSymbol->iconSize() );
btnChangeSymbol->setIcon( icon );

editTitle->setText( ddsLegend ? ddsLegend->title() : QString() );

mSizeClassesModel = new QStandardItemModel( viewSizeClasses );
mSizeClassesModel->setHorizontalHeaderLabels( QStringList() << tr( "Value" ) );
mSizeClassesModel->setHorizontalHeaderLabels( QStringList() << tr( "Value" ) << tr( "Label" ) );
mSizeClassesModel->setSortRole( Qt::UserRole + 1 );
if ( ddsLegend )
{
groupManualSizeClasses->setChecked( !ddsLegend->classes().isEmpty() );
Q_FOREACH ( const QgsDataDefinedSizeLegend::SizeClass &sc, ddsLegend->classes() )
{
QStandardItem *item = new QStandardItem( sc.label );
item->setEditable( false );
item->setData( sc.label.toInt() );
mSizeClassesModel->appendRow( item );
QStandardItem *item = new QStandardItem( QString::number( sc.size ) );
item->setData( sc.size );
QStandardItem *itemLabel = new QStandardItem( sc.label );
mSizeClassesModel->appendRow( QList<QStandardItem *>() << item << itemLabel );
}
mSizeClassesModel->sort( 0 );
}

connect( btnAddClass, &QToolButton::clicked, this, &QgsDataDefinedSizeLegendWidget::addSizeClass );
connect( btnRemoveClass, &QToolButton::clicked, this, &QgsDataDefinedSizeLegendWidget::removeSizeClass );

viewSizeClasses->setItemDelegateForColumn( 0, new SizeClassDelegate( viewSizeClasses ) );
viewSizeClasses->setModel( mSizeClassesModel );
connect( mSizeClassesModel, &QStandardItemModel::dataChanged, this, &QgsDataDefinedSizeLegendWidget::onSizeClassesChanged );

// prepare layer and model to preview legend
mPreviewLayer = new QgsVectorLayer( "Point?crs=EPSG:4326", "Preview", "memory" );
@@ -110,6 +135,7 @@ QgsDataDefinedSizeLegendWidget::QgsDataDefinedSizeLegendWidget( const QgsDataDef
connect( radCollapsed, &QRadioButton::clicked, this, &QgsPanelWidget::widgetChanged );
connect( groupManualSizeClasses, &QGroupBox::clicked, this, &QgsPanelWidget::widgetChanged );
connect( btnChangeSymbol, &QPushButton::clicked, this, &QgsDataDefinedSizeLegendWidget::changeSymbol );
connect( editTitle, &QLineEdit::textChanged, this, &QgsPanelWidget::widgetChanged );
connect( this, &QgsPanelWidget::widgetChanged, this, &QgsDataDefinedSizeLegendWidget::updatePreview );
updatePreview();
}
@@ -133,15 +159,17 @@ QgsDataDefinedSizeLegend *QgsDataDefinedSizeLegendWidget::dataDefinedSizeLegend(
{
ddsLegend->setSymbol( mSourceSymbol->clone() );
}

ddsLegend->setTitle( editTitle->text() );

if ( groupManualSizeClasses->isChecked() )
{
const QgsSizeScaleTransformer *transformer = dynamic_cast<const QgsSizeScaleTransformer *>( mSizeProperty.transformer() );
QList<QgsDataDefinedSizeLegend::SizeClass> classes;
for ( int i = 0; i < mSizeClassesModel->rowCount(); ++i )
{
double value = mSizeClassesModel->data( mSizeClassesModel->index( i, 0 ), Qt::UserRole + 1 ).toDouble();
double size = transformer ? transformer->size( value ) : value;
classes << QgsDataDefinedSizeLegend::SizeClass( size, QString::number( value ) );
double value = mSizeClassesModel->item( i, 0 )->data().toDouble();
QString label = mSizeClassesModel->item( i, 1 )->text();
classes << QgsDataDefinedSizeLegend::SizeClass( value, label );
}
ddsLegend->setClasses( classes );
}
@@ -199,9 +227,9 @@ void QgsDataDefinedSizeLegendWidget::addSizeClass()
return;

QStandardItem *item = new QStandardItem( QString::number( v ) );
item->setEditable( false );
item->setData( v );
mSizeClassesModel->appendRow( item );
QStandardItem *itemLabel = new QStandardItem( QString::number( v ) );
mSizeClassesModel->appendRow( QList<QStandardItem *>() << item << itemLabel );
mSizeClassesModel->sort( 0 );
emit widgetChanged();
}
@@ -215,3 +243,15 @@ void QgsDataDefinedSizeLegendWidget::removeSizeClass()
mSizeClassesModel->removeRow( idx.row() );
emit widgetChanged();
}

void QgsDataDefinedSizeLegendWidget::onSizeClassesChanged()
{
for ( int row = 0; row < mSizeClassesModel->rowCount(); ++row )
{
QStandardItem *item = mSizeClassesModel->item( row, 0 );
item->setData( item->text().toDouble() );
}

mSizeClassesModel->sort( 0 );
emit widgetChanged();
}
@@ -62,6 +62,7 @@ class GUI_EXPORT QgsDataDefinedSizeLegendWidget : public QgsPanelWidget, private
void changeSymbol();
void addSizeClass();
void removeSizeClass();
void onSizeClassesChanged();

private:
std::unique_ptr<QgsMarkerSymbol> mSourceSymbol; //!< Source symbol (without data-defined size set)

0 comments on commit 81653d6

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