Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsgraduatedsymbolrenderer.cpp
3 : : ---------------------
4 : : begin : November 2009
5 : : copyright : (C) 2009 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 : :
16 : : #include <QDomDocument>
17 : : #include <QDomElement>
18 : :
19 : : #include <ctime>
20 : : #include <cmath>
21 : :
22 : : #include "qgsgraduatedsymbolrenderer.h"
23 : :
24 : : #include "qgsattributes.h"
25 : : #include "qgscategorizedsymbolrenderer.h"
26 : : #include "qgscolorramp.h"
27 : : #include "qgsdatadefinedsizelegend.h"
28 : : #include "qgsexpression.h"
29 : : #include "qgsfeature.h"
30 : : #include "qgsinvertedpolygonrenderer.h"
31 : : #include "qgslogger.h"
32 : : #include "qgspainteffect.h"
33 : : #include "qgspainteffectregistry.h"
34 : : #include "qgspointdisplacementrenderer.h"
35 : : #include "qgsproperty.h"
36 : : #include "qgssymbol.h"
37 : : #include "qgssymbollayer.h"
38 : : #include "qgssymbollayerutils.h"
39 : : #include "qgsvectordataprovider.h"
40 : : #include "qgsvectorlayer.h"
41 : : #include "qgsvectorlayerutils.h"
42 : : #include "qgsexpressioncontextutils.h"
43 : : #include "qgsstyleentityvisitor.h"
44 : : #include "qgsclassificationmethod.h"
45 : : #include "qgsclassificationequalinterval.h"
46 : : #include "qgsapplication.h"
47 : : #include "qgsclassificationmethodregistry.h"
48 : : #include "qgsclassificationcustom.h"
49 : :
50 : :
51 : 0 : QgsGraduatedSymbolRenderer::QgsGraduatedSymbolRenderer( const QString &attrName, const QgsRangeList &ranges )
52 : 0 : : QgsFeatureRenderer( QStringLiteral( "graduatedSymbol" ) )
53 : 0 : , mAttrName( attrName )
54 : 0 : {
55 : : // TODO: check ranges for sanity (NULL symbols, invalid ranges)
56 : :
57 : : //important - we need a deep copy of the ranges list, not a shared copy. This is required because
58 : : //QgsRendererRange::symbol() is marked const, and so retrieving the symbol via this method does not
59 : : //trigger a detachment and copy of mRanges BUT that same method CAN be used to modify a symbol in place
60 : 0 : const auto constRanges = ranges;
61 : 0 : for ( const QgsRendererRange &range : constRanges )
62 : : {
63 : 0 : mRanges << range;
64 : : }
65 : :
66 : 0 : mClassificationMethod.reset( new QgsClassificationCustom() );
67 : 0 : }
68 : :
69 : 0 : QgsGraduatedSymbolRenderer::~QgsGraduatedSymbolRenderer()
70 : 0 : {
71 : 0 : mRanges.clear(); // should delete all the symbols
72 : 0 : }
73 : :
74 : :
75 : 0 : const QgsRendererRange *QgsGraduatedSymbolRenderer::rangeForValue( double value ) const
76 : : {
77 : 0 : for ( const QgsRendererRange &range : mRanges )
78 : : {
79 : 0 : if ( range.lowerValue() <= value && range.upperValue() >= value )
80 : : {
81 : 0 : if ( range.renderState() || mCounting )
82 : 0 : return ⦥
83 : : else
84 : 0 : return nullptr;
85 : : }
86 : : }
87 : :
88 : : // second chance -- use a bit of double tolerance to avoid floating point equality fuzziness
89 : : // if a value falls just outside of a range, but within acceptable double precision tolerance
90 : : // then we accept it anyway
91 : 0 : for ( const QgsRendererRange &range : mRanges )
92 : : {
93 : 0 : if ( qgsDoubleNear( range.lowerValue(), value ) || qgsDoubleNear( range.upperValue(), value ) )
94 : : {
95 : 0 : if ( range.renderState() || mCounting )
96 : 0 : return ⦥
97 : : else
98 : 0 : return nullptr;
99 : : }
100 : : }
101 : : // the value is out of the range: return NULL instead of symbol
102 : 0 : return nullptr;
103 : 0 : }
104 : :
105 : 0 : QgsSymbol *QgsGraduatedSymbolRenderer::symbolForValue( double value ) const
106 : : {
107 : 0 : if ( const QgsRendererRange *range = rangeForValue( value ) )
108 : 0 : return range->symbol();
109 : 0 : return nullptr;
110 : 0 : }
111 : :
112 : 0 : QString QgsGraduatedSymbolRenderer::legendKeyForValue( double value ) const
113 : : {
114 : 0 : if ( const QgsRendererRange *matchingRange = rangeForValue( value ) )
115 : : {
116 : 0 : int i = 0;
117 : 0 : for ( const QgsRendererRange &range : mRanges )
118 : : {
119 : 0 : if ( matchingRange == &range )
120 : 0 : return QString::number( i );
121 : 0 : i++;
122 : : }
123 : 0 : }
124 : 0 : return QString();
125 : 0 : }
126 : :
127 : 0 : QgsSymbol *QgsGraduatedSymbolRenderer::symbolForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
128 : : {
129 : 0 : return originalSymbolForFeature( feature, context );
130 : : }
131 : :
132 : 0 : QVariant QgsGraduatedSymbolRenderer::valueForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
133 : : {
134 : 0 : QgsAttributes attrs = feature.attributes();
135 : 0 : QVariant value;
136 : 0 : if ( mExpression )
137 : : {
138 : 0 : value = mExpression->evaluate( &context.expressionContext() );
139 : 0 : }
140 : : else
141 : : {
142 : 0 : value = attrs.value( mAttrNum );
143 : : }
144 : :
145 : 0 : return value;
146 : 0 : }
147 : :
148 : 0 : QgsSymbol *QgsGraduatedSymbolRenderer::originalSymbolForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
149 : : {
150 : 0 : QVariant value = valueForFeature( feature, context );
151 : :
152 : : // Null values should not be categorized
153 : 0 : if ( value.isNull() )
154 : 0 : return nullptr;
155 : :
156 : : // find the right category
157 : 0 : return symbolForValue( value.toDouble() );
158 : 0 : }
159 : :
160 : 0 : void QgsGraduatedSymbolRenderer::startRender( QgsRenderContext &context, const QgsFields &fields )
161 : : {
162 : 0 : QgsFeatureRenderer::startRender( context, fields );
163 : :
164 : 0 : mCounting = context.rendererScale() == 0.0;
165 : :
166 : : // find out classification attribute index from name
167 : 0 : mAttrNum = fields.lookupField( mAttrName );
168 : :
169 : 0 : if ( mAttrNum == -1 )
170 : : {
171 : 0 : mExpression.reset( new QgsExpression( mAttrName ) );
172 : 0 : mExpression->prepare( &context.expressionContext() );
173 : 0 : }
174 : :
175 : 0 : for ( const QgsRendererRange &range : std::as_const( mRanges ) )
176 : : {
177 : 0 : if ( !range.symbol() )
178 : 0 : continue;
179 : :
180 : 0 : range.symbol()->startRender( context, fields );
181 : : }
182 : 0 : }
183 : :
184 : 0 : void QgsGraduatedSymbolRenderer::stopRender( QgsRenderContext &context )
185 : : {
186 : 0 : QgsFeatureRenderer::stopRender( context );
187 : :
188 : 0 : for ( const QgsRendererRange &range : std::as_const( mRanges ) )
189 : : {
190 : 0 : if ( !range.symbol() )
191 : 0 : continue;
192 : :
193 : 0 : range.symbol()->stopRender( context );
194 : : }
195 : 0 : }
196 : :
197 : 0 : QSet<QString> QgsGraduatedSymbolRenderer::usedAttributes( const QgsRenderContext &context ) const
198 : : {
199 : 0 : QSet<QString> attributes;
200 : :
201 : : // mAttrName can contain either attribute name or an expression.
202 : : // Sometimes it is not possible to distinguish between those two,
203 : : // e.g. "a - b" can be both a valid attribute name or expression.
204 : : // Since we do not have access to fields here, try both options.
205 : 0 : attributes << mAttrName;
206 : :
207 : 0 : QgsExpression testExpr( mAttrName );
208 : 0 : if ( !testExpr.hasParserError() )
209 : 0 : attributes.unite( testExpr.referencedColumns() );
210 : :
211 : 0 : QgsRangeList::const_iterator range_it = mRanges.constBegin();
212 : 0 : for ( ; range_it != mRanges.constEnd(); ++range_it )
213 : : {
214 : 0 : QgsSymbol *symbol = range_it->symbol();
215 : 0 : if ( symbol )
216 : : {
217 : 0 : attributes.unite( symbol->usedAttributes( context ) );
218 : 0 : }
219 : 0 : }
220 : 0 : return attributes;
221 : 0 : }
222 : :
223 : 0 : bool QgsGraduatedSymbolRenderer::filterNeedsGeometry() const
224 : : {
225 : 0 : QgsExpression testExpr( mAttrName );
226 : 0 : if ( !testExpr.hasParserError() )
227 : : {
228 : 0 : QgsExpressionContext context;
229 : 0 : context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
230 : 0 : testExpr.prepare( &context );
231 : 0 : return testExpr.needsGeometry();
232 : 0 : }
233 : 0 : return false;
234 : 0 : }
235 : :
236 : 0 : bool QgsGraduatedSymbolRenderer::updateRangeSymbol( int rangeIndex, QgsSymbol *symbol )
237 : : {
238 : 0 : if ( rangeIndex < 0 || rangeIndex >= mRanges.size() )
239 : 0 : return false;
240 : 0 : mRanges[rangeIndex].setSymbol( symbol );
241 : 0 : return true;
242 : 0 : }
243 : :
244 : 0 : bool QgsGraduatedSymbolRenderer::updateRangeLabel( int rangeIndex, const QString &label )
245 : : {
246 : 0 : if ( rangeIndex < 0 || rangeIndex >= mRanges.size() )
247 : 0 : return false;
248 : 0 : mRanges[rangeIndex].setLabel( label );
249 : 0 : return true;
250 : 0 : }
251 : :
252 : 0 : bool QgsGraduatedSymbolRenderer::updateRangeUpperValue( int rangeIndex, double value )
253 : : {
254 : 0 : if ( rangeIndex < 0 || rangeIndex >= mRanges.size() )
255 : 0 : return false;
256 : 0 : QgsRendererRange &range = mRanges[rangeIndex];
257 : 0 : QgsClassificationMethod::ClassPosition pos = QgsClassificationMethod::Inner;
258 : 0 : if ( rangeIndex == 0 )
259 : 0 : pos = QgsClassificationMethod::LowerBound;
260 : 0 : else if ( rangeIndex == mRanges.count() )
261 : 0 : pos = QgsClassificationMethod::UpperBound;
262 : :
263 : 0 : bool isDefaultLabel = mClassificationMethod->labelForRange( range, pos ) == range.label();
264 : 0 : range.setUpperValue( value );
265 : 0 : if ( isDefaultLabel )
266 : 0 : range.setLabel( mClassificationMethod->labelForRange( range, pos ) );
267 : :
268 : 0 : return true;
269 : 0 : }
270 : :
271 : 0 : bool QgsGraduatedSymbolRenderer::updateRangeLowerValue( int rangeIndex, double value )
272 : : {
273 : 0 : if ( rangeIndex < 0 || rangeIndex >= mRanges.size() )
274 : 0 : return false;
275 : :
276 : 0 : QgsRendererRange &range = mRanges[rangeIndex];
277 : 0 : QgsClassificationMethod::ClassPosition pos = QgsClassificationMethod::Inner;
278 : 0 : if ( rangeIndex == 0 )
279 : 0 : pos = QgsClassificationMethod::LowerBound;
280 : 0 : else if ( rangeIndex == mRanges.count() )
281 : 0 : pos = QgsClassificationMethod::UpperBound;
282 : :
283 : 0 : bool isDefaultLabel = mClassificationMethod->labelForRange( range, pos ) == range.label();
284 : 0 : range.setLowerValue( value );
285 : 0 : if ( isDefaultLabel )
286 : 0 : range.setLabel( mClassificationMethod->labelForRange( range, pos ) );
287 : :
288 : 0 : return true;
289 : 0 : }
290 : :
291 : 0 : bool QgsGraduatedSymbolRenderer::updateRangeRenderState( int rangeIndex, bool value )
292 : : {
293 : 0 : if ( rangeIndex < 0 || rangeIndex >= mRanges.size() )
294 : 0 : return false;
295 : 0 : mRanges[rangeIndex].setRenderState( value );
296 : 0 : return true;
297 : 0 : }
298 : :
299 : 0 : QString QgsGraduatedSymbolRenderer::dump() const
300 : : {
301 : 0 : QString s = QStringLiteral( "GRADUATED: attr %1\n" ).arg( mAttrName );
302 : 0 : for ( int i = 0; i < mRanges.count(); i++ )
303 : 0 : s += mRanges[i].dump();
304 : 0 : return s;
305 : 0 : }
306 : :
307 : 0 : QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::clone() const
308 : : {
309 : 0 : QgsGraduatedSymbolRenderer *r = new QgsGraduatedSymbolRenderer( mAttrName, mRanges );
310 : :
311 : 0 : r->setClassificationMethod( mClassificationMethod->clone() );
312 : :
313 : 0 : if ( mSourceSymbol )
314 : 0 : r->setSourceSymbol( mSourceSymbol->clone() );
315 : 0 : if ( mSourceColorRamp )
316 : : {
317 : 0 : r->setSourceColorRamp( mSourceColorRamp->clone() );
318 : 0 : }
319 : 0 : r->setUsingSymbolLevels( usingSymbolLevels() );
320 : 0 : r->setDataDefinedSizeLegend( mDataDefinedSizeLegend ? new QgsDataDefinedSizeLegend( *mDataDefinedSizeLegend ) : nullptr );
321 : 0 : r->setGraduatedMethod( graduatedMethod() );
322 : 0 : copyRendererData( r );
323 : 0 : return r;
324 : 0 : }
325 : :
326 : 0 : void QgsGraduatedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
327 : : {
328 : 0 : QVariantMap newProps = props;
329 : 0 : newProps[ QStringLiteral( "attribute" )] = mAttrName;
330 : 0 : newProps[ QStringLiteral( "method" )] = graduatedMethodStr( mGraduatedMethod );
331 : :
332 : : // create a Rule for each range
333 : 0 : bool first = true;
334 : 0 : for ( QgsRangeList::const_iterator it = mRanges.constBegin(); it != mRanges.constEnd(); ++it )
335 : : {
336 : 0 : it->toSld( doc, element, newProps, first );
337 : 0 : first = false;
338 : 0 : }
339 : 0 : }
340 : :
341 : 0 : QgsSymbolList QgsGraduatedSymbolRenderer::symbols( QgsRenderContext &context ) const
342 : : {
343 : 0 : Q_UNUSED( context )
344 : 0 : QgsSymbolList lst;
345 : 0 : lst.reserve( mRanges.count() );
346 : 0 : for ( const QgsRendererRange &range : std::as_const( mRanges ) )
347 : : {
348 : 0 : lst.append( range.symbol() );
349 : : }
350 : 0 : return lst;
351 : 0 : }
352 : :
353 : 0 : bool QgsGraduatedSymbolRenderer::accept( QgsStyleEntityVisitorInterface *visitor ) const
354 : : {
355 : 0 : for ( const QgsRendererRange &range : std::as_const( mRanges ) )
356 : : {
357 : 0 : QgsStyleSymbolEntity entity( range.symbol() );
358 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "%1 - %2" ).arg( range.lowerValue() ).arg( range.upperValue() ), range.label() ) ) )
359 : 0 : return false;
360 : 0 : }
361 : :
362 : 0 : if ( mSourceColorRamp )
363 : : {
364 : 0 : QgsStyleColorRampEntity entity( mSourceColorRamp.get() );
365 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity ) ) )
366 : 0 : return false;
367 : 0 : }
368 : :
369 : 0 : return true;
370 : 0 : }
371 : :
372 : 0 : void QgsGraduatedSymbolRenderer::makeBreaksSymmetric( QList<double> &breaks, double symmetryPoint, bool astride )
373 : : {
374 : 0 : return QgsClassificationMethod::makeBreaksSymmetric( breaks, symmetryPoint, astride );
375 : : }
376 : :
377 : 0 : QList<double> QgsGraduatedSymbolRenderer::calcEqualIntervalBreaks( double minimum, double maximum, int classes, bool useSymmetricMode, double symmetryPoint, bool astride )
378 : : {
379 : 0 : QgsClassificationEqualInterval method;
380 : 0 : method.setSymmetricMode( useSymmetricMode, symmetryPoint, astride );
381 : 0 : QList<QgsClassificationRange> _classes = method.classes( minimum, maximum, classes );
382 : 0 : return QgsClassificationMethod::rangesToBreaks( _classes );
383 : 0 : }
384 : :
385 : : Q_NOWARN_DEPRECATED_PUSH
386 : 0 : QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::createRenderer(
387 : : QgsVectorLayer *vlayer,
388 : : const QString &attrName,
389 : : int classes,
390 : : Mode mode,
391 : : QgsSymbol *symbol,
392 : : QgsColorRamp *ramp,
393 : : const QgsRendererRangeLabelFormat &labelFormat,
394 : : bool useSymmetricMode,
395 : : double symmetryPoint,
396 : : const QStringList &listForCboPrettyBreaks,
397 : : bool astride
398 : : )
399 : : {
400 : 0 : Q_UNUSED( listForCboPrettyBreaks )
401 : :
402 : 0 : QgsRangeList ranges;
403 : 0 : QgsGraduatedSymbolRenderer *r = new QgsGraduatedSymbolRenderer( attrName, ranges );
404 : 0 : r->setSourceSymbol( symbol->clone() );
405 : 0 : r->setSourceColorRamp( ramp->clone() );
406 : :
407 : 0 : QString methodId = methodIdFromMode( mode );
408 : 0 : QgsClassificationMethod *method = QgsApplication::classificationMethodRegistry()->method( methodId );
409 : :
410 : 0 : if ( method )
411 : : {
412 : 0 : method->setSymmetricMode( useSymmetricMode, symmetryPoint, astride );
413 : 0 : method->setLabelFormat( labelFormat.format() );
414 : 0 : method->setLabelTrimTrailingZeroes( labelFormat.trimTrailingZeroes() );
415 : 0 : method->setLabelPrecision( labelFormat.precision() );
416 : 0 : }
417 : 0 : r->setClassificationMethod( method );
418 : :
419 : 0 : r->updateClasses( vlayer, classes );
420 : 0 : return r;
421 : 0 : }
422 : : Q_NOWARN_DEPRECATED_POP
423 : :
424 : 0 : void QgsGraduatedSymbolRenderer::updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses,
425 : : bool useSymmetricMode, double symmetryPoint, bool astride )
426 : : {
427 : 0 : if ( mAttrName.isEmpty() )
428 : 0 : return;
429 : :
430 : 0 : QString methodId = methodIdFromMode( mode );
431 : 0 : QgsClassificationMethod *method = QgsApplication::classificationMethodRegistry()->method( methodId );
432 : 0 : method->setSymmetricMode( useSymmetricMode, symmetryPoint, astride );
433 : 0 : setClassificationMethod( method );
434 : :
435 : 0 : updateClasses( vlayer, nclasses );
436 : 0 : }
437 : :
438 : 0 : void QgsGraduatedSymbolRenderer::updateClasses( const QgsVectorLayer *vl, int nclasses )
439 : : {
440 : 0 : if ( mClassificationMethod->id() == QgsClassificationCustom::METHOD_ID )
441 : 0 : return;
442 : :
443 : 0 : QList<QgsClassificationRange> classes = mClassificationMethod->classes( vl, mAttrName, nclasses );
444 : :
445 : 0 : deleteAllClasses();
446 : 0 :
447 : 0 : for ( QList<QgsClassificationRange>::iterator it = classes.begin(); it != classes.end(); ++it )
448 : : {
449 : 0 : QgsSymbol *newSymbol = mSourceSymbol ? mSourceSymbol->clone() : QgsSymbol::defaultSymbol( vl->geometryType() );
450 : 0 : addClass( QgsRendererRange( *it, newSymbol ) );
451 : 0 : }
452 : 0 : updateColorRamp( nullptr );
453 : 0 : }
454 : :
455 : : Q_NOWARN_DEPRECATED_PUSH
456 : 0 : QgsRendererRangeLabelFormat QgsGraduatedSymbolRenderer::labelFormat() const
457 : : {
458 : 0 : return QgsRendererRangeLabelFormat( mClassificationMethod->labelFormat(), mClassificationMethod->labelPrecision(), mClassificationMethod->labelTrimTrailingZeroes() );
459 : 0 : }
460 : : Q_NOWARN_DEPRECATED_POP
461 : :
462 : 0 : QgsFeatureRenderer *QgsGraduatedSymbolRenderer::create( QDomElement &element, const QgsReadWriteContext &context )
463 : : {
464 : 0 : QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
465 : 0 : if ( symbolsElem.isNull() )
466 : 0 : return nullptr;
467 : :
468 : 0 : QDomElement rangesElem = element.firstChildElement( QStringLiteral( "ranges" ) );
469 : 0 : if ( rangesElem.isNull() )
470 : 0 : return nullptr;
471 : :
472 : 0 : QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
473 : 0 : QgsRangeList ranges;
474 : :
475 : 0 : QDomElement rangeElem = rangesElem.firstChildElement();
476 : 0 : while ( !rangeElem.isNull() )
477 : : {
478 : 0 : if ( rangeElem.tagName() == QLatin1String( "range" ) )
479 : : {
480 : 0 : double lowerValue = rangeElem.attribute( QStringLiteral( "lower" ) ).toDouble();
481 : 0 : double upperValue = rangeElem.attribute( QStringLiteral( "upper" ) ).toDouble();
482 : 0 : QString symbolName = rangeElem.attribute( QStringLiteral( "symbol" ) );
483 : 0 : QString label = rangeElem.attribute( QStringLiteral( "label" ) );
484 : 0 : bool render = rangeElem.attribute( QStringLiteral( "render" ), QStringLiteral( "true" ) ) != QLatin1String( "false" );
485 : 0 : if ( symbolMap.contains( symbolName ) )
486 : : {
487 : 0 : QgsSymbol *symbol = symbolMap.take( symbolName );
488 : 0 : ranges.append( QgsRendererRange( lowerValue, upperValue, symbol, label, render ) );
489 : 0 : }
490 : 0 : }
491 : 0 : rangeElem = rangeElem.nextSiblingElement();
492 : : }
493 : :
494 : 0 : QString attrName = element.attribute( QStringLiteral( "attr" ) );
495 : :
496 : 0 : QgsGraduatedSymbolRenderer *r = new QgsGraduatedSymbolRenderer( attrName, ranges );
497 : :
498 : 0 : QString attrMethod = element.attribute( QStringLiteral( "graduatedMethod" ) );
499 : 0 : if ( !attrMethod.isEmpty() )
500 : : {
501 : 0 : if ( attrMethod == graduatedMethodStr( GraduatedColor ) )
502 : 0 : r->setGraduatedMethod( GraduatedColor );
503 : 0 : else if ( attrMethod == graduatedMethodStr( GraduatedSize ) )
504 : 0 : r->setGraduatedMethod( GraduatedSize );
505 : 0 : }
506 : :
507 : :
508 : : // delete symbols if there are any more
509 : 0 : QgsSymbolLayerUtils::clearSymbolMap( symbolMap );
510 : :
511 : : // try to load source symbol (optional)
512 : 0 : QDomElement sourceSymbolElem = element.firstChildElement( QStringLiteral( "source-symbol" ) );
513 : 0 : if ( !sourceSymbolElem.isNull() )
514 : : {
515 : 0 : QgsSymbolMap sourceSymbolMap = QgsSymbolLayerUtils::loadSymbols( sourceSymbolElem, context );
516 : 0 : if ( sourceSymbolMap.contains( QStringLiteral( "0" ) ) )
517 : : {
518 : 0 : r->setSourceSymbol( sourceSymbolMap.take( QStringLiteral( "0" ) ) );
519 : 0 : }
520 : 0 : QgsSymbolLayerUtils::clearSymbolMap( sourceSymbolMap );
521 : 0 : }
522 : :
523 : : // try to load color ramp (optional)
524 : 0 : QDomElement sourceColorRampElem = element.firstChildElement( QStringLiteral( "colorramp" ) );
525 : 0 : if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
526 : : {
527 : 0 : r->setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ) );
528 : 0 : }
529 : :
530 : : // try to load mode
531 : :
532 : 0 : QDomElement modeElem = element.firstChildElement( QStringLiteral( "mode" ) ); // old format, backward compatibility
533 : 0 : QDomElement methodElem = element.firstChildElement( QStringLiteral( "classificationMethod" ) );
534 : 0 : QgsClassificationMethod *method = nullptr;
535 : :
536 : : // TODO QGIS 4 Remove
537 : : // backward compatibility for QGIS project < 3.10
538 : 0 : if ( !modeElem.isNull() )
539 : : {
540 : 0 : QString modeString = modeElem.attribute( QStringLiteral( "name" ) );
541 : 0 : QString methodId;
542 : : // the strings saved in the project does not match with the old Mode enum
543 : 0 : if ( modeString == QLatin1String( "equal" ) )
544 : 0 : methodId = QStringLiteral( "EqualInterval" );
545 : 0 : else if ( modeString == QLatin1String( "quantile" ) )
546 : 0 : methodId = QStringLiteral( "Quantile" );
547 : 0 : else if ( modeString == QLatin1String( "jenks" ) )
548 : 0 : methodId = QStringLiteral( "Jenks" );
549 : 0 : else if ( modeString == QLatin1String( "stddev" ) )
550 : 0 : methodId = QStringLiteral( "StdDev" );
551 : 0 : else if ( modeString == QLatin1String( "pretty" ) )
552 : 0 : methodId = QStringLiteral( "Pretty" );
553 : :
554 : 0 : method = QgsApplication::classificationMethodRegistry()->method( methodId );
555 : :
556 : : // symmetric mode
557 : 0 : QDomElement symmetricModeElem = element.firstChildElement( QStringLiteral( "symmetricMode" ) );
558 : 0 : if ( !symmetricModeElem.isNull() )
559 : : {
560 : : // symmetry
561 : 0 : QString symmetricEnabled = symmetricModeElem.attribute( QStringLiteral( "enabled" ) );
562 : 0 : QString symmetricPointString = symmetricModeElem.attribute( QStringLiteral( "symmetryPoint" ) );
563 : 0 : QString astrideEnabled = symmetricModeElem.attribute( QStringLiteral( "astride" ) );
564 : 0 : method->setSymmetricMode( symmetricEnabled == QLatin1String( "true" ), symmetricPointString.toDouble(), astrideEnabled == QLatin1String( "true" ) );
565 : 0 : }
566 : 0 : QDomElement labelFormatElem = element.firstChildElement( QStringLiteral( "labelformat" ) );
567 : 0 : if ( !labelFormatElem.isNull() )
568 : : {
569 : : // label format
570 : 0 : QString format = labelFormatElem.attribute( QStringLiteral( "format" ), "%1" + QStringLiteral( " - " ) + "%2" );
571 : 0 : int precision = labelFormatElem.attribute( QStringLiteral( "decimalplaces" ), QStringLiteral( "4" ) ).toInt();
572 : 0 : bool trimTrailingZeroes = labelFormatElem.attribute( QStringLiteral( "trimtrailingzeroes" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
573 : 0 : method->setLabelFormat( format );
574 : 0 : method->setLabelPrecision( precision );
575 : 0 : method->setLabelTrimTrailingZeroes( trimTrailingZeroes );
576 : 0 : }
577 : : // End of backward compatibility
578 : 0 : }
579 : : else
580 : : {
581 : : // QGIS project 3.10+
582 : 0 : method = QgsClassificationMethod::create( methodElem, context );
583 : : }
584 : :
585 : : // apply the method
586 : 0 : r->setClassificationMethod( method );
587 : :
588 : 0 : QDomElement rotationElem = element.firstChildElement( QStringLiteral( "rotation" ) );
589 : 0 : if ( !rotationElem.isNull() && !rotationElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
590 : : {
591 : 0 : for ( const QgsRendererRange &range : std::as_const( r->mRanges ) )
592 : : {
593 : 0 : convertSymbolRotation( range.symbol(), rotationElem.attribute( QStringLiteral( "field" ) ) );
594 : : }
595 : 0 : if ( r->mSourceSymbol )
596 : : {
597 : 0 : convertSymbolRotation( r->mSourceSymbol.get(), rotationElem.attribute( QStringLiteral( "field" ) ) );
598 : 0 : }
599 : 0 : }
600 : 0 : QDomElement sizeScaleElem = element.firstChildElement( QStringLiteral( "sizescale" ) );
601 : 0 : if ( !sizeScaleElem.isNull() && !sizeScaleElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
602 : : {
603 : 0 : for ( const QgsRendererRange &range : std::as_const( r->mRanges ) )
604 : : {
605 : 0 : convertSymbolSizeScale( range.symbol(),
606 : 0 : QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
607 : 0 : sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
608 : : }
609 : 0 : if ( r->mSourceSymbol && r->mSourceSymbol->type() == QgsSymbol::Marker )
610 : : {
611 : 0 : convertSymbolSizeScale( r->mSourceSymbol.get(),
612 : 0 : QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
613 : 0 : sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
614 : 0 : }
615 : 0 : }
616 : :
617 : 0 : QDomElement ddsLegendSizeElem = element.firstChildElement( QStringLiteral( "data-defined-size-legend" ) );
618 : 0 : if ( !ddsLegendSizeElem.isNull() )
619 : : {
620 : 0 : r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readXml( ddsLegendSizeElem, context ) );
621 : 0 : }
622 : : // TODO: symbol levels
623 : 0 : return r;
624 : 0 : }
625 : :
626 : 0 : QDomElement QgsGraduatedSymbolRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
627 : : {
628 : 0 : QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
629 : 0 : rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "graduatedSymbol" ) );
630 : 0 : rendererElem.setAttribute( QStringLiteral( "symbollevels" ), ( mUsingSymbolLevels ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
631 : 0 : rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
632 : 0 : rendererElem.setAttribute( QStringLiteral( "attr" ), mAttrName );
633 : 0 : rendererElem.setAttribute( QStringLiteral( "graduatedMethod" ), graduatedMethodStr( mGraduatedMethod ) );
634 : :
635 : : // ranges
636 : 0 : int i = 0;
637 : 0 : QgsSymbolMap symbols;
638 : 0 : QDomElement rangesElem = doc.createElement( QStringLiteral( "ranges" ) );
639 : 0 : QgsRangeList::const_iterator it = mRanges.constBegin();
640 : 0 : for ( ; it != mRanges.constEnd(); ++it )
641 : : {
642 : 0 : const QgsRendererRange &range = *it;
643 : 0 : QString symbolName = QString::number( i );
644 : 0 : symbols.insert( symbolName, range.symbol() );
645 : :
646 : 0 : QDomElement rangeElem = doc.createElement( QStringLiteral( "range" ) );
647 : 0 : rangeElem.setAttribute( QStringLiteral( "lower" ), QString::number( range.lowerValue(), 'f', 15 ) );
648 : 0 : rangeElem.setAttribute( QStringLiteral( "upper" ), QString::number( range.upperValue(), 'f', 15 ) );
649 : 0 : rangeElem.setAttribute( QStringLiteral( "symbol" ), symbolName );
650 : 0 : rangeElem.setAttribute( QStringLiteral( "label" ), range.label() );
651 : 0 : rangeElem.setAttribute( QStringLiteral( "render" ), range.renderState() ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
652 : 0 : rangesElem.appendChild( rangeElem );
653 : 0 : i++;
654 : 0 : }
655 : :
656 : 0 : rendererElem.appendChild( rangesElem );
657 : :
658 : : // save symbols
659 : 0 : QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc, context );
660 : 0 : rendererElem.appendChild( symbolsElem );
661 : :
662 : : // save source symbol
663 : 0 : if ( mSourceSymbol )
664 : : {
665 : 0 : QgsSymbolMap sourceSymbols;
666 : 0 : sourceSymbols.insert( QStringLiteral( "0" ), mSourceSymbol.get() );
667 : 0 : QDomElement sourceSymbolElem = QgsSymbolLayerUtils::saveSymbols( sourceSymbols, QStringLiteral( "source-symbol" ), doc, context );
668 : 0 : rendererElem.appendChild( sourceSymbolElem );
669 : 0 : }
670 : :
671 : : // save source color ramp
672 : 0 : if ( mSourceColorRamp )
673 : : {
674 : 0 : QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
675 : 0 : rendererElem.appendChild( colorRampElem );
676 : 0 : }
677 : :
678 : : // save classification method
679 : 0 : QDomElement classificationMethodElem = mClassificationMethod->save( doc, context );
680 : 0 : rendererElem.appendChild( classificationMethodElem );
681 : :
682 : 0 : QDomElement rotationElem = doc.createElement( QStringLiteral( "rotation" ) );
683 : 0 : rendererElem.appendChild( rotationElem );
684 : :
685 : 0 : QDomElement sizeScaleElem = doc.createElement( QStringLiteral( "sizescale" ) );
686 : 0 : rendererElem.appendChild( sizeScaleElem );
687 : :
688 : 0 : if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
689 : 0 : mPaintEffect->saveProperties( doc, rendererElem );
690 : :
691 : 0 : if ( !mOrderBy.isEmpty() )
692 : : {
693 : 0 : QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
694 : 0 : mOrderBy.save( orderBy );
695 : 0 : rendererElem.appendChild( orderBy );
696 : 0 : }
697 : 0 : rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
698 : :
699 : 0 : if ( mDataDefinedSizeLegend )
700 : : {
701 : 0 : QDomElement ddsLegendElem = doc.createElement( QStringLiteral( "data-defined-size-legend" ) );
702 : 0 : mDataDefinedSizeLegend->writeXml( ddsLegendElem, context );
703 : 0 : rendererElem.appendChild( ddsLegendElem );
704 : 0 : }
705 : :
706 : 0 : return rendererElem;
707 : 0 : }
708 : :
709 : 0 : QgsLegendSymbolList QgsGraduatedSymbolRenderer::baseLegendSymbolItems() const
710 : : {
711 : 0 : QgsLegendSymbolList lst;
712 : 0 : int i = 0;
713 : 0 : lst.reserve( mRanges.size() );
714 : 0 : for ( const QgsRendererRange &range : mRanges )
715 : : {
716 : 0 : lst << QgsLegendSymbolItem( range.symbol(), range.label(), QString::number( i++ ), true );
717 : : }
718 : 0 : return lst;
719 : 0 : }
720 : :
721 : : Q_NOWARN_DEPRECATED_PUSH
722 : 0 : QString QgsGraduatedSymbolRenderer::methodIdFromMode( QgsGraduatedSymbolRenderer::Mode mode )
723 : : {
724 : 0 : switch ( mode )
725 : : {
726 : : case EqualInterval:
727 : 0 : return QStringLiteral( "EqualInterval" );
728 : : case Quantile:
729 : 0 : return QStringLiteral( "Quantile" );
730 : : case Jenks:
731 : 0 : return QStringLiteral( "Jenks" );
732 : : case StdDev:
733 : 0 : return QStringLiteral( "StdDev" );
734 : : case Pretty:
735 : 0 : return QStringLiteral( "Pretty" );
736 : : case Custom:
737 : 0 : return QString();
738 : : }
739 : 0 : return QString();
740 : 0 : }
741 : :
742 : 0 : QgsGraduatedSymbolRenderer::Mode QgsGraduatedSymbolRenderer::modeFromMethodId( const QString &methodId )
743 : : {
744 : 0 : if ( methodId == QLatin1String( "EqualInterval" ) )
745 : 0 : return EqualInterval;
746 : 0 : if ( methodId == QLatin1String( "Quantile" ) )
747 : 0 : return Quantile;
748 : 0 : if ( methodId == QLatin1String( "Jenks" ) )
749 : 0 : return Jenks;
750 : 0 : if ( methodId == QLatin1String( "StdDev" ) )
751 : 0 : return StdDev;
752 : 0 : if ( methodId == QLatin1String( "Pretty" ) )
753 : 0 : return Pretty;
754 : : else
755 : 0 : return Custom;
756 : 0 : }
757 : : Q_NOWARN_DEPRECATED_POP
758 : :
759 : 0 : QgsLegendSymbolList QgsGraduatedSymbolRenderer::legendSymbolItems() const
760 : : {
761 : 0 : if ( mDataDefinedSizeLegend && mSourceSymbol && mSourceSymbol->type() == QgsSymbol::Marker )
762 : : {
763 : : // check that all symbols that have the same size expression
764 : 0 : QgsProperty ddSize;
765 : 0 : for ( const QgsRendererRange &range : mRanges )
766 : : {
767 : 0 : const QgsMarkerSymbol *symbol = static_cast<const QgsMarkerSymbol *>( range.symbol() );
768 : 0 : if ( ddSize )
769 : : {
770 : 0 : QgsProperty sSize( symbol->dataDefinedSize() );
771 : 0 : if ( sSize && sSize != ddSize )
772 : : {
773 : : // no common size expression
774 : 0 : return baseLegendSymbolItems();
775 : : }
776 : 0 : }
777 : : else
778 : : {
779 : 0 : ddSize = symbol->dataDefinedSize();
780 : : }
781 : : }
782 : :
783 : 0 : if ( ddSize && ddSize.isActive() )
784 : : {
785 : 0 : QgsLegendSymbolList lst;
786 : :
787 : 0 : QgsDataDefinedSizeLegend ddSizeLegend( *mDataDefinedSizeLegend );
788 : 0 : ddSizeLegend.updateFromSymbolAndProperty( static_cast<const QgsMarkerSymbol *>( mSourceSymbol.get() ), ddSize );
789 : 0 : lst += ddSizeLegend.legendSymbolList();
790 : :
791 : 0 : lst += baseLegendSymbolItems();
792 : 0 : return lst;
793 : 0 : }
794 : 0 : }
795 : :
796 : 0 : return baseLegendSymbolItems();
797 : 0 : }
798 : :
799 : 0 : QSet< QString > QgsGraduatedSymbolRenderer::legendKeysForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
800 : : {
801 : 0 : QVariant value = valueForFeature( feature, context );
802 : :
803 : : // Null values should not be categorized
804 : 0 : if ( value.isNull() )
805 : 0 : return QSet< QString >();
806 : :
807 : : // find the right category
808 : 0 : QString key = legendKeyForValue( value.toDouble() );
809 : 0 : if ( !key.isNull() )
810 : 0 : return QSet< QString >() << key;
811 : : else
812 : 0 : return QSet< QString >();
813 : 0 : }
814 : :
815 : 0 : QgsSymbol *QgsGraduatedSymbolRenderer::sourceSymbol()
816 : : {
817 : 0 : return mSourceSymbol.get();
818 : : }
819 : :
820 : 0 : const QgsSymbol *QgsGraduatedSymbolRenderer::sourceSymbol() const
821 : : {
822 : 0 : return mSourceSymbol.get();
823 : : }
824 : :
825 : 0 : void QgsGraduatedSymbolRenderer::setSourceSymbol( QgsSymbol *sym )
826 : : {
827 : 0 : mSourceSymbol.reset( sym );
828 : 0 : }
829 : :
830 : 0 : QgsColorRamp *QgsGraduatedSymbolRenderer::sourceColorRamp()
831 : : {
832 : 0 : return mSourceColorRamp.get();
833 : : }
834 : :
835 : 0 : const QgsColorRamp *QgsGraduatedSymbolRenderer::sourceColorRamp() const
836 : : {
837 : 0 : return mSourceColorRamp.get();
838 : : }
839 : :
840 : 0 : void QgsGraduatedSymbolRenderer::setSourceColorRamp( QgsColorRamp *ramp )
841 : : {
842 : 0 : if ( ramp == mSourceColorRamp.get() )
843 : 0 : return;
844 : :
845 : 0 : mSourceColorRamp.reset( ramp );
846 : 0 : }
847 : :
848 : 0 : double QgsGraduatedSymbolRenderer::minSymbolSize() const
849 : : {
850 : 0 : double min = std::numeric_limits<double>::max();
851 : 0 : for ( int i = 0; i < mRanges.count(); i++ )
852 : : {
853 : 0 : double sz = 0;
854 : 0 : if ( mRanges[i].symbol()->type() == QgsSymbol::Marker )
855 : 0 : sz = static_cast< QgsMarkerSymbol * >( mRanges[i].symbol() )->size();
856 : 0 : else if ( mRanges[i].symbol()->type() == QgsSymbol::Line )
857 : 0 : sz = static_cast< QgsLineSymbol * >( mRanges[i].symbol() )->width();
858 : 0 : min = std::min( sz, min );
859 : 0 : }
860 : 0 : return min;
861 : : }
862 : :
863 : 0 : double QgsGraduatedSymbolRenderer::maxSymbolSize() const
864 : : {
865 : 0 : double max = std::numeric_limits<double>::min();
866 : 0 : for ( int i = 0; i < mRanges.count(); i++ )
867 : : {
868 : 0 : double sz = 0;
869 : 0 : if ( mRanges[i].symbol()->type() == QgsSymbol::Marker )
870 : 0 : sz = static_cast< QgsMarkerSymbol * >( mRanges[i].symbol() )->size();
871 : 0 : else if ( mRanges[i].symbol()->type() == QgsSymbol::Line )
872 : 0 : sz = static_cast< QgsLineSymbol * >( mRanges[i].symbol() )->width();
873 : 0 : max = std::max( sz, max );
874 : 0 : }
875 : 0 : return max;
876 : : }
877 : :
878 : 0 : void QgsGraduatedSymbolRenderer::setSymbolSizes( double minSize, double maxSize )
879 : : {
880 : 0 : for ( int i = 0; i < mRanges.count(); i++ )
881 : : {
882 : 0 : std::unique_ptr<QgsSymbol> symbol( mRanges.at( i ).symbol() ? mRanges.at( i ).symbol()->clone() : nullptr );
883 : 0 : const double size = mRanges.count() > 1
884 : 0 : ? minSize + i * ( maxSize - minSize ) / ( mRanges.count() - 1 )
885 : 0 : : .5 * ( maxSize + minSize );
886 : 0 : if ( symbol->type() == QgsSymbol::Marker )
887 : 0 : static_cast< QgsMarkerSymbol * >( symbol.get() )->setSize( size );
888 : 0 : if ( symbol->type() == QgsSymbol::Line )
889 : 0 : static_cast< QgsLineSymbol * >( symbol.get() )->setWidth( size );
890 : 0 : updateRangeSymbol( i, symbol.release() );
891 : 0 : }
892 : 0 : }
893 : :
894 : 0 : void QgsGraduatedSymbolRenderer::updateColorRamp( QgsColorRamp *ramp )
895 : : {
896 : 0 : int i = 0;
897 : 0 : if ( ramp )
898 : : {
899 : 0 : setSourceColorRamp( ramp );
900 : 0 : }
901 : :
902 : 0 : if ( mSourceColorRamp )
903 : : {
904 : 0 : for ( const QgsRendererRange &range : std::as_const( mRanges ) )
905 : : {
906 : 0 : QgsSymbol *symbol = range.symbol() ? range.symbol()->clone() : nullptr;
907 : 0 : if ( symbol )
908 : : {
909 : : double colorValue;
910 : 0 : colorValue = ( mRanges.count() > 1 ? static_cast< double >( i ) / ( mRanges.count() - 1 ) : 0 );
911 : 0 : symbol->setColor( mSourceColorRamp->color( colorValue ) );
912 : 0 : }
913 : 0 : updateRangeSymbol( i, symbol );
914 : 0 : ++i;
915 : : }
916 : 0 : }
917 : :
918 : 0 : }
919 : :
920 : 0 : void QgsGraduatedSymbolRenderer::updateSymbols( QgsSymbol *sym )
921 : : {
922 : 0 : if ( !sym )
923 : 0 : return;
924 : :
925 : 0 : int i = 0;
926 : 0 : for ( const QgsRendererRange &range : std::as_const( mRanges ) )
927 : : {
928 : 0 : std::unique_ptr<QgsSymbol> symbol( sym->clone() );
929 : 0 : if ( mGraduatedMethod == GraduatedColor )
930 : : {
931 : 0 : symbol->setColor( range.symbol()->color() );
932 : 0 : }
933 : 0 : else if ( mGraduatedMethod == GraduatedSize )
934 : : {
935 : 0 : if ( symbol->type() == QgsSymbol::Marker )
936 : 0 : static_cast<QgsMarkerSymbol *>( symbol.get() )->setSize(
937 : 0 : static_cast<QgsMarkerSymbol *>( range.symbol() )->size() );
938 : 0 : else if ( symbol->type() == QgsSymbol::Line )
939 : 0 : static_cast<QgsLineSymbol *>( symbol.get() )->setWidth(
940 : 0 : static_cast<QgsLineSymbol *>( range.symbol() )->width() );
941 : 0 : }
942 : 0 : updateRangeSymbol( i, symbol.release() );
943 : 0 : ++i;
944 : 0 : }
945 : 0 : setSourceSymbol( sym->clone() );
946 : 0 : }
947 : :
948 : 0 : bool QgsGraduatedSymbolRenderer::legendSymbolItemsCheckable() const
949 : : {
950 : 0 : return true;
951 : : }
952 : :
953 : 0 : bool QgsGraduatedSymbolRenderer::legendSymbolItemChecked( const QString &key )
954 : : {
955 : : bool ok;
956 : 0 : int index = key.toInt( &ok );
957 : 0 : if ( ok && index >= 0 && index < mRanges.size() )
958 : 0 : return mRanges.at( index ).renderState();
959 : : else
960 : 0 : return true;
961 : 0 : }
962 : :
963 : 0 : void QgsGraduatedSymbolRenderer::checkLegendSymbolItem( const QString &key, bool state )
964 : : {
965 : : bool ok;
966 : 0 : int index = key.toInt( &ok );
967 : 0 : if ( ok )
968 : 0 : updateRangeRenderState( index, state );
969 : 0 : }
970 : :
971 : 0 : void QgsGraduatedSymbolRenderer::setLegendSymbolItem( const QString &key, QgsSymbol *symbol )
972 : : {
973 : : bool ok;
974 : 0 : int index = key.toInt( &ok );
975 : 0 : if ( ok )
976 : 0 : updateRangeSymbol( index, symbol );
977 : : else
978 : 0 : delete symbol;
979 : 0 : }
980 : :
981 : 0 : void QgsGraduatedSymbolRenderer::addClass( QgsSymbol *symbol )
982 : : {
983 : 0 : QgsSymbol *newSymbol = symbol->clone();
984 : 0 : QString label = QStringLiteral( "0.0 - 0.0" );
985 : 0 : mRanges.insert( 0, QgsRendererRange( 0.0, 0.0, newSymbol, label ) );
986 : 0 : }
987 : :
988 : 0 : void QgsGraduatedSymbolRenderer::addClass( double lower, double upper )
989 : : {
990 : 0 : QgsSymbol *newSymbol = mSourceSymbol->clone();
991 : 0 : QString label = mClassificationMethod->labelForRange( lower, upper );
992 : 0 : mRanges.append( QgsRendererRange( lower, upper, newSymbol, label ) );
993 : 0 : }
994 : :
995 : 0 : void QgsGraduatedSymbolRenderer::addBreak( double breakValue, bool updateSymbols )
996 : : {
997 : 0 : QMutableListIterator< QgsRendererRange > it( mRanges );
998 : 0 : while ( it.hasNext() )
999 : : {
1000 : 0 : QgsRendererRange range = it.next();
1001 : 0 : if ( range.lowerValue() < breakValue && range.upperValue() > breakValue )
1002 : : {
1003 : 0 : QgsRendererRange newRange = QgsRendererRange();
1004 : 0 : newRange.setLowerValue( breakValue );
1005 : 0 : newRange.setUpperValue( range.upperValue() );
1006 : 0 : newRange.setLabel( mClassificationMethod->labelForRange( newRange ) );
1007 : 0 : newRange.setSymbol( mSourceSymbol->clone() );
1008 : :
1009 : : //update old range
1010 : 0 : bool isDefaultLabel = range.label() == mClassificationMethod->labelForRange( range );
1011 : 0 : range.setUpperValue( breakValue );
1012 : 0 : if ( isDefaultLabel )
1013 : 0 : range.setLabel( mClassificationMethod->labelForRange( range.lowerValue(), breakValue ) );
1014 : 0 : it.setValue( range );
1015 : :
1016 : 0 : it.insert( newRange );
1017 : : break;
1018 : 0 : }
1019 : 0 : }
1020 : :
1021 : 0 : if ( updateSymbols )
1022 : : {
1023 : 0 : switch ( mGraduatedMethod )
1024 : : {
1025 : : case GraduatedColor:
1026 : 0 : updateColorRamp( mSourceColorRamp.get() );
1027 : 0 : break;
1028 : : case GraduatedSize:
1029 : 0 : setSymbolSizes( minSymbolSize(), maxSymbolSize() );
1030 : 0 : break;
1031 : : }
1032 : 0 : }
1033 : 0 : }
1034 : :
1035 : 0 : void QgsGraduatedSymbolRenderer::addClass( const QgsRendererRange &range )
1036 : : {
1037 : 0 : mRanges.append( range );
1038 : 0 : }
1039 : :
1040 : 0 : void QgsGraduatedSymbolRenderer::deleteClass( int idx )
1041 : : {
1042 : 0 : mRanges.removeAt( idx );
1043 : 0 : }
1044 : :
1045 : 0 : void QgsGraduatedSymbolRenderer::deleteAllClasses()
1046 : : {
1047 : 0 : mRanges.clear();
1048 : 0 : }
1049 : :
1050 : : Q_NOWARN_DEPRECATED_PUSH
1051 : 0 : void QgsGraduatedSymbolRenderer::setLabelFormat( const QgsRendererRangeLabelFormat &labelFormat, bool updateRanges )
1052 : : {
1053 : 0 : mClassificationMethod->setLabelFormat( labelFormat.format() );
1054 : 0 : mClassificationMethod->setLabelPrecision( labelFormat.precision() );
1055 : 0 : mClassificationMethod->setLabelTrimTrailingZeroes( labelFormat.trimTrailingZeroes() );
1056 : :
1057 : 0 : if ( updateRanges )
1058 : : {
1059 : 0 : updateRangeLabels();
1060 : 0 : }
1061 : 0 : }
1062 : : Q_NOWARN_DEPRECATED_POP
1063 : :
1064 : 0 : void QgsGraduatedSymbolRenderer::updateRangeLabels()
1065 : : {
1066 : 0 : for ( int i = 0; i < mRanges.count(); i++ )
1067 : : {
1068 : 0 : QgsClassificationMethod::ClassPosition pos = QgsClassificationMethod::Inner;
1069 : 0 : if ( i == 0 )
1070 : 0 : pos = QgsClassificationMethod::LowerBound;
1071 : 0 : else if ( i == mRanges.count() - 1 )
1072 : 0 : pos = QgsClassificationMethod::UpperBound;
1073 : 0 : mRanges[i].setLabel( mClassificationMethod->labelForRange( mRanges[i], pos ) );
1074 : 0 : }
1075 : 0 : }
1076 : :
1077 : 0 : void QgsGraduatedSymbolRenderer::calculateLabelPrecision( bool updateRanges )
1078 : : {
1079 : : // Find the minimum size of a class
1080 : 0 : double minClassRange = 0.0;
1081 : 0 : for ( const QgsRendererRange &rendererRange : std::as_const( mRanges ) )
1082 : : {
1083 : 0 : double range = rendererRange.upperValue() - rendererRange.lowerValue();
1084 : 0 : if ( range <= 0.0 )
1085 : 0 : continue;
1086 : 0 : if ( minClassRange == 0.0 || range < minClassRange )
1087 : 0 : minClassRange = range;
1088 : : }
1089 : 0 : if ( minClassRange <= 0.0 )
1090 : 0 : return;
1091 : :
1092 : : // Now set the number of decimal places to ensure no more than 20% error in
1093 : : // representing this range (up to 10% at upper and lower end)
1094 : :
1095 : 0 : int ndp = 10;
1096 : 0 : double nextDpMinRange = 0.0000000099;
1097 : 0 : while ( ndp > 0 && nextDpMinRange < minClassRange )
1098 : : {
1099 : 0 : ndp--;
1100 : 0 : nextDpMinRange *= 10.0;
1101 : : }
1102 : 0 : mClassificationMethod->setLabelPrecision( ndp );
1103 : 0 : if ( updateRanges )
1104 : 0 : updateRangeLabels();
1105 : 0 : }
1106 : :
1107 : 0 : void QgsGraduatedSymbolRenderer::moveClass( int from, int to )
1108 : : {
1109 : 0 : if ( from < 0 || from >= mRanges.size() || to < 0 || to >= mRanges.size() )
1110 : 0 : return;
1111 : 0 : mRanges.move( from, to );
1112 : 0 : }
1113 : :
1114 : 0 : bool valueLessThan( const QgsRendererRange &r1, const QgsRendererRange &r2 )
1115 : : {
1116 : 0 : return r1 < r2;
1117 : : }
1118 : :
1119 : 0 : bool valueGreaterThan( const QgsRendererRange &r1, const QgsRendererRange &r2 )
1120 : : {
1121 : 0 : return !valueLessThan( r1, r2 );
1122 : : }
1123 : :
1124 : 0 : void QgsGraduatedSymbolRenderer::sortByValue( Qt::SortOrder order )
1125 : : {
1126 : 0 : if ( order == Qt::AscendingOrder )
1127 : : {
1128 : 0 : std::sort( mRanges.begin(), mRanges.end(), valueLessThan );
1129 : 0 : }
1130 : : else
1131 : : {
1132 : 0 : std::sort( mRanges.begin(), mRanges.end(), valueGreaterThan );
1133 : : }
1134 : 0 : }
1135 : :
1136 : 0 : bool QgsGraduatedSymbolRenderer::rangesOverlap() const
1137 : : {
1138 : 0 : QgsRangeList sortedRanges = mRanges;
1139 : 0 : std::sort( sortedRanges.begin(), sortedRanges.end(), valueLessThan );
1140 : :
1141 : 0 : QgsRangeList::const_iterator it = sortedRanges.constBegin();
1142 : 0 : if ( it == sortedRanges.constEnd() )
1143 : 0 : return false;
1144 : :
1145 : 0 : if ( ( *it ).upperValue() < ( *it ).lowerValue() )
1146 : 0 : return true;
1147 : :
1148 : 0 : double prevMax = ( *it ).upperValue();
1149 : 0 : ++it;
1150 : :
1151 : 0 : for ( ; it != sortedRanges.constEnd(); ++it )
1152 : : {
1153 : 0 : if ( ( *it ).upperValue() < ( *it ).lowerValue() )
1154 : 0 : return true;
1155 : :
1156 : 0 : if ( ( *it ).lowerValue() < prevMax )
1157 : 0 : return true;
1158 : :
1159 : 0 : prevMax = ( *it ).upperValue();
1160 : 0 : }
1161 : 0 : return false;
1162 : 0 : }
1163 : :
1164 : 0 : bool QgsGraduatedSymbolRenderer::rangesHaveGaps() const
1165 : : {
1166 : 0 : QgsRangeList sortedRanges = mRanges;
1167 : 0 : std::sort( sortedRanges.begin(), sortedRanges.end(), valueLessThan );
1168 : :
1169 : 0 : QgsRangeList::const_iterator it = sortedRanges.constBegin();
1170 : 0 : if ( it == sortedRanges.constEnd() )
1171 : 0 : return false;
1172 : :
1173 : 0 : double prevMax = ( *it ).upperValue();
1174 : 0 : ++it;
1175 : :
1176 : 0 : for ( ; it != sortedRanges.constEnd(); ++it )
1177 : : {
1178 : 0 : if ( !qgsDoubleNear( ( *it ).lowerValue(), prevMax ) )
1179 : 0 : return true;
1180 : :
1181 : 0 : prevMax = ( *it ).upperValue();
1182 : 0 : }
1183 : 0 : return false;
1184 : 0 : }
1185 : :
1186 : 0 : bool labelLessThan( const QgsRendererRange &r1, const QgsRendererRange &r2 )
1187 : : {
1188 : 0 : return QString::localeAwareCompare( r1.label(), r2.label() ) < 0;
1189 : 0 : }
1190 : :
1191 : 0 : bool labelGreaterThan( const QgsRendererRange &r1, const QgsRendererRange &r2 )
1192 : : {
1193 : 0 : return !labelLessThan( r1, r2 );
1194 : : }
1195 : :
1196 : 0 : void QgsGraduatedSymbolRenderer::sortByLabel( Qt::SortOrder order )
1197 : : {
1198 : 0 : if ( order == Qt::AscendingOrder )
1199 : : {
1200 : 0 : std::sort( mRanges.begin(), mRanges.end(), labelLessThan );
1201 : 0 : }
1202 : : else
1203 : : {
1204 : 0 : std::sort( mRanges.begin(), mRanges.end(), labelGreaterThan );
1205 : : }
1206 : 0 : }
1207 : :
1208 : 0 : QgsClassificationMethod *QgsGraduatedSymbolRenderer::classificationMethod() const
1209 : : {
1210 : 0 : return mClassificationMethod.get();
1211 : : }
1212 : :
1213 : 0 : void QgsGraduatedSymbolRenderer::setClassificationMethod( QgsClassificationMethod *method )
1214 : : {
1215 : 0 : mClassificationMethod.reset( method );
1216 : 0 : }
1217 : :
1218 : 0 : void QgsGraduatedSymbolRenderer::setMode( QgsGraduatedSymbolRenderer::Mode mode )
1219 : : {
1220 : 0 : QString methodId = methodIdFromMode( mode );
1221 : 0 : QgsClassificationMethod *method = QgsApplication::classificationMethodRegistry()->method( methodId );
1222 : 0 : setClassificationMethod( method );
1223 : 0 : }
1224 : :
1225 : 0 : void QgsGraduatedSymbolRenderer::setUseSymmetricMode( bool useSymmetricMode ) SIP_DEPRECATED
1226 : : {
1227 : 0 : mClassificationMethod->setSymmetricMode( useSymmetricMode, mClassificationMethod->symmetryPoint(), mClassificationMethod->symmetryAstride() );
1228 : 0 : }
1229 : :
1230 : 0 : void QgsGraduatedSymbolRenderer::setSymmetryPoint( double symmetryPoint ) SIP_DEPRECATED
1231 : : {
1232 : 0 : mClassificationMethod->setSymmetricMode( mClassificationMethod->symmetricModeEnabled(), symmetryPoint, mClassificationMethod->symmetryAstride() );
1233 : 0 : }
1234 : :
1235 : 0 : void QgsGraduatedSymbolRenderer::setAstride( bool astride ) SIP_DEPRECATED
1236 : : {
1237 : 0 : mClassificationMethod->setSymmetricMode( mClassificationMethod->symmetricModeEnabled(), mClassificationMethod->symmetryPoint(), astride );
1238 : 0 : }
1239 : :
1240 : 0 : QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::convertFromRenderer( const QgsFeatureRenderer *renderer )
1241 : : {
1242 : 0 : std::unique_ptr< QgsGraduatedSymbolRenderer > r;
1243 : 0 : if ( renderer->type() == QLatin1String( "graduatedSymbol" ) )
1244 : : {
1245 : 0 : r.reset( static_cast<QgsGraduatedSymbolRenderer *>( renderer->clone() ) );
1246 : 0 : }
1247 : 0 : else if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
1248 : : {
1249 : 0 : const QgsCategorizedSymbolRenderer *categorizedSymbolRenderer = dynamic_cast<const QgsCategorizedSymbolRenderer *>( renderer );
1250 : 0 : if ( categorizedSymbolRenderer )
1251 : : {
1252 : 0 : r = std::make_unique< QgsGraduatedSymbolRenderer >( QString(), QgsRangeList() );
1253 : 0 : if ( categorizedSymbolRenderer->sourceSymbol() )
1254 : 0 : r->setSourceSymbol( categorizedSymbolRenderer->sourceSymbol()->clone() );
1255 : 0 : if ( categorizedSymbolRenderer->sourceColorRamp() )
1256 : : {
1257 : 0 : bool isRandom = dynamic_cast<const QgsRandomColorRamp *>( categorizedSymbolRenderer->sourceColorRamp() ) ||
1258 : 0 : dynamic_cast<const QgsLimitedRandomColorRamp *>( categorizedSymbolRenderer->sourceColorRamp() );
1259 : 0 : if ( !isRandom )
1260 : 0 : r->setSourceColorRamp( categorizedSymbolRenderer->sourceColorRamp()->clone() );
1261 : 0 : }
1262 : 0 : r->setClassAttribute( categorizedSymbolRenderer->classAttribute() );
1263 : 0 : }
1264 : 0 : }
1265 : 0 : else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
1266 : : {
1267 : 0 : const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer );
1268 : 0 : if ( pointDistanceRenderer )
1269 : 0 : r.reset( convertFromRenderer( pointDistanceRenderer->embeddedRenderer() ) );
1270 : 0 : }
1271 : 0 : else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
1272 : : {
1273 : 0 : const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer );
1274 : 0 : if ( invertedPolygonRenderer )
1275 : 0 : r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1276 : 0 : }
1277 : :
1278 : : // If not one of the specifically handled renderers, then just grab the symbol from the renderer
1279 : : // Could have applied this to specific renderer types (singleSymbol, graduatedSymbol)
1280 : :
1281 : 0 : if ( !r )
1282 : : {
1283 : 0 : r = std::make_unique< QgsGraduatedSymbolRenderer >( QString(), QgsRangeList() );
1284 : 0 : QgsRenderContext context;
1285 : 0 : QgsSymbolList symbols = const_cast<QgsFeatureRenderer *>( renderer )->symbols( context );
1286 : 0 : if ( !symbols.isEmpty() )
1287 : : {
1288 : 0 : r->setSourceSymbol( symbols.at( 0 )->clone() );
1289 : 0 : }
1290 : 0 : }
1291 : :
1292 : 0 : r->setOrderBy( renderer->orderBy() );
1293 : 0 : r->setOrderByEnabled( renderer->orderByEnabled() );
1294 : :
1295 : 0 : return r.release();
1296 : 0 : }
1297 : :
1298 : 0 : void QgsGraduatedSymbolRenderer::setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings )
1299 : : {
1300 : 0 : mDataDefinedSizeLegend.reset( settings );
1301 : 0 : }
1302 : :
1303 : 0 : QgsDataDefinedSizeLegend *QgsGraduatedSymbolRenderer::dataDefinedSizeLegend() const
1304 : : {
1305 : 0 : return mDataDefinedSizeLegend.get();
1306 : : }
1307 : :
1308 : 0 : QString QgsGraduatedSymbolRenderer::graduatedMethodStr( GraduatedMethod method )
1309 : : {
1310 : 0 : switch ( method )
1311 : : {
1312 : : case GraduatedColor:
1313 : 0 : return QStringLiteral( "GraduatedColor" );
1314 : : case GraduatedSize:
1315 : 0 : return QStringLiteral( "GraduatedSize" );
1316 : : }
1317 : 0 : return QString();
1318 : 0 : }
1319 : :
1320 : :
|