[FEATURE] Rendering of data-defined size in legend in "collapsed" mode

Instead of having different marker sizes in legend as separate legend nodes,
the new "collapsed" mode packs all sizes into one legend node.

This commit only makes it available in the API, not exposed in GUI yet.
This commit is contained in:
Martin Dobias 2017-06-15 16:47:56 +02:00
parent a8999639c7
commit 8c4d5bbb95
23 changed files with 949 additions and 78 deletions

View File

@ -36,6 +36,7 @@
%Include qgscoordinatetransform.sip
%Include qgscredentials.sip
%Include qgscrscache.sip
%Include qgsdatadefinedsizelegend.sip
%Include qgsdataitem.sip
%Include qgsdataitemprovider.sip
%Include qgsdataitemproviderregistry.sip

View File

@ -151,6 +151,12 @@ Emitted on internal data change so the layer tree model can forward the signal t
Construct the node with pointer to its parent layer node
%End
QgsRenderContext *createTemporaryRenderContext() const /Factory/;
%Docstring
Returns a temporary context or null if legendMapViewData are not valid
:rtype: QgsRenderContext
%End
protected:
};
@ -371,6 +377,30 @@ class QgsWmsLegendNode : QgsLayerTreeModelLegendNode
};
class QgsDataDefinedSizeLegendNode : QgsLayerTreeModelLegendNode
{
%Docstring
Produces legend node with a marker symbol
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayertreemodellegendnode.h"
%End
public:
QgsDataDefinedSizeLegendNode( QgsLayerTreeLayer *nodeLayer, const QgsDataDefinedSizeLegend &settings, QObject *parent /TransferThis/ = 0 );
%Docstring
Construct the node using QgsDataDefinedSizeLegend as definition of the node's appearance
%End
virtual QVariant data( int role ) const;
virtual ItemMetrics draw( const QgsLegendSettings &settings, ItemContext *ctx );
};
/************************************************************************
* This file has been generated automatically from *
* *

View File

@ -760,6 +760,19 @@ Returns list with all diagram settings in the renderer
virtual QList< QgsLayerTreeModelLegendNode * > legendItems( QgsLayerTreeLayer *nodeLayer ) const /Factory/;
void setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings /Transfer/ );
%Docstring
Configures appearance of legend. Takes ownership of the passed settings objects.
.. versionadded:: 3.0
%End
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
%Docstring
Returns configuration of appearance of legend. Will return null if no configuration has been set.
.. versionadded:: 3.0
:rtype: QgsDataDefinedSizeLegend
%End
protected:
virtual bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const;
@ -767,6 +780,8 @@ Returns list with all diagram settings in the renderer
virtual QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const;
QgsLinearlyInterpolatedDiagramRenderer( const QgsLinearlyInterpolatedDiagramRenderer &other );
};
/************************************************************************

View File

@ -78,6 +78,7 @@ class QgsCategorizedSymbolRenderer : QgsFeatureRenderer
public:
QgsCategorizedSymbolRenderer( const QString &attrName = QString(), const QgsCategoryList &categories = QgsCategoryList() );
~QgsCategorizedSymbolRenderer();
virtual QgsSymbol *symbolForFeature( QgsFeature &feature, QgsRenderContext &context );
virtual QgsSymbol *originalSymbolForFeature( QgsFeature &feature, QgsRenderContext &context );
@ -220,10 +221,31 @@ create renderer from XML element
:rtype: QgsCategorizedSymbolRenderer
%End
void setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings /Transfer/ );
%Docstring
Configures appearance of legend when renderer is configured to use data-defined size for marker symbols.
This allows to configure for what values (symbol sizes) should be shown in the legend, whether to display
different symbol sizes collapsed in one legend node or separated across multiple legend nodes etc.
When renderer does not use data-defined size or does not use marker symbols, these settings will be ignored.
Takes ownership of the passed settings objects. Null pointer is a valid input that disables data-defined
size legend.
.. versionadded:: 3.0
%End
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
%Docstring
Returns configuration of appearance of legend when using data-defined size for marker symbols.
Will return null if the functionality is disabled.
.. versionadded:: 3.0
:rtype: QgsDataDefinedSizeLegend
%End
protected:
void rebuildHash();
%Docstring
hashtable for faster access to symbols

View File

@ -7,6 +7,7 @@
************************************************************************/
class QgsRendererRange
{
@ -405,9 +406,30 @@ create renderer from XML element
:rtype: QgsGraduatedSymbolRenderer
%End
void setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings /Transfer/ );
%Docstring
Configures appearance of legend when renderer is configured to use data-defined size for marker symbols.
This allows to configure for what values (symbol sizes) should be shown in the legend, whether to display
different symbol sizes collapsed in one legend node or separated across multiple legend nodes etc.
When renderer does not use data-defined size or does not use marker symbols, these settings will be ignored.
Takes ownership of the passed settings objects. Null pointer is a valid input that disables data-defined
size legend.
.. versionadded:: 3.0
%End
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
%Docstring
Returns configuration of appearance of legend when using data-defined size for marker symbols.
Will return null if the functionality is disabled.
.. versionadded:: 3.0
:rtype: QgsDataDefinedSizeLegend
%End
protected:
QgsSymbol *symbolForValue( double value );
%Docstring
attribute index (derived from attribute name in startRender)

View File

@ -94,6 +94,22 @@ Identation level that tells how deep the item is in a hierarchy of items. For fl
Set symbol of the item. Takes ownership of symbol.
%End
void setDataDefinedSizeLegendSettings( QgsDataDefinedSizeLegend *settings /Transfer/ );
%Docstring
Sets extra information about data-defined size. If set, this item should be converted to QgsDataDefinedSizeLegendNode
rather than QgsSymbolLegendNode instance as usual. Passing null removes any data-defined size legend settings.
Takes ownership of the settings object.
.. versionadded:: 3.0
%End
QgsDataDefinedSizeLegend *dataDefinedSizeLegendSettings() const;
%Docstring
Returns extra information for data-defined size legend rendering. Normally it returns null.
.. versionadded:: 3.0
:rtype: QgsDataDefinedSizeLegend
%End
};

View File

@ -7,6 +7,7 @@
************************************************************************/
class QgsSingleSymbolRenderer : QgsFeatureRenderer
{
@ -16,6 +17,7 @@ class QgsSingleSymbolRenderer : QgsFeatureRenderer
public:
QgsSingleSymbolRenderer( QgsSymbol *symbol /Transfer/ );
~QgsSingleSymbolRenderer();
virtual QgsSymbol *symbolForFeature( QgsFeature &feature, QgsRenderContext &context );
virtual QgsSymbol *originalSymbolForFeature( QgsFeature &feature, QgsRenderContext &context );
@ -58,6 +60,26 @@ create renderer from XML element
:rtype: QgsSingleSymbolRenderer
%End
void setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings /Transfer/ );
%Docstring
Configures appearance of legend when renderer is configured to use data-defined size for marker symbols.
This allows to configure for what values (symbol sizes) should be shown in the legend, whether to display
different symbol sizes collapsed in one legend node or separated across multiple legend nodes etc.
When renderer does not use data-defined size or does not use marker symbols, these settings will be ignored.
Takes ownership of the passed settings objects. Null pointer is a valid input that disables data-defined
size legend.
.. versionadded:: 3.0
%End
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
%Docstring
Returns configuration of appearance of legend when using data-defined size for marker symbols.
Will return null if the functionality is disabled.
.. versionadded:: 3.0
:rtype: QgsDataDefinedSizeLegend
%End
protected:
private:

View File

@ -141,6 +141,7 @@ SET(QGIS_CORE_SRCS
qgscredentials.cpp
qgscrscache.cpp
qgsdartmeasurement.cpp
qgsdatadefinedsizelegend.cpp
qgsdataitem.cpp
qgsdataitemprovider.cpp
qgsdataitemproviderregistry.cpp
@ -738,6 +739,7 @@ SET(QGIS_CORE_HDRS
qgscrscache.h
qgscsexception.h
qgsdartmeasurement.h
qgsdatadefinedsizelegend.h
qgsdataitem.h
qgsdataitemprovider.h
qgsdataitemproviderregistry.h

View File

@ -18,6 +18,7 @@
#include "qgslayertreemodellegendnode.h"
#include "qgsdatadefinedsizelegend.h"
#include "qgslayertree.h"
#include "qgslayertreemodel.h"
#include "qgslegendsettings.h"
@ -215,22 +216,23 @@ void QgsSymbolLegendNode::uncheckAllItems()
checkAll( false );
}
inline
QgsRenderContext *QgsSymbolLegendNode::createTemporaryRenderContext() const
QgsRenderContext *QgsLayerTreeModelLegendNode::createTemporaryRenderContext() const
{
double scale = 0.0;
double mupp = 0.0;
int dpi = 0;
if ( model() )
model()->legendMapViewData( &mupp, &dpi, &scale );
bool validData = !qgsDoubleNear( mupp, 0.0 ) && dpi != 0 && !qgsDoubleNear( scale, 0.0 );
if ( qgsDoubleNear( mupp, 0.0 ) || dpi == 0 || qgsDoubleNear( scale, 0.0 ) )
return nullptr;
// setup temporary render context
std::unique_ptr<QgsRenderContext> context( new QgsRenderContext );
QgsRenderContext *context = new QgsRenderContext;
context->setScaleFactor( dpi / 25.4 );
context->setRendererScale( scale );
context->setMapToPixel( QgsMapToPixel( mupp ) );
return validData ? context.release() : nullptr;
return context;
}
void QgsSymbolLegendNode::checkAll( bool state )
@ -718,3 +720,71 @@ void QgsWmsLegendNode::invalidateMapBasedData()
mValid = false;
emit dataChanged();
}
// -------------------------------------------------------------------------
QgsDataDefinedSizeLegendNode::QgsDataDefinedSizeLegendNode( QgsLayerTreeLayer *nodeLayer, const QgsDataDefinedSizeLegend &settings, QObject *parent )
: QgsLayerTreeModelLegendNode( nodeLayer, parent )
, mSettings( new QgsDataDefinedSizeLegend( settings ) )
{
}
QVariant QgsDataDefinedSizeLegendNode::data( int role ) const
{
if ( role == Qt::DecorationRole )
{
cacheImage();
return QPixmap::fromImage( mImage );
}
else if ( role == Qt::SizeHintRole )
{
cacheImage();
return mImage.size();
}
return QVariant();
}
QgsLayerTreeModelLegendNode::ItemMetrics QgsDataDefinedSizeLegendNode::draw( const QgsLegendSettings &settings, QgsLayerTreeModelLegendNode::ItemContext *ctx )
{
// setup temporary render context
QgsRenderContext context;
context.setScaleFactor( settings.dpi() / 25.4 );
context.setRendererScale( settings.mapScale() );
context.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * context.scaleFactor() ) ) );
context.setForceVectorOutput( true );
if ( ctx )
{
context.setPainter( ctx->painter );
ctx->painter->save();
ctx->painter->setRenderHint( QPainter::Antialiasing );
ctx->painter->translate( ctx->point );
ctx->painter->scale( 1 / context.scaleFactor(), 1 / context.scaleFactor() );
}
QgsDataDefinedSizeLegend ddsLegend( *mSettings );
ddsLegend.setFont( settings.style( QgsLegendStyle::SymbolLabel ).font() );
ddsLegend.setTextColor( settings.fontColor() );
QSize contentSize;
int labelXOffset;
ddsLegend.drawCollapsedLegend( context, &contentSize, &labelXOffset );
if ( ctx )
ctx->painter->restore();
ItemMetrics im;
im.symbolSize = QSizeF( ( contentSize.width() - labelXOffset ) / context.scaleFactor(), contentSize.height() / context.scaleFactor() );
im.labelSize = QSizeF( labelXOffset / context.scaleFactor(), contentSize.height() / context.scaleFactor() );
return im;
}
void QgsDataDefinedSizeLegendNode::cacheImage() const
{
if ( mImage.isNull() )
{
std::unique_ptr<QgsRenderContext> context( createTemporaryRenderContext() );
mImage = mSettings->collapsedLegendImage( *context.get() );
}
}

View File

@ -131,6 +131,9 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
//! Construct the node with pointer to its parent layer node
explicit QgsLayerTreeModelLegendNode( QgsLayerTreeLayer *nodeL, QObject *parent SIP_TRANSFERTHIS = nullptr );
//! Returns a temporary context or null if legendMapViewData are not valid
QgsRenderContext *createTemporaryRenderContext() const SIP_FACTORY;
protected:
QgsLayerTreeLayer *mLayerNode = nullptr;
bool mEmbeddedInParent;
@ -237,9 +240,6 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
// ident the symbol icon to make it look like a tree structure
static const int INDENT_SIZE = 20;
// return a temporary context or null if legendMapViewData are not valid
QgsRenderContext *createTemporaryRenderContext() const;
/** Sets all items belonging to the same layer as this node to the same check state.
* \param state check state
*/
@ -380,4 +380,27 @@ class CORE_EXPORT QgsWmsLegendNode : public QgsLayerTreeModelLegendNode
mutable std::unique_ptr<QgsImageFetcher> mFetcher;
};
/**
* \ingroup core
* Produces legend node with a marker symbol
* \since QGIS 3.0
*/
class CORE_EXPORT QgsDataDefinedSizeLegendNode : public QgsLayerTreeModelLegendNode
{
public:
//! Construct the node using QgsDataDefinedSizeLegend as definition of the node's appearance
QgsDataDefinedSizeLegendNode( QgsLayerTreeLayer *nodeLayer, const QgsDataDefinedSizeLegend &settings, QObject *parent SIP_TRANSFERTHIS = nullptr );
virtual QVariant data( int role ) const override;
ItemMetrics draw( const QgsLegendSettings &settings, ItemContext *ctx ) override;
private:
void cacheImage() const;
std::unique_ptr<QgsDataDefinedSizeLegend> mSettings;
mutable QImage mImage;
};
#endif // QGSLAYERTREEMODELLEGENDNODE_H

View File

@ -0,0 +1,289 @@
/***************************************************************************
qgsdatadefinedsizelegend.cpp
--------------------------------------
Date : June 2017
Copyright : (C) 2017 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsdatadefinedsizelegend.h"
#include "qgsproperty.h"
#include "qgspropertytransformer.h"
#include "qgssymbollayerutils.h"
QgsDataDefinedSizeLegend::QgsDataDefinedSizeLegend()
{
}
QgsDataDefinedSizeLegend::QgsDataDefinedSizeLegend( const QgsDataDefinedSizeLegend &other )
: mType( other.mType )
, mTitleLabel( other.mTitleLabel )
, mSizeClasses( other.mSizeClasses )
, mSymbol( other.mSymbol.get() ? other.mSymbol->clone() : nullptr )
{
}
QgsDataDefinedSizeLegend &QgsDataDefinedSizeLegend::operator=( const QgsDataDefinedSizeLegend &other )
{
if ( this != &other )
{
mType = other.mType;
mTitleLabel = other.mTitleLabel;
mSizeClasses = other.mSizeClasses;
mSymbol.reset( other.mSymbol.get() ? other.mSymbol->clone() : nullptr );
}
return *this;
}
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();
// automatically generated classes
if ( const QgsSizeScaleTransformer *sizeTransformer = dynamic_cast< const QgsSizeScaleTransformer * >( ddSize.transformer() ) )
{
mSizeClasses.clear();
Q_FOREACH ( double v, QgsSymbolLayerUtils::prettyBreaks( sizeTransformer->minValue(), sizeTransformer->maxValue(), 4 ) )
{
mSizeClasses << SizeClass( sizeTransformer->size( v ), QString::number( v ) );
}
}
}
QgsLegendSymbolList QgsDataDefinedSizeLegend::legendSymbolList() const
{
QgsLegendSymbolList lst;
if ( !mTitleLabel.isEmpty() )
{
QgsLegendSymbolItem title( nullptr, mTitleLabel, QString() );
lst << title;
}
if ( mType == LegendCollapsed )
{
QgsLegendSymbolItem i;
i.setDataDefinedSizeLegendSettings( new QgsDataDefinedSizeLegend( *this ) );
lst << i;
return lst;
}
else if ( mType == LegendSeparated )
{
Q_FOREACH ( const SizeClass &cl, mSizeClasses )
{
QgsLegendSymbolItem si( mSymbol.get(), cl.label, QString() );
QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( si.symbol() );
s->setSize( cl.size );
lst << si;
}
}
return lst;
}
void QgsDataDefinedSizeLegend::drawCollapsedLegend( QgsRenderContext &context, QSize *outputSize, int *labelXOffset ) const
{
if ( mType != LegendCollapsed || mSizeClasses.isEmpty() || !mSymbol )
{
if ( outputSize )
*outputSize = QSize();
if ( labelXOffset )
*labelXOffset = 0;
return;
}
// parameters that could be configurable
double hLengthLineMM = 2; // extra horizontal space to be occupied by callout line
double hSpaceLineTextMM = 1; // horizontal space between end of the line and start of the text
std::unique_ptr<QgsMarkerSymbol> s( mSymbol->clone() );
// make sure we draw bigger symbols first
QList<SizeClass> classes = mSizeClasses;
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 ) );
int hSpaceLineText = qRound( context.convertToPainterUnits( hSpaceLineTextMM, QgsUnitTypes::RenderMillimeters ) );
int dpm = qRound( context.scaleFactor() * 1000 ); // scale factor = dots per millimeter
// get font metrics - we need a temporary image just to get the metrics right for the given DPI
QImage tmpImg( QSize( 1, 1 ), QImage::Format_ARGB32_Premultiplied );
tmpImg.setDotsPerMeterX( dpm );
tmpImg.setDotsPerMeterY( dpm );
QFontMetrics fm( mFont, &tmpImg );
int textHeight = fm.height();
int leading = fm.leading();
int minTextDistY = textHeight + leading;
//
// determine layout of the rendered elements
//
// find out how wide the text will be
int maxTextWidth = 0;
Q_FOREACH ( const SizeClass &c, classes )
{
int w = fm.width( c.label );
if ( w > maxTextWidth )
maxTextWidth = w;
}
// find out size of the largest symbol
double largestSize = classes.at( 0 ).size;
double outputLargestSize = context.convertToPainterUnits( largestSize, s->sizeUnit(), s->sizeMapUnitScale() );
// find out top Y coordinate for individual symbol sizes
QList<int> symbolTopY;
Q_FOREACH ( const SizeClass &c, classes )
{
double outputSymbolSize = context.convertToPainterUnits( c.size, s->sizeUnit(), s->sizeMapUnitScale() );
switch ( mVAlign )
{
case AlignCenter:
symbolTopY << qRound( outputLargestSize / 2 - outputSymbolSize / 2 );
break;
case AlignBottom:
symbolTopY << qRound( outputLargestSize - outputSymbolSize );
break;
}
}
// determine Y coordinate of texts: ideally they should be at the same level as symbolTopY
// but we need to avoid overlapping texts, so adjust the vertical positions
int middleIndex = 0; // classes.count() / 2; // will get the ideal position
QList<int> textCenterY;
int lastY = symbolTopY[middleIndex];
textCenterY << lastY;
for ( int i = middleIndex + 1; i < classes.count(); ++i )
{
int symbolY = symbolTopY[i];
if ( symbolY - lastY < minTextDistY )
symbolY = lastY + minTextDistY;
textCenterY << symbolY;
lastY = symbolY;
}
int textTopY = textCenterY.first() - textHeight / 2;
int textBottomY = textCenterY.last() + textHeight / 2;
int totalTextHeight = textBottomY - textTopY;
int fullWidth = outputLargestSize + hLengthLine + hSpaceLineText + maxTextWidth;
int fullHeight = qMax( qRound( outputLargestSize ) - textTopY, totalTextHeight );
if ( outputSize )
*outputSize = QSize( fullWidth, fullHeight );
if ( labelXOffset )
*labelXOffset = outputLargestSize + hLengthLine + hSpaceLineText;
if ( !context.painter() )
return; // only layout
//
// drawing
//
QPainter *p = context.painter();
p->save();
p->translate( 0, -textTopY );
// draw symbols first so that they do not cover
Q_FOREACH ( const SizeClass &c, classes )
{
s->setSize( c.size );
double outputSymbolSize = context.convertToPainterUnits( c.size, s->sizeUnit(), s->sizeMapUnitScale() );
double tx = ( outputLargestSize - outputSymbolSize ) / 2;
p->save();
switch ( mVAlign )
{
case AlignCenter:
p->translate( tx, ( outputLargestSize - outputSymbolSize ) / 2 );
break;
case AlignBottom:
p->translate( tx, outputLargestSize - outputSymbolSize );
break;
}
s->drawPreviewIcon( p, QSize( outputSymbolSize, outputSymbolSize ) );
p->restore();
}
p->setPen( mTextColor );
p->setFont( mFont );
int i = 0;
Q_FOREACH ( const SizeClass &c, classes )
{
// line from symbol to the text
p->drawLine( outputLargestSize / 2, symbolTopY[i], outputLargestSize + hLengthLine, textCenterY[i] );
// draw label
QRect rect( outputLargestSize + hLengthLine + hSpaceLineText, textCenterY[i] - textHeight / 2,
maxTextWidth, textHeight );
p->drawText( rect, mTextAlignment, c.label );
i++;
}
p->restore();
}
QImage QgsDataDefinedSizeLegend::collapsedLegendImage( QgsRenderContext &context, double paddingMM ) const
{
if ( mType != LegendCollapsed || mSizeClasses.isEmpty() || !mSymbol )
return QImage();
// find out the size first
QSize contentSize;
drawCollapsedLegend( context, &contentSize );
int padding = qRound( context.convertToPainterUnits( paddingMM, QgsUnitTypes::RenderMillimeters ) );
int dpm = qRound( context.scaleFactor() * 1000 ); // scale factor = dots per millimeter
QImage img( contentSize.width() + padding * 2, contentSize.height() + padding * 2, QImage::Format_ARGB32_Premultiplied );
img.setDotsPerMeterX( dpm );
img.setDotsPerMeterY( dpm );
img.fill( Qt::transparent );
QPainter painter( &img );
painter.setRenderHint( QPainter::Antialiasing, true );
painter.translate( padding, padding ); // so we do not need to care about padding at all
// now do the rendering
QPainter *oldPainter = context.painter();
context.setPainter( &painter );
drawCollapsedLegend( context );
context.setPainter( oldPainter );
painter.end();
return img;
}
QgsDataDefinedSizeLegend *QgsDataDefinedSizeLegend::readTypeAndAlignmentFromXml( const QDomElement &elem )
{
if ( elem.isNull() )
return nullptr;
QgsDataDefinedSizeLegend *ddsLegend = new QgsDataDefinedSizeLegend;
ddsLegend->setLegendType( elem.attribute( "type" ) == "collapsed" ? LegendCollapsed : LegendSeparated );
ddsLegend->setVerticalAlignment( elem.attribute( "valign" ) == "center" ? AlignCenter : AlignBottom );
return ddsLegend;
}
void QgsDataDefinedSizeLegend::writeTypeAndAlignmentToXml( const QgsDataDefinedSizeLegend &ddsLegend, QDomElement &elem )
{
elem.setAttribute( "type", ddsLegend.legendType() == LegendCollapsed ? "collapsed" : "separated" );
elem.setAttribute( "valign", ddsLegend.verticalAlignment() == AlignCenter ? "center" : "bottom" );
}

View File

@ -0,0 +1,145 @@
/***************************************************************************
qgsdatadefinedsizelegend.cpp
--------------------------------------
Date : June 2017
Copyright : (C) 2017 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSDATADEFINEDSIZELEGEND_H
#define QGSDATADEFINEDSIZELEGEND_H
#include "qgslegendsymbolitem.h"
#include <QColor>
#include <QFont>
class QDomElement;
class QgsMarkerSymbol;
class QgsProperty;
class QgsRenderContext;
/** \ingroup core
* Object that keeps configuration of appearance of marker symbol's data-defined size in legend.
* For example: the list of classes (size values), whether the classes should appear in separate
* legend nodes or whether to collapse them into one legend node.
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsDataDefinedSizeLegend
{
public:
QgsDataDefinedSizeLegend();
QgsDataDefinedSizeLegend( const QgsDataDefinedSizeLegend &other );
QgsDataDefinedSizeLegend &operator=( const QgsDataDefinedSizeLegend &other );
//! Determines how to display data-defined size legend
enum LegendType
{
LegendSeparated, //!< Each class (size value) has a separate legend node
LegendCollapsed, //!< All classes are rendered within one legend node
};
//! How to vertically align symbols when all classes go into one node
enum VerticalAlignment
{
AlignCenter, //!< Symbols are aligned to the center
AlignBottom, //!< Symbols are aligned to the bottom
};
//! Definition of one class for the legend
struct SizeClass
{
SizeClass( double size, const QString &label ): size( size ), label( label ) {}
double size; //!< Marker size in units used by the symbol (usually millimeters)
QString label; //!< Label to be shown with the particular symbol size
};
//! Sets how the legend should be rendered
void setLegendType( LegendType type ) { mType = type; }
//! Returns how the legend should be rendered
LegendType legendType() const { return mType; }
//! Sets marker symbol that will be used to draw markers in legend
void setSymbol( QgsMarkerSymbol *symbol SIP_TRANSFER ) { mSymbol.reset( symbol ); }
//! Returns marker symbol that will be used to draw markers in legend
QgsMarkerSymbol *symbol() const { return mSymbol.get(); }
//! 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
QList<QgsDataDefinedSizeLegend::SizeClass> classes() const { return mSizeClasses; }
//! Sets title label for data-defined size legend
void setTitle( const QString &title ) { mTitleLabel = title; }
//! Returns title label for data-defined size legend
QString title() const { return mTitleLabel; }
//! Sets vertical alignment of symbols - only valid for collapsed legend
void setVerticalAlignment( VerticalAlignment vAlign ) { mVAlign = vAlign; }
//! Returns vertical alignment of symbols - only valid for collapsed legend
VerticalAlignment verticalAlignment() const { return mVAlign; }
//! Sets font used for rendering of labels - only valid for collapsed legend
void setFont( const QFont &font ) { mFont = font; }
//! Returns font used for rendering of labels - only valid for collapsed legend
QFont font() const { return mFont; }
//! Sets text color for rendering of labels - only valid for collapsed legend
void setTextColor( const QColor &color ) { mTextColor = color; }
//! Returns text color for rendering of labels - only valid for collapsed legend
QColor textColor() const { return mTextColor; }
//! Sets horizontal text alignment for rendering of labels - only valid for collapsed legend
void setTextAlignment( Qt::AlignmentFlag flag ) { mTextAlignment = flag; }
//! Returns horizontal text alignment for rendering of labels - only valid for collapsed legend
Qt::AlignmentFlag textAlignment() const { return mTextAlignment; }
//
//! Updates the list of classes, source symbol and title label from given symbol and property
void updateFromSymbolAndProperty( const QgsMarkerSymbol *symbol, const QgsProperty &ddSize );
//! Generates legend symbol items according to the configuration
QgsLegendSymbolList legendSymbolList() const;
//! Draw the legend if using LegendOneNodeForAll and optionally output size of the legend and x offset of labels (in painter units).
//! If the painter in context is null, it only does size calculation without actual rendering.
//! Does nothing if legend is not configured as collapsed.
void drawCollapsedLegend( QgsRenderContext &context, QSize *outputSize SIP_OUT = nullptr, int *labelXOffset SIP_OUT = nullptr ) const;
//! Returns output image that would be shown in the legend. Returns invalid image if legend is not configured as collapsed.
QImage collapsedLegendImage( QgsRenderContext &context, double paddingMM = 1 ) const;
//! Creates instance from given element and returns it (caller takes ownership). Returns null on error.
//! \note only reads legend type and vertical alignment of symbols
//! \note This is a temporary method and may be removed in the future
static QgsDataDefinedSizeLegend *readTypeAndAlignmentFromXml( const QDomElement &elem ) SIP_FACTORY;
//! Writes configuration to the given XML element.
//! \note only writes legend type and vertical alignment of symbols
//! \note This is a temporary method and may be removed in the future
static void writeTypeAndAlignmentToXml( const QgsDataDefinedSizeLegend &ddsLegend, QDomElement &elem );
private:
LegendType mType = LegendSeparated;
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;
VerticalAlignment mVAlign = AlignBottom;
QFont mFont;
QColor mTextColor = Qt::black;
Qt::AlignmentFlag mTextAlignment = Qt::AlignLeft;
};
#endif // QGSDATADEFINEDSIZELEGEND_H

View File

@ -13,6 +13,8 @@
* *
***************************************************************************/
#include "qgsdiagramrenderer.h"
#include "qgsdatadefinedsizelegend.h"
#include "qgsvectorlayer.h"
#include "diagram/qgstextdiagram.h"
#include "diagram/qgspiediagram.h"
@ -610,11 +612,20 @@ void QgsSingleCategoryDiagramRenderer::writeXml( QDomElement &layerElem, QDomDoc
}
QgsLinearlyInterpolatedDiagramRenderer::QgsLinearlyInterpolatedDiagramRenderer(): QgsDiagramRenderer()
QgsLinearlyInterpolatedDiagramRenderer::QgsLinearlyInterpolatedDiagramRenderer()
: QgsDiagramRenderer()
{
mInterpolationSettings.classificationAttributeIsExpression = false;
}
QgsLinearlyInterpolatedDiagramRenderer::QgsLinearlyInterpolatedDiagramRenderer( const QgsLinearlyInterpolatedDiagramRenderer &other )
: QgsDiagramRenderer( other )
, mSettings( other.mSettings )
, mInterpolationSettings( other.mInterpolationSettings )
, mDataDefinedSizeLegend( other.mDataDefinedSizeLegend ? new QgsDataDefinedSizeLegend( *other.mDataDefinedSizeLegend ) : nullptr )
{
}
QgsLinearlyInterpolatedDiagramRenderer *QgsLinearlyInterpolatedDiagramRenderer::clone() const
{
return new QgsLinearlyInterpolatedDiagramRenderer( *this );
@ -684,6 +695,13 @@ void QgsLinearlyInterpolatedDiagramRenderer::readXml( const QDomElement &elem, c
{
mSettings.readXml( settingsElem );
}
QDomElement ddsLegendSizeElem = elem.firstChildElement( "data-defined-size-legend" );
if ( !ddsLegendSizeElem.isNull() )
{
mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readTypeAndAlignmentFromXml( ddsLegendSizeElem ) );
}
_readXml( elem, context );
}
@ -705,6 +723,14 @@ void QgsLinearlyInterpolatedDiagramRenderer::writeXml( QDomElement &layerElem, Q
rendererElem.setAttribute( QStringLiteral( "classificationField" ), mInterpolationSettings.classificationField );
}
mSettings.writeXml( rendererElem, doc );
if ( mDataDefinedSizeLegend )
{
QDomElement ddsLegendElem = doc.createElement( QStringLiteral( "data-defined-size-legend" ) );
QgsDataDefinedSizeLegend::writeTypeAndAlignmentToXml( *mDataDefinedSizeLegend, ddsLegendElem );
rendererElem.appendChild( ddsLegendElem );
}
_writeXml( rendererElem, doc, context );
layerElem.appendChild( rendererElem );
}
@ -745,17 +771,42 @@ QList< QgsLayerTreeModelLegendNode * > QgsLinearlyInterpolatedDiagramRenderer::l
if ( mShowSizeLegend && mDiagram && mSizeLegendSymbol )
{
// add size legend
QgsMarkerSymbol *legendSymbol = mSizeLegendSymbol.get()->clone();
legendSymbol->setSizeUnit( mSettings.sizeType );
legendSymbol->setSizeMapUnitScale( mSettings.sizeScale );
QList<QgsDataDefinedSizeLegend::SizeClass> sizeClasses;
Q_FOREACH ( double v, QgsSymbolLayerUtils::prettyBreaks( mInterpolationSettings.lowerValue, mInterpolationSettings.upperValue, 4 ) )
{
double size = mDiagram->legendSize( v, mSettings, mInterpolationSettings );
QgsLegendSymbolItem si( mSizeLegendSymbol.get(), QString::number( v ), QString() );
QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( si.symbol() );
s->setSize( size );
s->setSizeUnit( mSettings.sizeType );
s->setSizeMapUnitScale( mSettings.sizeScale );
nodes << new QgsSymbolLegendNode( nodeLayer, si );
sizeClasses << QgsDataDefinedSizeLegend::SizeClass( size, QString::number( v ) );
}
QgsDataDefinedSizeLegend ddSizeLegend;
if ( mDataDefinedSizeLegend ) // copy legend configuration if any...
ddSizeLegend = *mDataDefinedSizeLegend;
ddSizeLegend.setSymbol( legendSymbol ); // transfers ownership
ddSizeLegend.setClasses( sizeClasses );
Q_FOREACH ( const QgsLegendSymbolItem &si, ddSizeLegend.legendSymbolList() )
{
if ( si.dataDefinedSizeLegendSettings() )
nodes << new QgsDataDefinedSizeLegendNode( nodeLayer, *si.dataDefinedSizeLegendSettings() );
else
nodes << new QgsSymbolLegendNode( nodeLayer, si );
}
}
return nodes;
}
void QgsLinearlyInterpolatedDiagramRenderer::setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings )
{
mDataDefinedSizeLegend.reset( settings );
}
QgsDataDefinedSizeLegend *QgsLinearlyInterpolatedDiagramRenderer::dataDefinedSizeLegend() const
{
return mDataDefinedSizeLegend.get();
}

View File

@ -32,6 +32,7 @@
#include "qgspropertycollection.h"
class QgsDataDefinedSizeLegend;
class QgsDiagram;
class QgsDiagramRenderer;
class QgsFeature;
@ -722,14 +723,31 @@ class CORE_EXPORT QgsLinearlyInterpolatedDiagramRenderer : public QgsDiagramRend
QList< QgsLayerTreeModelLegendNode * > legendItems( QgsLayerTreeLayer *nodeLayer ) const override SIP_FACTORY;
/**
* Configures appearance of legend. Takes ownership of the passed settings objects.
* \since QGIS 3.0
*/
void setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings SIP_TRANSFER );
/**
* Returns configuration of appearance of legend. Will return null if no configuration has been set.
* \since QGIS 3.0
*/
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
protected:
bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const override;
QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const override;
QgsLinearlyInterpolatedDiagramRenderer( const QgsLinearlyInterpolatedDiagramRenderer &other );
private:
QgsDiagramSettings mSettings;
QgsDiagramInterpolationSettings mInterpolationSettings;
//! Stores more settings about how legend for varying size of symbols should be rendered
std::unique_ptr<QgsDataDefinedSizeLegend> mDataDefinedSizeLegend;
};
#endif // QGSDIAGRAMRENDERERV2_H

View File

@ -196,8 +196,10 @@ QList<QgsLayerTreeModelLegendNode *> QgsDefaultVectorLayerLegend::createLayerTre
Q_FOREACH ( const QgsLegendSymbolItem &i, r->legendSymbolItems() )
{
QgsSymbolLegendNode *n = new QgsSymbolLegendNode( nodeLayer, i );
nodes.append( n );
if ( i.dataDefinedSizeLegendSettings() )
nodes << new QgsDataDefinedSizeLegendNode( nodeLayer, *i.dataDefinedSizeLegendSettings() );
else
nodes << new QgsSymbolLegendNode( nodeLayer, i );
}
if ( nodes.count() == 1 && nodes[0]->data( Qt::EditRole ).toString().isEmpty() )

View File

@ -16,6 +16,7 @@
#include "qgscategorizedsymbolrenderer.h"
#include "qgsdatadefinedsizelegend.h"
#include "qgssymbol.h"
#include "qgssymbollayerutils.h"
#include "qgscolorramp.h"
@ -167,6 +168,10 @@ QgsCategorizedSymbolRenderer::QgsCategorizedSymbolRenderer( const QString &attrN
}
}
QgsCategorizedSymbolRenderer::~QgsCategorizedSymbolRenderer()
{
}
void QgsCategorizedSymbolRenderer::rebuildHash()
{
mSymbolHash.clear();
@ -653,6 +658,12 @@ QgsFeatureRenderer *QgsCategorizedSymbolRenderer::create( QDomElement &element,
}
}
QDomElement ddsLegendSizeElem = element.firstChildElement( "data-defined-size-legend" );
if ( !ddsLegendSizeElem.isNull() )
{
r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readTypeAndAlignmentFromXml( ddsLegendSizeElem ) );
}
// TODO: symbol levels
return r;
}
@ -727,6 +738,13 @@ QDomElement QgsCategorizedSymbolRenderer::save( QDomDocument &doc, const QgsRead
}
rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? "1" : "0" ) );
if ( mDataDefinedSizeLegend )
{
QDomElement ddsLegendElem = doc.createElement( QStringLiteral( "data-defined-size-legend" ) );
QgsDataDefinedSizeLegend::writeTypeAndAlignmentToXml( *mDataDefinedSizeLegend, ddsLegendElem );
rendererElem.appendChild( ddsLegendElem );
}
return rendererElem;
}
@ -744,8 +762,7 @@ QgsLegendSymbolList QgsCategorizedSymbolRenderer::baseLegendSymbolItems() const
QgsLegendSymbolList QgsCategorizedSymbolRenderer::legendSymbolItems() const
{
QgsLegendSymbolList lst;
if ( mSourceSymbol && mSourceSymbol->type() == QgsSymbol::Marker )
if ( mDataDefinedSizeLegend && mSourceSymbol && mSourceSymbol->type() == QgsSymbol::Marker )
{
// check that all symbols that have the same size expression
QgsProperty ddSize;
@ -767,28 +784,15 @@ QgsLegendSymbolList QgsCategorizedSymbolRenderer::legendSymbolItems() const
}
}
if ( !ddSize || !ddSize.isActive() )
if ( ddSize && ddSize.isActive() )
{
return baseLegendSymbolItems();
}
QgsLegendSymbolList lst;
if ( const QgsSizeScaleTransformer *sizeTransformer = dynamic_cast< const QgsSizeScaleTransformer * >( ddSize.transformer() ) )
{
QgsLegendSymbolItem title( nullptr, ddSize.propertyType() == QgsProperty::ExpressionBasedProperty ? ddSize.expressionString()
: ddSize.field(), QString() );
lst << title;
Q_FOREACH ( double v, QgsSymbolLayerUtils::prettyBreaks( sizeTransformer->minValue(), sizeTransformer->maxValue(), 4 ) )
{
QgsLegendSymbolItem si( mSourceSymbol.get(), QString::number( v ), QString() );
QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( si.symbol() );
s->setDataDefinedSize( QgsProperty() );
s->setSize( sizeTransformer->size( v ) );
lst << si;
}
// now list the categorized symbols
const QgsLegendSymbolList list2 = baseLegendSymbolItems();
Q_FOREACH ( const QgsLegendSymbolItem &item, list2 )
lst << item;
QgsDataDefinedSizeLegend ddSizeLegend( *mDataDefinedSizeLegend );
ddSizeLegend.updateFromSymbolAndProperty( static_cast<const QgsMarkerSymbol *>( mSourceSymbol.get() ), ddSize );
lst += ddSizeLegend.legendSymbolList();
lst += baseLegendSymbolItems();
return lst;
}
}
@ -942,3 +946,13 @@ QgsCategorizedSymbolRenderer *QgsCategorizedSymbolRenderer::convertFromRenderer(
return r;
}
void QgsCategorizedSymbolRenderer::setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings )
{
mDataDefinedSizeLegend.reset( settings );
}
QgsDataDefinedSizeLegend *QgsCategorizedSymbolRenderer::dataDefinedSizeLegend() const
{
return mDataDefinedSizeLegend.get();
}

View File

@ -24,6 +24,7 @@
#include <QHash>
class QgsDataDefinedSizeLegend;
class QgsVectorLayer;
/** \ingroup core
@ -78,6 +79,7 @@ class CORE_EXPORT QgsCategorizedSymbolRenderer : public QgsFeatureRenderer
public:
QgsCategorizedSymbolRenderer( const QString &attrName = QString(), const QgsCategoryList &categories = QgsCategoryList() );
~QgsCategorizedSymbolRenderer();
virtual QgsSymbol *symbolForFeature( QgsFeature &feature, QgsRenderContext &context ) override;
virtual QgsSymbol *originalSymbolForFeature( QgsFeature &feature, QgsRenderContext &context ) override;
@ -179,6 +181,25 @@ class CORE_EXPORT QgsCategorizedSymbolRenderer : public QgsFeatureRenderer
//! \returns a new renderer if the conversion was possible, otherwise 0.
static QgsCategorizedSymbolRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer ) SIP_FACTORY;
/**
* Configures appearance of legend when renderer is configured to use data-defined size for marker symbols.
* This allows to configure for what values (symbol sizes) should be shown in the legend, whether to display
* different symbol sizes collapsed in one legend node or separated across multiple legend nodes etc.
*
* When renderer does not use data-defined size or does not use marker symbols, these settings will be ignored.
* Takes ownership of the passed settings objects. Null pointer is a valid input that disables data-defined
* size legend.
* \since QGIS 3.0
*/
void setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings SIP_TRANSFER );
/**
* Returns configuration of appearance of legend when using data-defined size for marker symbols.
* Will return null if the functionality is disabled.
* \since QGIS 3.0
*/
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
protected:
QString mAttrName;
QgsCategoryList mCategories;
@ -186,6 +207,8 @@ class CORE_EXPORT QgsCategorizedSymbolRenderer : public QgsFeatureRenderer
std::unique_ptr<QgsColorRamp> mSourceColorRamp;
std::unique_ptr<QgsExpression> mExpression;
std::unique_ptr<QgsDataDefinedSizeLegend> mDataDefinedSizeLegend;
//! attribute index (derived from attribute name in startRender)
int mAttrNum;

View File

@ -18,6 +18,7 @@
#include "qgsattributes.h"
#include "qgscolorramp.h"
#include "qgsdatadefinedsizelegend.h"
#include "qgsexpression.h"
#include "qgsfeature.h"
#include "qgsinvertedpolygonrenderer.h"
@ -1037,6 +1038,13 @@ QgsFeatureRenderer *QgsGraduatedSymbolRenderer::create( QDomElement &element, co
labelFormat.setFromDomElement( labelFormatElem );
r->setLabelFormat( labelFormat );
}
QDomElement ddsLegendSizeElem = element.firstChildElement( "data-defined-size-legend" );
if ( !ddsLegendSizeElem.isNull() )
{
r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readTypeAndAlignmentFromXml( ddsLegendSizeElem ) );
}
// TODO: symbol levels
return r;
}
@ -1133,6 +1141,13 @@ QDomElement QgsGraduatedSymbolRenderer::save( QDomDocument &doc, const QgsReadWr
}
rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? "1" : "0" ) );
if ( mDataDefinedSizeLegend )
{
QDomElement ddsLegendElem = doc.createElement( QStringLiteral( "data-defined-size-legend" ) );
QgsDataDefinedSizeLegend::writeTypeAndAlignmentToXml( *mDataDefinedSizeLegend, ddsLegendElem );
rendererElem.appendChild( ddsLegendElem );
}
return rendererElem;
}
@ -1149,8 +1164,7 @@ QgsLegendSymbolList QgsGraduatedSymbolRenderer::baseLegendSymbolItems() const
QgsLegendSymbolList QgsGraduatedSymbolRenderer::legendSymbolItems() const
{
QgsLegendSymbolList list;
if ( mSourceSymbol && mSourceSymbol->type() == QgsSymbol::Marker )
if ( mDataDefinedSizeLegend && mSourceSymbol && mSourceSymbol->type() == QgsSymbol::Marker )
{
// check that all symbols that have the same size expression
QgsProperty ddSize;
@ -1172,29 +1186,16 @@ QgsLegendSymbolList QgsGraduatedSymbolRenderer::legendSymbolItems() const
}
}
if ( !ddSize || !ddSize.isActive() )
if ( ddSize && ddSize.isActive() )
{
return baseLegendSymbolItems();
}
QgsLegendSymbolList lst;
if ( const QgsSizeScaleTransformer *sizeTransformer = dynamic_cast< const QgsSizeScaleTransformer * >( ddSize.transformer() ) )
{
QgsLegendSymbolItem title( nullptr, ddSize.propertyType() == QgsProperty::ExpressionBasedProperty ? ddSize.expressionString()
: ddSize.field(), QString() );
list << title;
Q_FOREACH ( double v, QgsSymbolLayerUtils::prettyBreaks( sizeTransformer->minValue(), sizeTransformer->maxValue(), 4 ) )
{
QgsLegendSymbolItem si( mSourceSymbol.get(), QString::number( v ), QString() );
QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( si.symbol() );
s->setDataDefinedSize( QgsProperty() );
s->setSize( sizeTransformer->size( v ) );
list << si;
}
// now list the graduated symbols
const QgsLegendSymbolList list2 = baseLegendSymbolItems();
Q_FOREACH ( const QgsLegendSymbolItem &item, list2 )
list << item;
return list;
QgsDataDefinedSizeLegend ddSizeLegend( *mDataDefinedSizeLegend );
ddSizeLegend.updateFromSymbolAndProperty( static_cast<const QgsMarkerSymbol *>( mSourceSymbol.get() ), ddSize );
lst += ddSizeLegend.legendSymbolList();
lst += baseLegendSymbolItems();
return lst;
}
}
@ -1623,6 +1624,16 @@ QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::convertFromRenderer( con
return r;
}
void QgsGraduatedSymbolRenderer::setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings )
{
mDataDefinedSizeLegend.reset( settings );
}
QgsDataDefinedSizeLegend *QgsGraduatedSymbolRenderer::dataDefinedSizeLegend() const
{
return mDataDefinedSizeLegend.get();
}
const char *QgsGraduatedSymbolRenderer::graduatedMethodStr( GraduatedMethod method )
{
switch ( method )

View File

@ -23,6 +23,8 @@
#include "qgsexpression.h"
#include <QRegExp>
class QgsDataDefinedSizeLegend;
/** \ingroup core
* \class QgsRendererRange
*/
@ -330,6 +332,25 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
//! \returns a new renderer if the conversion was possible, otherwise 0.
static QgsGraduatedSymbolRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer ) SIP_FACTORY;
/**
* Configures appearance of legend when renderer is configured to use data-defined size for marker symbols.
* This allows to configure for what values (symbol sizes) should be shown in the legend, whether to display
* different symbol sizes collapsed in one legend node or separated across multiple legend nodes etc.
*
* When renderer does not use data-defined size or does not use marker symbols, these settings will be ignored.
* Takes ownership of the passed settings objects. Null pointer is a valid input that disables data-defined
* size legend.
* \since QGIS 3.0
*/
void setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings SIP_TRANSFER );
/**
* Returns configuration of appearance of legend when using data-defined size for marker symbols.
* Will return null if the functionality is disabled.
* \since QGIS 3.0
*/
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
protected:
QString mAttrName;
QgsRangeList mRanges;
@ -344,6 +365,8 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
int mAttrNum;
bool mCounting;
std::unique_ptr<QgsDataDefinedSizeLegend> mDataDefinedSizeLegend;
QgsSymbol *symbolForValue( double value );
/** Returns the matching legend key for a value.

View File

@ -15,6 +15,7 @@
#include "qgslegendsymbolitem.h"
#include "qgsdatadefinedsizelegend.h"
#include "qgssymbol.h"
QgsLegendSymbolItem::QgsLegendSymbolItem()
@ -61,6 +62,7 @@ QgsLegendSymbolItem &QgsLegendSymbolItem::operator=( const QgsLegendSymbolItem &
mLabel = other.mLabel;
mKey = other.mKey;
mCheckable = other.mCheckable;
mDataDefinedSizeLegendSettings.reset( other.mDataDefinedSizeLegendSettings.get() ? new QgsDataDefinedSizeLegend( *other.mDataDefinedSizeLegendSettings.get() ) : nullptr );
mOriginalSymbolPointer = other.mOriginalSymbolPointer;
mScaleMinDenom = other.mScaleMinDenom;
mScaleMaxDenom = other.mScaleMaxDenom;
@ -89,3 +91,13 @@ void QgsLegendSymbolItem::setSymbol( QgsSymbol *s )
mSymbol = s ? s->clone() : nullptr;
mOriginalSymbolPointer = s;
}
void QgsLegendSymbolItem::setDataDefinedSizeLegendSettings( QgsDataDefinedSizeLegend *settings )
{
mDataDefinedSizeLegendSettings.reset( settings );
}
QgsDataDefinedSizeLegend *QgsLegendSymbolItem::dataDefinedSizeLegendSettings() const
{
return mDataDefinedSizeLegendSettings.get();
}

View File

@ -16,11 +16,13 @@
#ifndef QGSLEGENDSYMBOLITEMV2_H
#define QGSLEGENDSYMBOLITEMV2_H
#include <memory>
#include <QString>
#include "qgis.h"
#include "qgis_core.h"
class QgsDataDefinedSizeLegend;
class QgsSymbol;
/** \ingroup core
@ -72,6 +74,21 @@ class CORE_EXPORT QgsLegendSymbolItem
//! Set symbol of the item. Takes ownership of symbol.
void setSymbol( QgsSymbol *s SIP_TRANSFER );
/**
* Sets extra information about data-defined size. If set, this item should be converted to QgsDataDefinedSizeLegendNode
* rather than QgsSymbolLegendNode instance as usual. Passing null removes any data-defined size legend settings.
*
* Takes ownership of the settings object.
* \since QGIS 3.0
*/
void setDataDefinedSizeLegendSettings( QgsDataDefinedSizeLegend *settings SIP_TRANSFER );
/**
* Returns extra information for data-defined size legend rendering. Normally it returns null.
* \since QGIS 3.0
*/
QgsDataDefinedSizeLegend *dataDefinedSizeLegendSettings() const;
private:
//! symbol. owned by the struct. can be null.
QgsSymbol *mSymbol = nullptr;
@ -84,6 +101,10 @@ class CORE_EXPORT QgsLegendSymbolItem
QgsSymbol *mOriginalSymbolPointer = nullptr;
//! optional pointer to data-defined legend size settings - if set, the output legend
//! node should be QgsDataDefinedSizeLegendNode rather than ordinary QgsSymbolLegendNode
std::unique_ptr<QgsDataDefinedSizeLegend> mDataDefinedSizeLegendSettings;
// additional data that may be used for filtering
int mScaleMinDenom;

View File

@ -18,6 +18,7 @@
#include "qgssymbol.h"
#include "qgssymbollayerutils.h"
#include "qgsdatadefinedsizelegend.h"
#include "qgslogger.h"
#include "qgsfeature.h"
#include "qgsvectorlayer.h"
@ -39,6 +40,10 @@ QgsSingleSymbolRenderer::QgsSingleSymbolRenderer( QgsSymbol *symbol )
Q_ASSERT( symbol );
}
QgsSingleSymbolRenderer::~QgsSingleSymbolRenderer()
{
}
QgsSymbol *QgsSingleSymbolRenderer::symbolForFeature( QgsFeature &, QgsRenderContext & )
{
return mSymbol.get();
@ -154,6 +159,12 @@ QgsFeatureRenderer *QgsSingleSymbolRenderer::create( QDomElement &element, const
sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
}
QDomElement ddsLegendSizeElem = element.firstChildElement( "data-defined-size-legend" );
if ( !ddsLegendSizeElem.isNull() )
{
r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readTypeAndAlignmentFromXml( ddsLegendSizeElem ) );
}
// TODO: symbol levels
return r;
}
@ -275,33 +286,28 @@ QDomElement QgsSingleSymbolRenderer::save( QDomDocument &doc, const QgsReadWrite
}
rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? "1" : "0" ) );
if ( mDataDefinedSizeLegend )
{
QDomElement ddsLegendElem = doc.createElement( QStringLiteral( "data-defined-size-legend" ) );
QgsDataDefinedSizeLegend::writeTypeAndAlignmentToXml( *mDataDefinedSizeLegend, ddsLegendElem );
rendererElem.appendChild( ddsLegendElem );
}
return rendererElem;
}
QgsLegendSymbolList QgsSingleSymbolRenderer::legendSymbolItems() const
{
QgsLegendSymbolList lst;
if ( mSymbol->type() == QgsSymbol::Marker )
if ( mDataDefinedSizeLegend && mSymbol->type() == QgsSymbol::Marker )
{
const QgsMarkerSymbol *symbol = static_cast<const QgsMarkerSymbol *>( mSymbol.get() );
QgsProperty sizeDD( symbol->dataDefinedSize() );
if ( sizeDD && sizeDD.isActive() )
{
if ( const QgsSizeScaleTransformer *sizeTransformer = dynamic_cast< const QgsSizeScaleTransformer * >( sizeDD.transformer() ) )
{
QgsLegendSymbolItem title( nullptr, sizeDD.propertyType() == QgsProperty::ExpressionBasedProperty ? sizeDD.expressionString()
: sizeDD.field(), QString() );
lst << title;
Q_FOREACH ( double v, QgsSymbolLayerUtils::prettyBreaks( sizeTransformer->minValue(), sizeTransformer->maxValue(), 4 ) )
{
QgsLegendSymbolItem si( mSymbol.get(), QString::number( v ), QString() );
QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( si.symbol() );
s->setDataDefinedSize( QgsProperty() );
s->setSize( sizeTransformer->size( v ) );
lst << si;
}
return lst;
}
QgsDataDefinedSizeLegend ddSizeLegend( *mDataDefinedSizeLegend );
ddSizeLegend.updateFromSymbolAndProperty( static_cast<const QgsMarkerSymbol *>( mSymbol.get() ), sizeDD );
lst += ddSizeLegend.legendSymbolList();
}
}
@ -360,3 +366,13 @@ QgsSingleSymbolRenderer *QgsSingleSymbolRenderer::convertFromRenderer( const Qgs
return r;
}
void QgsSingleSymbolRenderer::setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings )
{
mDataDefinedSizeLegend.reset( settings );
}
QgsDataDefinedSizeLegend *QgsSingleSymbolRenderer::dataDefinedSizeLegend() const
{
return mDataDefinedSizeLegend.get();
}

View File

@ -21,6 +21,8 @@
#include "qgssymbol.h"
#include "qgsexpression.h"
class QgsDataDefinedSizeLegend;
/** \ingroup core
* \class QgsSingleSymbolRenderer
*/
@ -29,6 +31,7 @@ class CORE_EXPORT QgsSingleSymbolRenderer : public QgsFeatureRenderer
public:
QgsSingleSymbolRenderer( QgsSymbol *symbol SIP_TRANSFER );
~QgsSingleSymbolRenderer();
virtual QgsSymbol *symbolForFeature( QgsFeature &feature, QgsRenderContext &context ) override;
virtual QgsSymbol *originalSymbolForFeature( QgsFeature &feature, QgsRenderContext &context ) override;
@ -61,8 +64,28 @@ class CORE_EXPORT QgsSingleSymbolRenderer : public QgsFeatureRenderer
//! \returns a new renderer if the conversion was possible, otherwise 0.
static QgsSingleSymbolRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer ) SIP_FACTORY;
/**
* Configures appearance of legend when renderer is configured to use data-defined size for marker symbols.
* This allows to configure for what values (symbol sizes) should be shown in the legend, whether to display
* different symbol sizes collapsed in one legend node or separated across multiple legend nodes etc.
*
* When renderer does not use data-defined size or does not use marker symbols, these settings will be ignored.
* Takes ownership of the passed settings objects. Null pointer is a valid input that disables data-defined
* size legend.
* \since QGIS 3.0
*/
void setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings SIP_TRANSFER );
/**
* Returns configuration of appearance of legend when using data-defined size for marker symbols.
* Will return null if the functionality is disabled.
* \since QGIS 3.0
*/
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
protected:
std::unique_ptr<QgsSymbol> mSymbol;
std::unique_ptr<QgsDataDefinedSizeLegend> mDataDefinedSizeLegend;
private:
#ifdef SIP_RUN