QGIS API Documentation  3.6.0-Noosa (5873452)
qgsdatadefinedsizelegend.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdatadefinedsizelegend.cpp
3  --------------------------------------
4  Date : June 2017
5  Copyright : (C) 2017 by Martin Dobias
6  Email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
17 
18 #include "qgsproperty.h"
19 #include "qgspropertytransformer.h"
20 #include "qgssymbollayerutils.h"
21 #include "qgsxmlutils.h"
22 
23 
25  : mType( other.mType )
26  , mTitleLabel( other.mTitleLabel )
27  , mSizeClasses( other.mSizeClasses )
28  , mSymbol( other.mSymbol.get() ? other.mSymbol->clone() : nullptr )
29  , mSizeScaleTransformer( other.mSizeScaleTransformer.get() ? new QgsSizeScaleTransformer( *other.mSizeScaleTransformer ) : nullptr )
30  , mVAlign( other.mVAlign )
31  , mFont( other.mFont )
32  , mTextColor( other.mTextColor )
33  , mTextAlignment( other.mTextAlignment )
34 {
35 }
36 
38 {
39  if ( this != &other )
40  {
41  mType = other.mType;
42  mTitleLabel = other.mTitleLabel;
43  mSizeClasses = other.mSizeClasses;
44  mSymbol.reset( other.mSymbol.get() ? other.mSymbol->clone() : nullptr );
45  mSizeScaleTransformer.reset( other.mSizeScaleTransformer.get() ? new QgsSizeScaleTransformer( *other.mSizeScaleTransformer ) : nullptr );
46  mVAlign = other.mVAlign;
47  mFont = other.mFont;
48  mTextColor = other.mTextColor;
49  mTextAlignment = other.mTextAlignment;
50  }
51  return *this;
52 }
53 
55 {
56  mSymbol.reset( symbol );
57 }
58 
60 {
61  return mSymbol.get();
62 }
63 
65 {
66  mSizeScaleTransformer.reset( transformer );
67 }
68 
70 {
71  return mSizeScaleTransformer.get();
72 }
73 
74 
76 {
77  mSymbol.reset( symbol->clone() );
78  mSymbol->setDataDefinedSize( QgsProperty() ); // original symbol may have had data-defined size associated
79 
80  const QgsSizeScaleTransformer *sizeTransformer = dynamic_cast< const QgsSizeScaleTransformer * >( ddSize.transformer() );
81  mSizeScaleTransformer.reset( sizeTransformer ? sizeTransformer->clone() : nullptr );
82 
83  if ( mTitleLabel.isEmpty() )
84  mTitleLabel = ddSize.propertyType() == QgsProperty::ExpressionBasedProperty ? ddSize.expressionString() : ddSize.field();
85 
86  // automatically generate classes if no classes are defined
87  if ( sizeTransformer && mSizeClasses.isEmpty() )
88  {
89  mSizeClasses.clear();
90  Q_FOREACH ( double v, QgsSymbolLayerUtils::prettyBreaks( sizeTransformer->minValue(), sizeTransformer->maxValue(), 4 ) )
91  {
92  mSizeClasses << SizeClass( v, QString::number( v ) );
93  }
94  }
95 }
96 
98 {
100  if ( !mTitleLabel.isEmpty() )
101  {
102  QgsLegendSymbolItem title( nullptr, mTitleLabel, QString() );
103  lst << title;
104  }
105 
106  if ( mType == LegendCollapsed )
107  {
110  lst << i;
111  return lst;
112  }
113  else if ( mType == LegendSeparated )
114  {
115  Q_FOREACH ( const SizeClass &cl, mSizeClasses )
116  {
117  QgsLegendSymbolItem si( mSymbol.get(), cl.label, QString() );
118  QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( si.symbol() );
119  s->setSize( cl.size );
120  lst << si;
121  }
122  }
123  return lst;
124 }
125 
126 
127 void QgsDataDefinedSizeLegend::drawCollapsedLegend( QgsRenderContext &context, QSize *outputSize, int *labelXOffset ) const
128 {
129  if ( mType != LegendCollapsed || mSizeClasses.isEmpty() || !mSymbol )
130  {
131  if ( outputSize )
132  *outputSize = QSize();
133  if ( labelXOffset )
134  *labelXOffset = 0;
135  return;
136  }
137 
138  // parameters that could be configurable
139  double hLengthLineMM = 2; // extra horizontal space to be occupied by callout line
140  double hSpaceLineTextMM = 1; // horizontal space between end of the line and start of the text
141 
142  std::unique_ptr<QgsMarkerSymbol> s( mSymbol->clone() );
143 
144  QList<SizeClass> classes = mSizeClasses;
145 
146  // optionally scale size values if transformer is defined
147  if ( mSizeScaleTransformer )
148  {
149  for ( auto it = classes.begin(); it != classes.end(); ++it )
150  it->size = mSizeScaleTransformer->size( it->size );
151  }
152 
153  // make sure we draw bigger symbols first
154  std::sort( classes.begin(), classes.end(), []( const SizeClass & a, const SizeClass & b ) { return a.size > b.size; } );
155 
156  int hLengthLine = std::round( context.convertToPainterUnits( hLengthLineMM, QgsUnitTypes::RenderMillimeters ) );
157  int hSpaceLineText = std::round( context.convertToPainterUnits( hSpaceLineTextMM, QgsUnitTypes::RenderMillimeters ) );
158  int dpm = std::round( context.scaleFactor() * 1000 ); // scale factor = dots per millimeter
159 
160  // get font metrics - we need a temporary image just to get the metrics right for the given DPI
161  QImage tmpImg( QSize( 1, 1 ), QImage::Format_ARGB32_Premultiplied );
162  tmpImg.setDotsPerMeterX( dpm );
163  tmpImg.setDotsPerMeterY( dpm );
164  QFontMetrics fm( mFont, &tmpImg );
165  int textHeight = fm.height();
166  int leading = fm.leading();
167  int minTextDistY = textHeight + leading;
168 
169  //
170  // determine layout of the rendered elements
171  //
172 
173  // find out how wide the text will be
174  int maxTextWidth = 0;
175  Q_FOREACH ( const SizeClass &c, classes )
176  {
177  int w = fm.width( c.label );
178  if ( w > maxTextWidth )
179  maxTextWidth = w;
180  }
181  // add extra width needed to handle varying rendering of font weight
182  maxTextWidth += 1;
183 
184  // find out size of the largest symbol
185  double largestSize = classes.at( 0 ).size;
186  int outputLargestSize = std::round( context.convertToPainterUnits( largestSize, s->sizeUnit(), s->sizeMapUnitScale() ) );
187 
188  // find out top Y coordinate for individual symbol sizes
189  QList<int> symbolTopY;
190  Q_FOREACH ( const SizeClass &c, classes )
191  {
192  int outputSymbolSize = std::round( context.convertToPainterUnits( c.size, s->sizeUnit(), s->sizeMapUnitScale() ) );
193  switch ( mVAlign )
194  {
195  case AlignCenter:
196  symbolTopY << std::round( outputLargestSize / 2 - outputSymbolSize / 2 );
197  break;
198  case AlignBottom:
199  symbolTopY << std::round( outputLargestSize - outputSymbolSize );
200  break;
201  }
202  }
203 
204  // determine Y coordinate of texts: ideally they should be at the same level as symbolTopY
205  // but we need to avoid overlapping texts, so adjust the vertical positions
206  int middleIndex = 0; // classes.count() / 2; // will get the ideal position
207  QList<int> textCenterY;
208  int lastY = symbolTopY[middleIndex];
209  textCenterY << lastY;
210  for ( int i = middleIndex + 1; i < classes.count(); ++i )
211  {
212  int symbolY = symbolTopY[i];
213  if ( symbolY - lastY < minTextDistY )
214  symbolY = lastY + minTextDistY;
215  textCenterY << symbolY;
216  lastY = symbolY;
217  }
218 
219  int textTopY = textCenterY.first() - textHeight / 2;
220  int textBottomY = textCenterY.last() + textHeight / 2;
221  int totalTextHeight = textBottomY - textTopY;
222 
223  int fullWidth = outputLargestSize + hLengthLine + hSpaceLineText + maxTextWidth;
224  int fullHeight = std::max( static_cast< int >( std::round( outputLargestSize ) ) - textTopY, totalTextHeight );
225 
226  if ( outputSize )
227  *outputSize = QSize( fullWidth, fullHeight );
228  if ( labelXOffset )
229  *labelXOffset = outputLargestSize + hLengthLine + hSpaceLineText;
230 
231  if ( !context.painter() )
232  return; // only layout
233 
234  //
235  // drawing
236  //
237 
238  QPainter *p = context.painter();
239 
240  p->save();
241  p->translate( 0, -textTopY );
242 
243  // draw symbols first so that they do not cover
244  Q_FOREACH ( const SizeClass &c, classes )
245  {
246  s->setSize( c.size );
247 
248  int outputSymbolSize = std::round( context.convertToPainterUnits( c.size, s->sizeUnit(), s->sizeMapUnitScale() ) );
249  double tx = ( outputLargestSize - outputSymbolSize ) / 2;
250 
251  p->save();
252  switch ( mVAlign )
253  {
254  case AlignCenter:
255  p->translate( tx, ( outputLargestSize - outputSymbolSize ) / 2 );
256  break;
257  case AlignBottom:
258  p->translate( tx, outputLargestSize - outputSymbolSize );
259  break;
260  }
261  s->drawPreviewIcon( p, QSize( outputSymbolSize, outputSymbolSize ) );
262  p->restore();
263  }
264 
265  p->setPen( mTextColor );
266  p->setFont( mFont );
267 
268  int i = 0;
269  Q_FOREACH ( const SizeClass &c, classes )
270  {
271  // line from symbol to the text
272  p->drawLine( outputLargestSize / 2, symbolTopY[i], outputLargestSize + hLengthLine, textCenterY[i] );
273 
274  // draw label
275  QRect rect( outputLargestSize + hLengthLine + hSpaceLineText, textCenterY[i] - textHeight / 2,
276  maxTextWidth, textHeight );
277  p->drawText( rect, mTextAlignment, c.label );
278  i++;
279  }
280 
281  p->restore();
282 }
283 
284 
285 QImage QgsDataDefinedSizeLegend::collapsedLegendImage( QgsRenderContext &context, const QColor &backgroundColor, double paddingMM ) const
286 {
287  if ( mType != LegendCollapsed || mSizeClasses.isEmpty() || !mSymbol )
288  return QImage();
289 
290  // find out the size first
291  QSize contentSize;
292  drawCollapsedLegend( context, &contentSize );
293 
294  int padding = std::round( context.convertToPainterUnits( paddingMM, QgsUnitTypes::RenderMillimeters ) );
295  int dpm = std::round( context.scaleFactor() * 1000 ); // scale factor = dots per millimeter
296 
297  QImage img( contentSize.width() + padding * 2, contentSize.height() + padding * 2, QImage::Format_ARGB32_Premultiplied );
298  img.setDotsPerMeterX( dpm );
299  img.setDotsPerMeterY( dpm );
300  img.fill( backgroundColor );
301 
302  QPainter painter( &img );
303  painter.setRenderHint( QPainter::Antialiasing, true );
304 
305  painter.translate( padding, padding ); // so we do not need to care about padding at all
306 
307  // now do the rendering
308  QPainter *oldPainter = context.painter();
309  context.setPainter( &painter );
310  drawCollapsedLegend( context );
311  context.setPainter( oldPainter );
312 
313  painter.end();
314  return img;
315 }
316 
318 {
319  if ( elem.isNull() )
320  return nullptr;
322  ddsLegend->setLegendType( elem.attribute( QStringLiteral( "type" ) ) == QLatin1String( "collapsed" ) ? LegendCollapsed : LegendSeparated );
323  ddsLegend->setVerticalAlignment( elem.attribute( QStringLiteral( "valign" ) ) == QLatin1String( "center" ) ? AlignCenter : AlignBottom );
324  ddsLegend->setTitle( elem.attribute( QStringLiteral( "title" ) ) );
325 
326  QDomElement elemSymbol = elem.firstChildElement( QStringLiteral( "symbol" ) );
327  if ( !elemSymbol.isNull() )
328  {
329  ddsLegend->setSymbol( QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( elemSymbol, context ) );
330  }
331 
332  QgsSizeScaleTransformer *transformer = nullptr;
333  QDomElement elemTransformer = elem.firstChildElement( QStringLiteral( "transformer" ) );
334  if ( !elemTransformer.isNull() )
335  {
336  transformer = new QgsSizeScaleTransformer;
337  transformer->loadVariant( QgsXmlUtils::readVariant( elemTransformer ) );
338  }
339  ddsLegend->setSizeScaleTransformer( transformer );
340 
341  QDomElement elemTextStyle = elem.firstChildElement( QStringLiteral( "text-style" ) );
342  if ( !elemTextStyle.isNull() )
343  {
344  QDomElement elemFont = elemTextStyle.firstChildElement( QStringLiteral( "font" ) );
345  if ( !elemFont.isNull() )
346  {
347  ddsLegend->setFont( QFont( elemFont.attribute( QStringLiteral( "family" ) ), elemFont.attribute( QStringLiteral( "size" ) ).toInt(),
348  elemFont.attribute( QStringLiteral( "weight" ) ).toInt(), elemFont.attribute( QStringLiteral( "italic" ) ).toInt() ) );
349  }
350  ddsLegend->setTextColor( QgsSymbolLayerUtils::decodeColor( elemTextStyle.attribute( QStringLiteral( "color" ) ) ) );
351  ddsLegend->setTextAlignment( static_cast<Qt::AlignmentFlag>( elemTextStyle.attribute( QStringLiteral( "align" ) ).toInt() ) );
352  }
353 
354  QDomElement elemClasses = elem.firstChildElement( QStringLiteral( "classes" ) );
355  if ( !elemClasses.isNull() )
356  {
357  QList<SizeClass> classes;
358  QDomElement elemClass = elemClasses.firstChildElement( QStringLiteral( "class" ) );
359  while ( !elemClass.isNull() )
360  {
361  classes << SizeClass( elemClass.attribute( QStringLiteral( "size" ) ).toDouble(), elemClass.attribute( QStringLiteral( "label" ) ) );
362  elemClass = elemClass.nextSiblingElement();
363  }
364  ddsLegend->setClasses( classes );
365  }
366 
367  return ddsLegend;
368 }
369 
370 void QgsDataDefinedSizeLegend::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
371 {
372  QDomDocument doc = elem.ownerDocument();
373 
374  elem.setAttribute( QStringLiteral( "type" ), mType == LegendCollapsed ? "collapsed" : "separated" );
375  elem.setAttribute( QStringLiteral( "valign" ), mVAlign == AlignCenter ? "center" : "bottom" );
376  elem.setAttribute( QStringLiteral( "title" ), mTitleLabel );
377 
378  if ( mSymbol )
379  {
380  QDomElement elemSymbol = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "source" ), mSymbol.get(), doc, context );
381  elem.appendChild( elemSymbol );
382  }
383 
384  if ( mSizeScaleTransformer )
385  {
386  QDomElement elemTransformer = QgsXmlUtils::writeVariant( mSizeScaleTransformer->toVariant(), doc );
387  elemTransformer.setTagName( QStringLiteral( "transformer" ) );
388  elem.appendChild( elemTransformer );
389  }
390 
391  QDomElement elemFont = doc.createElement( QStringLiteral( "font" ) );
392  elemFont.setAttribute( QStringLiteral( "family" ), mFont.family() );
393  elemFont.setAttribute( QStringLiteral( "size" ), mFont.pointSize() );
394  elemFont.setAttribute( QStringLiteral( "weight" ), mFont.weight() );
395  elemFont.setAttribute( QStringLiteral( "italic" ), mFont.italic() );
396 
397  QDomElement elemTextStyle = doc.createElement( QStringLiteral( "text-style" ) );
398  elemTextStyle.setAttribute( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mTextColor ) );
399  elemTextStyle.setAttribute( QStringLiteral( "align" ), static_cast<int>( mTextAlignment ) );
400  elemTextStyle.appendChild( elemFont );
401  elem.appendChild( elemTextStyle );
402 
403  if ( !mSizeClasses.isEmpty() )
404  {
405  QDomElement elemClasses = doc.createElement( QStringLiteral( "classes" ) );
406  Q_FOREACH ( const SizeClass &sc, mSizeClasses )
407  {
408  QDomElement elemClass = doc.createElement( QStringLiteral( "class" ) );
409  elemClass.setAttribute( QStringLiteral( "size" ), sc.size );
410  elemClass.setAttribute( QStringLiteral( "label" ), sc.label );
411  elemClasses.appendChild( elemClass );
412  }
413  elem.appendChild( elemClasses );
414  }
415 }
The class is used as a container of context for various read/write operations on other objects...
Symbols are aligned to the center.
Each class (size value) has a separate legend node.
QString label
Label to be shown with the particular symbol size.
QList< QgsLegendSymbolItem > QgsLegendSymbolList
void setTextAlignment(Qt::AlignmentFlag flag)
Sets horizontal text alignment for rendering of labels - only valid for collapsed legend...
void setFont(const QFont &font)
Sets font used for rendering of labels - only valid for collapsed legend.
Definition of one class for the legend.
Expression based property (QgsExpressionBasedProperty)
Definition: qgsproperty.h:239
QgsDataDefinedSizeLegend & operator=(const QgsDataDefinedSizeLegend &other)
QgsSizeScaleTransformer * clone() const override
Returns a clone of the transformer.
void setSize(double size)
Sets the size for the whole symbol.
Definition: qgssymbol.cpp:1277
QgsLegendSymbolList legendSymbolList() const
Generates legend symbol items according to the configuration.
void setTitle(const QString &title)
Sets title label for data-defined size legend.
void setLegendType(LegendType type)
Sets how the legend should be rendered.
QgsMarkerSymbol * symbol() const
Returns marker symbol that will be used to draw markers in legend.
A marker symbol type, for rendering Point and MultiPoint geometries.
Definition: qgssymbol.h:732
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
const QgsPropertyTransformer * transformer() const
Returns the existing transformer used for manipulating the calculated values for the property...
static QString encodeColor(const QColor &color)
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString expressionString() const
Returns the expression used for the property value.
QImage collapsedLegendImage(QgsRenderContext &context, const QColor &backgroundColor=Qt::transparent, double paddingMM=1) const
Returns output image that would be shown in the legend. Returns invalid image if legend is not config...
static QVariant readVariant(const QDomElement &element)
Read a QVariant from a QDomElement.
Type propertyType() const
Returns the property type.
QList< QgsDataDefinedSizeLegend::SizeClass > classes() const
Returns list of classes: each class is a pair of symbol size (in units used by the symbol) and label...
QgsSizeScaleTransformer * sizeScaleTransformer() const
Returns transformer for scaling of symbol sizes. Returns null if no transformer is defined...
QgsDataDefinedSizeLegend()=default
Constructor for QgsDataDefinedSizeLegend.
double size
Marker size in units used by the symbol (usually millimeters). May be further scaled before rendering...
double minValue() const
Returns the minimum value expected by the transformer.
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
void updateFromSymbolAndProperty(const QgsMarkerSymbol *symbol, const QgsProperty &ddSize)
Updates the list of classes, source symbol and title label from given symbol and property.
void setSizeScaleTransformer(QgsSizeScaleTransformer *transformer SIP_TRANSFER)
Sets transformer for scaling of symbol sizes. Takes ownership of the object. Accepts null pointer to ...
A store for object properties.
Definition: qgsproperty.h:229
void writeXml(QDomElement &elem, const QgsReadWriteContext &context) const
Writes configuration to the given XML element.
QString field() const
Returns the current field name the property references.
void setVerticalAlignment(VerticalAlignment vAlign)
Sets vertical alignment of symbols - only valid for collapsed legend.
Symbols are aligned to the bottom.
static QList< double > prettyBreaks(double minimum, double maximum, int classes)
Computes a sequence of about &#39;classes&#39; equally spaced round values which cover the range of values fr...
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setSymbol(QgsMarkerSymbol *symbol SIP_TRANSFER)
Sets marker symbol that will be used to draw markers in legend.
QString title() const
Returns title label for data-defined size legend.
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
QgsPropertyTransformer subclass for scaling a value into a size according to various scaling methods...
void setTextColor(const QColor &color)
Sets text color for rendering of labels - only valid for collapsed legend.
void drawCollapsedLegend(QgsRenderContext &context, QSize *outputSize SIP_OUT=nullptr, int *labelXOffset SIP_OUT=nullptr) const
Draw the legend if using LegendOneNodeForAll and optionally output size of the legend and x offset of...
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
Object that keeps configuration of appearance of marker symbol&#39;s data-defined size in legend...
All classes are rendered within one legend node.
static QgsDataDefinedSizeLegend * readXml(const QDomElement &elem, const QgsReadWriteContext &context) SIP_FACTORY
Creates instance from given element and returns it (caller takes ownership). Returns null on error...
void setDataDefinedSizeLegendSettings(QgsDataDefinedSizeLegend *settings)
Sets extra information about data-defined size.
static QDomElement writeVariant(const QVariant &value, QDomDocument &doc)
Write a QVariant to a QDomElement.
QgsMarkerSymbol * clone() const override
Returns a deep copy of this symbol.
Definition: qgssymbol.cpp:1585
static QColor decodeColor(const QString &str)
void setClasses(const QList< QgsDataDefinedSizeLegend::SizeClass > &classes)
Sets list of classes: each class is a pair of symbol size (in units used by the symbol) and label...
double maxValue() const
Returns the maximum value expected by the transformer.