QGIS API Documentation 3.43.0-Master (56aa1fd18d7)
qgscategorizedsymbolrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscategorizedsymbolrenderer.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#include <algorithm>
16
18
20#include "qgssymbol.h"
21#include "qgssymbollayerutils.h"
22#include "qgscolorramp.h"
23#include "qgscolorrampimpl.h"
27#include "qgspainteffect.h"
28#include "qgssymbollayer.h"
29#include "qgsfeature.h"
30#include "qgsvectorlayer.h"
31#include "qgslogger.h"
32#include "qgsproperty.h"
33#include "qgsstyle.h"
34#include "qgsfieldformatter.h"
36#include "qgsapplication.h"
40#include "qgsmarkersymbol.h"
42#include "qgssldexportcontext.h"
43
44#include <QDomDocument>
45#include <QDomElement>
46#include <QSettings> // for legend
47#include <QRegularExpression>
48#include <QUuid>
49
50QgsRendererCategory::QgsRendererCategory( const QVariant &value, QgsSymbol *symbol, const QString &label, bool render, const QString &uuid )
51 : mValue( value )
52 , mSymbol( symbol )
53 , mLabel( label )
54 , mRender( render )
55{
56 mUuid = !uuid.isEmpty() ? uuid : QUuid::createUuid().toString();
57}
58
60 : mValue( cat.mValue )
61 , mSymbol( cat.mSymbol ? cat.mSymbol->clone() : nullptr )
62 , mLabel( cat.mLabel )
63 , mRender( cat.mRender )
64 , mUuid( cat.mUuid )
65{
66}
67
69{
70 mValue = cat.mValue;
71 mSymbol.reset( cat.mSymbol ? cat.mSymbol->clone() : nullptr );
72 mLabel = cat.mLabel;
73 mRender = cat.mRender;
74 mUuid = cat.mUuid;
75 return *this;
76}
77
79
81{
82 return mUuid;
83}
84
86{
87 return mValue;
88}
89
91{
92 return mSymbol.get();
93}
94
96{
97 return mLabel;
98}
99
101{
102 return mRender;
103}
104
105void QgsRendererCategory::setValue( const QVariant &value )
106{
107 mValue = value;
108}
109
111{
112 if ( mSymbol.get() != s ) mSymbol.reset( s );
113}
114
115void QgsRendererCategory::setLabel( const QString &label )
116{
117 mLabel = label;
118}
119
121{
122 mRender = render;
123}
124
126{
127 return QStringLiteral( "%1::%2::%3:%4\n" ).arg( mValue.toString(), mLabel, mSymbol->dump() ).arg( mRender );
128}
129
130void QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
131{
132 if ( !mSymbol.get() || props.value( QStringLiteral( "attribute" ), QString() ).toString().isEmpty() )
133 return;
134
135 QString attrName = props[ QStringLiteral( "attribute" )].toString();
136
137 QgsSldExportContext context;
138 context.setExtraProperties( props );
139 toSld( doc, element, attrName, context );
140}
141
142bool QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, const QString &classAttribute, QgsSldExportContext &context ) const
143{
144 if ( !mSymbol.get() || classAttribute.isEmpty() )
145 return false;
146
147 QString attrName = classAttribute;
148
149 // try to determine if attribute name is actually a field reference or expression.
150 // If it's a field reference, we need to quote it.
151 // Because we don't have access to the layer or fields here, we treat a parser error
152 // as just an unquoted field name (eg a field name with spaces)
153 const QgsExpression attrExpression = QgsExpression( attrName );
154 if ( attrExpression.hasParserError() )
155 {
156 attrName = QgsExpression::quotedColumnRef( attrName );
157 }
158 else if ( attrExpression.isField() )
159 {
161 qgis::down_cast<const QgsExpressionNodeColumnRef *>( attrExpression.rootNode() )->name()
162 );
163 }
164
165 QDomElement ruleElem = doc.createElement( QStringLiteral( "se:Rule" ) );
166
167 QDomElement nameElem = doc.createElement( QStringLiteral( "se:Name" ) );
168 nameElem.appendChild( doc.createTextNode( mLabel ) );
169 ruleElem.appendChild( nameElem );
170
171 QDomElement descrElem = doc.createElement( QStringLiteral( "se:Description" ) );
172 QDomElement titleElem = doc.createElement( QStringLiteral( "se:Title" ) );
173 QString descrStr = QStringLiteral( "%1 is '%2'" ).arg( attrName, mValue.toString() );
174 titleElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : descrStr ) );
175 descrElem.appendChild( titleElem );
176 ruleElem.appendChild( descrElem );
177
178 // create the ogc:Filter for the range
179 QString filterFunc;
180 if ( mValue.userType() == QMetaType::Type::QVariantList )
181 {
182 const QVariantList list = mValue.toList();
183 if ( list.size() == 1 )
184 {
185 filterFunc = QStringLiteral( "%1 = %2" ).arg( attrName, QgsExpression::quotedValue( list.at( 0 ) ) );
186 }
187 else
188 {
189 QStringList valuesList;
190 valuesList.reserve( list.size() );
191 for ( const QVariant &v : list )
192 {
193 valuesList << QgsExpression::quotedValue( v );
194 }
195 filterFunc = QStringLiteral( "%1 IN (%2)" ).arg( attrName,
196 valuesList.join( ',' ) );
197 }
198 }
199 else if ( QgsVariantUtils::isNull( mValue ) || mValue.toString().isEmpty() )
200 {
201 filterFunc = QStringLiteral( "ELSE" );
202 }
203 else
204 {
205 filterFunc = QStringLiteral( "%1 = %2" ).arg( attrName, QgsExpression::quotedValue( mValue ) );
206 }
207
208 QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, filterFunc, context );
209
210 // add the mix/max scale denoms if we got any from the callers
211 const QVariantMap oldProps = context.extraProperties();
212 QVariantMap props = oldProps;
213 QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props );
214 context.setExtraProperties( props );
215 mSymbol->toSld( doc, ruleElem, context );
216 context.setExtraProperties( oldProps );
217 if ( !QgsSymbolLayerUtils::hasSldSymbolizer( ruleElem ) )
218 {
219 // symbol could not be converted to SLD, or is an "empty" symbol. In this case we do not generate a rule, as
220 // SLD spec requires a Symbolizer element to be present
221 return false;
222 }
223
224 element.appendChild( ruleElem );
225 return true;
226}
227
229
231 : QgsFeatureRenderer( QStringLiteral( "categorizedSymbol" ) )
232 , mAttrName( attrName )
233{
234 //important - we need a deep copy of the categories list, not a shared copy. This is required because
235 //QgsRendererCategory::symbol() is marked const, and so retrieving the symbol via this method does not
236 //trigger a detachment and copy of mCategories BUT that same method CAN be used to modify a symbol in place
237 for ( const QgsRendererCategory &cat : categories )
238 {
239 if ( !cat.symbol() )
240 {
241 QgsDebugError( QStringLiteral( "invalid symbol in a category! ignoring..." ) );
242 }
243 mCategories << cat;
244 }
245}
246
248{
250 QgsCategoryList::const_iterator catIt = mCategories.constBegin();
251 for ( ; catIt != mCategories.constEnd(); ++catIt )
252 {
253 if ( QgsSymbol *catSymbol = catIt->symbol() )
254 {
255 if ( catSymbol->flags().testFlag( Qgis::SymbolFlag::AffectsLabeling ) )
257 }
258 }
259
260 return res;
261}
262
264
266{
267 mSymbolHash.clear();
268
269 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
270 {
271 const QVariant val = cat.value();
272 if ( val.userType() == QMetaType::Type::QVariantList )
273 {
274 const QVariantList list = val.toList();
275 for ( const QVariant &v : list )
276 {
277 mSymbolHash.insert( v.toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : nullptr );
278 }
279 }
280 else
281 {
282 mSymbolHash.insert( val.toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : nullptr );
283 }
284 }
285}
286
291
293{
294 bool found = false;
295 return symbolForValue( value, found );
296}
297
298QgsSymbol *QgsCategorizedSymbolRenderer::symbolForValue( const QVariant &value, bool &foundMatchingSymbol ) const
299{
300 foundMatchingSymbol = false;
301
302 // TODO: special case for int, double
303 QHash<QString, QgsSymbol *>::const_iterator it = mSymbolHash.constFind( QgsVariantUtils::isNull( value ) ? QString() : value.toString() );
304 if ( it == mSymbolHash.constEnd() )
305 {
306 if ( mSymbolHash.isEmpty() )
307 {
308 QgsDebugError( QStringLiteral( "there are no hashed symbols!!!" ) );
309 }
310 else
311 {
312 QgsDebugMsgLevel( "attribute value not found: " + value.toString(), 3 );
313 }
314 return nullptr;
315 }
316
317 foundMatchingSymbol = true;
318
319 return *it;
320}
321
323{
324 return originalSymbolForFeature( feature, context );
325}
326
327QVariant QgsCategorizedSymbolRenderer::valueForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
328{
329 QgsAttributes attrs = feature.attributes();
330 QVariant value;
331 if ( mAttrNum == -1 )
332 {
333 Q_ASSERT( mExpression );
334
335 value = mExpression->evaluate( &context.expressionContext() );
336 }
337 else
338 {
339 value = attrs.value( mAttrNum );
340 }
341
342 return value;
343}
344
346{
347 QVariant value = valueForFeature( feature, context );
348
349 bool foundCategory = false;
350 // find the right symbol for the category
351 QgsSymbol *symbol = symbolForValue( value, foundCategory );
352
353 if ( !foundCategory )
354 {
355 // if no symbol found, use default symbol
356 return symbolForValue( QVariant( "" ), foundCategory );
357 }
358
359 return symbol;
360}
361
362
364{
365 for ( int i = 0; i < mCategories.count(); i++ )
366 {
367 if ( mCategories[i].value() == val )
368 return i;
369 }
370 return -1;
371}
372
374{
375 int idx = -1;
376 for ( int i = 0; i < mCategories.count(); i++ )
377 {
378 if ( mCategories[i].label() == val )
379 {
380 if ( idx != -1 )
381 return -1;
382 else
383 idx = i;
384 }
385 }
386 return idx;
387}
388
389bool QgsCategorizedSymbolRenderer::updateCategoryValue( int catIndex, const QVariant &value )
390{
391 if ( catIndex < 0 || catIndex >= mCategories.size() )
392 return false;
393 mCategories[catIndex].setValue( value );
394 return true;
395}
396
398{
399 if ( catIndex < 0 || catIndex >= mCategories.size() )
400 return false;
401 mCategories[catIndex].setSymbol( symbol );
402 return true;
403}
404
405bool QgsCategorizedSymbolRenderer::updateCategoryLabel( int catIndex, const QString &label )
406{
407 if ( catIndex < 0 || catIndex >= mCategories.size() )
408 return false;
409 mCategories[catIndex].setLabel( label );
410 return true;
411}
412
414{
415 if ( catIndex < 0 || catIndex >= mCategories.size() )
416 return false;
417 mCategories[catIndex].setRenderState( render );
418 return true;
419}
420
422{
423 if ( !cat.symbol() )
424 {
425 QgsDebugError( QStringLiteral( "invalid symbol in a category! ignoring..." ) );
426 return;
427 }
428
429 mCategories.append( cat );
430}
431
433{
434 if ( catIndex < 0 || catIndex >= mCategories.size() )
435 return false;
436
437 mCategories.removeAt( catIndex );
438 return true;
439}
440
445
447{
448 if ( from < 0 || from >= mCategories.size() || to < 0 || to >= mCategories.size() ) return;
449 mCategories.move( from, to );
450}
451
453{
454 return qgsVariantLessThan( c1.value(), c2.value() );
455}
457{
458 return qgsVariantGreaterThan( c1.value(), c2.value() );
459}
460
462{
463 if ( order == Qt::AscendingOrder )
464 {
465 std::sort( mCategories.begin(), mCategories.end(), valueLessThan );
466 }
467 else
468 {
469 std::sort( mCategories.begin(), mCategories.end(), valueGreaterThan );
470 }
471}
472
474{
475 return QString::localeAwareCompare( c1.label(), c2.label() ) < 0;
476}
477
479{
480 return !labelLessThan( c1, c2 );
481}
482
484{
485 if ( order == Qt::AscendingOrder )
486 {
487 std::sort( mCategories.begin(), mCategories.end(), labelLessThan );
488 }
489 else
490 {
491 std::sort( mCategories.begin(), mCategories.end(), labelGreaterThan );
492 }
493}
494
496{
497 QgsFeatureRenderer::startRender( context, fields );
498
499 mCounting = context.rendererScale() == 0.0;
500
501 // make sure that the hash table is up to date
502 rebuildHash();
503
504 // find out classification attribute index from name
505 mAttrNum = fields.lookupField( mAttrName );
506 if ( mAttrNum == -1 )
507 {
508 mExpression.reset( new QgsExpression( mAttrName ) );
509 mExpression->prepare( &context.expressionContext() );
510 }
511
512 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
513 {
514 cat.symbol()->startRender( context, fields );
515 }
516}
517
519{
521
522 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
523 {
524 cat.symbol()->stopRender( context );
525 }
526 mExpression.reset();
527}
528
530{
531 QSet<QString> attributes;
532
533 // mAttrName can contain either attribute name or an expression.
534 // Sometimes it is not possible to distinguish between those two,
535 // e.g. "a - b" can be both a valid attribute name or expression.
536 // Since we do not have access to fields here, try both options.
537 attributes << mAttrName;
538
539 QgsExpression testExpr( mAttrName );
540 if ( !testExpr.hasParserError() )
541 attributes.unite( testExpr.referencedColumns() );
542
543 QgsCategoryList::const_iterator catIt = mCategories.constBegin();
544 for ( ; catIt != mCategories.constEnd(); ++catIt )
545 {
546 QgsSymbol *catSymbol = catIt->symbol();
547 if ( catSymbol )
548 {
549 attributes.unite( catSymbol->usedAttributes( context ) );
550 }
551 }
552 return attributes;
553}
554
556{
557 QgsExpression testExpr( mAttrName );
558 if ( !testExpr.hasParserError() )
559 {
560 QgsExpressionContext context;
561 context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
562 testExpr.prepare( &context );
563 return testExpr.needsGeometry();
564 }
565 return false;
566}
567
569{
570 QString s = QStringLiteral( "CATEGORIZED: idx %1\n" ).arg( mAttrName );
571 for ( int i = 0; i < mCategories.count(); i++ )
572 s += mCategories[i].dump();
573 return s;
574}
575
590
591void QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
592{
593 QgsSldExportContext context;
594 context.setExtraProperties( props );
595 toSld( doc, element, context );
596}
597
598bool QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
599{
600 const QVariantMap oldProps = context.extraProperties();
601 QVariantMap newProps = oldProps;
602 newProps[ QStringLiteral( "attribute" )] = mAttrName;
603 context.setExtraProperties( newProps );
604
605 // create a Rule for each range
606 bool result = true;
607 for ( QgsCategoryList::const_iterator it = mCategories.constBegin(); it != mCategories.constEnd(); ++it )
608 {
609 if ( !it->toSld( doc, element, mAttrName, context ) )
610 result = false;
611 }
612 context.setExtraProperties( oldProps );
613 return result;
614}
615
617{
618 int attrNum = fields.lookupField( mAttrName );
619 bool isExpression = ( attrNum == -1 );
620
621 bool hasDefault = false;
622 bool defaultActive = false;
623 bool allActive = true;
624 bool noneActive = true;
625
626 //we need to build lists of both inactive and active values, as either list may be required
627 //depending on whether the default category is active or not
628 QString activeValues;
629 QString inactiveValues;
630
631 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
632 {
633 if ( cat.value() == "" || QgsVariantUtils::isNull( cat.value() ) )
634 {
635 hasDefault = true;
636 defaultActive = cat.renderState();
637 }
638
639 noneActive = noneActive && !cat.renderState();
640 allActive = allActive && cat.renderState();
641
642 const bool isList = cat.value().userType() == QMetaType::Type::QVariantList;
643 QString value = QgsExpression::quotedValue( cat.value(), static_cast<QMetaType::Type>( cat.value().userType() ) );
644
645 if ( !cat.renderState() )
646 {
647 if ( value != "" )
648 {
649 if ( isList )
650 {
651 const QVariantList list = cat.value().toList();
652 for ( const QVariant &v : list )
653 {
654 if ( !inactiveValues.isEmpty() )
655 inactiveValues.append( ',' );
656
657 inactiveValues.append( QgsExpression::quotedValue( v, isExpression ? static_cast<QMetaType::Type>( v.userType() ) : fields.at( attrNum ).type() ) );
658 }
659 }
660 else
661 {
662 if ( !inactiveValues.isEmpty() )
663 inactiveValues.append( ',' );
664
665 inactiveValues.append( value );
666 }
667 }
668 }
669 else
670 {
671 if ( value != "" )
672 {
673 if ( isList )
674 {
675 const QVariantList list = cat.value().toList();
676 for ( const QVariant &v : list )
677 {
678 if ( !activeValues.isEmpty() )
679 activeValues.append( ',' );
680
681 activeValues.append( QgsExpression::quotedValue( v, isExpression ? static_cast<QMetaType::Type>( v.userType() ) : fields.at( attrNum ).type() ) );
682 }
683 }
684 else
685 {
686 if ( !activeValues.isEmpty() )
687 activeValues.append( ',' );
688
689 activeValues.append( value );
690 }
691 }
692 }
693 }
694
695 QString attr = isExpression ? mAttrName : QStringLiteral( "\"%1\"" ).arg( mAttrName );
696
697 if ( allActive && hasDefault )
698 {
699 return QString();
700 }
701 else if ( noneActive )
702 {
703 return QStringLiteral( "FALSE" );
704 }
705 else if ( defaultActive )
706 {
707 return QStringLiteral( "(%1) NOT IN (%2) OR (%1) IS NULL" ).arg( attr, inactiveValues );
708 }
709 else
710 {
711 return QStringLiteral( "(%1) IN (%2)" ).arg( attr, activeValues );
712 }
713}
714
716{
717 Q_UNUSED( context )
718 QgsSymbolList lst;
719 lst.reserve( mCategories.count() );
720 for ( const QgsRendererCategory &cat : mCategories )
721 {
722 lst.append( cat.symbol() );
723 }
724 return lst;
725}
726
728{
729 for ( const QgsRendererCategory &cat : mCategories )
730 {
731 QgsStyleSymbolEntity entity( cat.symbol() );
732 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, cat.value().toString(), cat.label() ) ) )
733 return false;
734 }
735
736 if ( mSourceColorRamp )
737 {
739 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity ) ) )
740 return false;
741 }
742
743 return true;
744}
745
747{
748 QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
749 if ( symbolsElem.isNull() )
750 return nullptr;
751
752 QDomElement catsElem = element.firstChildElement( QStringLiteral( "categories" ) );
753 if ( catsElem.isNull() )
754 return nullptr;
755
756 QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
757 QgsCategoryList cats;
758
759 // Value from string (long, ulong, double and string)
760 const auto valueFromString = []( const QString & value, const QString & valueType ) -> QVariant
761 {
762 if ( valueType == QLatin1String( "double" ) )
763 {
764 bool ok;
765 const auto val { value.toDouble( &ok ) };
766 if ( ok )
767 {
768 return val;
769 }
770 }
771 else if ( valueType == QLatin1String( "ulong" ) )
772 {
773 bool ok;
774 const auto val { value.toULongLong( &ok ) };
775 if ( ok )
776 {
777 return val;
778 }
779 }
780 else if ( valueType == QLatin1String( "long" ) )
781 {
782 bool ok;
783 const auto val { value.toLongLong( &ok ) };
784 if ( ok )
785 {
786 return val;
787 }
788 }
789 else if ( valueType == QLatin1String( "bool" ) )
790 {
791 if ( value.toLower() == QLatin1String( "false" ) )
792 return false;
793 if ( value.toLower() == QLatin1String( "true" ) )
794 return true;
795 }
796 else if ( valueType == QLatin1String( "NULL" ) )
797 {
798 // This is the default ("fallback") category
799 return QVariant();
800 }
801 return value;
802 };
803
804 QDomElement catElem = catsElem.firstChildElement();
805 int i = 0;
806 QSet<QString> usedUuids;
807 while ( !catElem.isNull() )
808 {
809 if ( catElem.tagName() == QLatin1String( "category" ) )
810 {
811 QVariant value;
812 if ( catElem.hasAttribute( QStringLiteral( "value" ) ) )
813 {
814 value = valueFromString( catElem.attribute( QStringLiteral( "value" ) ), catElem.attribute( QStringLiteral( "type" ), QString() ) ) ;
815 }
816 else
817 {
818 QVariantList values;
819 QDomElement valElem = catElem.firstChildElement();
820 while ( !valElem.isNull() )
821 {
822 if ( valElem.tagName() == QLatin1String( "val" ) )
823 {
824 values << valueFromString( valElem.attribute( QStringLiteral( "value" ) ), valElem.attribute( QStringLiteral( "type" ), QString() ) );
825 }
826 valElem = valElem.nextSiblingElement();
827 }
828 if ( !values.isEmpty() )
829 value = values;
830 }
831 QString symbolName = catElem.attribute( QStringLiteral( "symbol" ) );
832 QString label = catElem.attribute( QStringLiteral( "label" ) );
833 bool render = catElem.attribute( QStringLiteral( "render" ) ) != QLatin1String( "false" );
834 QString uuid = catElem.attribute( QStringLiteral( "uuid" ), QString::number( i++ ) );
835 while ( usedUuids.contains( uuid ) )
836 {
837 uuid = QUuid::createUuid().toString();
838 }
839 if ( symbolMap.contains( symbolName ) )
840 {
841 QgsSymbol *symbol = symbolMap.take( symbolName );
842 cats.append( QgsRendererCategory( value, symbol, label, render, uuid ) );
843 usedUuids << uuid;
844 }
845 }
846 catElem = catElem.nextSiblingElement();
847 }
848
849 QString attrName = element.attribute( QStringLiteral( "attr" ) );
850
852
853 // delete symbols if there are any more
855
856 // try to load source symbol (optional)
857 QDomElement sourceSymbolElem = element.firstChildElement( QStringLiteral( "source-symbol" ) );
858 if ( !sourceSymbolElem.isNull() )
859 {
860 QgsSymbolMap sourceSymbolMap = QgsSymbolLayerUtils::loadSymbols( sourceSymbolElem, context );
861 if ( sourceSymbolMap.contains( QStringLiteral( "0" ) ) )
862 {
863 r->setSourceSymbol( sourceSymbolMap.take( QStringLiteral( "0" ) ) );
864 }
865 QgsSymbolLayerUtils::clearSymbolMap( sourceSymbolMap );
866 }
867
868 // try to load color ramp (optional)
869 QDomElement sourceColorRampElem = element.firstChildElement( QStringLiteral( "colorramp" ) );
870 if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
871 {
872 r->setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ).release() );
873 }
874
875 QDomElement rotationElem = element.firstChildElement( QStringLiteral( "rotation" ) );
876 if ( !rotationElem.isNull() && !rotationElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
877 {
878 for ( const QgsRendererCategory &cat : r->mCategories )
879 {
880 convertSymbolRotation( cat.symbol(), rotationElem.attribute( QStringLiteral( "field" ) ) );
881 }
882 if ( r->mSourceSymbol )
883 {
884 convertSymbolRotation( r->mSourceSymbol.get(), rotationElem.attribute( QStringLiteral( "field" ) ) );
885 }
886 }
887
888 QDomElement sizeScaleElem = element.firstChildElement( QStringLiteral( "sizescale" ) );
889 if ( !sizeScaleElem.isNull() && !sizeScaleElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
890 {
891 for ( const QgsRendererCategory &cat : r->mCategories )
892 {
893 convertSymbolSizeScale( cat.symbol(),
894 QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
895 sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
896 }
897 if ( r->mSourceSymbol && r->mSourceSymbol->type() == Qgis::SymbolType::Marker )
898 {
900 QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
901 sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
902 }
903 }
904
905 QDomElement ddsLegendSizeElem = element.firstChildElement( QStringLiteral( "data-defined-size-legend" ) );
906 if ( !ddsLegendSizeElem.isNull() )
907 {
908 r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readXml( ddsLegendSizeElem, context ) );
909 }
910
911 // TODO: symbol levels
912 return r;
913}
914
915QDomElement QgsCategorizedSymbolRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
916{
917 // clazy:skip
918 QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
919 rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "categorizedSymbol" ) );
920 rendererElem.setAttribute( QStringLiteral( "attr" ), mAttrName );
921
922 // String for type
923 // We just need string, bool, and three numeric types: double, ulong and long for unsigned, signed and float/double
924 const auto stringForType = []( const QMetaType::Type type ) -> QString
925 {
926 if ( type == QMetaType::Type::QChar || type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
927 {
928 return QStringLiteral( "long" );
929 }
930 else if ( type == QMetaType::Type::UInt || type == QMetaType::Type::ULongLong )
931 {
932 return QStringLiteral( "ulong" );
933 }
934 else if ( type == QMetaType::Type::Double )
935 {
936 return QStringLiteral( "double" ) ;
937 }
938 else if ( type == QMetaType::Type::Bool )
939 {
940 return QStringLiteral( "bool" );
941 }
942 else // Default: string
943 {
944 return QStringLiteral( "string" );
945 }
946 };
947
948 // categories
949 if ( !mCategories.isEmpty() )
950 {
951 int i = 0;
953 QDomElement catsElem = doc.createElement( QStringLiteral( "categories" ) );
954 QgsCategoryList::const_iterator it = mCategories.constBegin();
955 for ( ; it != mCategories.constEnd(); ++it )
956 {
957 const QgsRendererCategory &cat = *it;
958 QString symbolName = QString::number( i );
959 symbols.insert( symbolName, cat.symbol() );
960
961 QDomElement catElem = doc.createElement( QStringLiteral( "category" ) );
962 if ( cat.value().userType() == QMetaType::Type::QVariantList )
963 {
964 const QVariantList list = cat.value().toList();
965 for ( const QVariant &v : list )
966 {
967 QDomElement valueElem = doc.createElement( QStringLiteral( "val" ) );
968 valueElem.setAttribute( QStringLiteral( "value" ), v.toString() );
969 valueElem.setAttribute( QStringLiteral( "type" ), stringForType( static_cast<QMetaType::Type>( v.userType() ) ) );
970 catElem.appendChild( valueElem );
971 }
972 }
973 else
974 {
975 if ( QgsVariantUtils::isNull( cat.value() ) )
976 {
977 // We need to save NULL value as specific kind, it is the default ("fallback") category
978 catElem.setAttribute( QStringLiteral( "value" ), "NULL" );
979 catElem.setAttribute( QStringLiteral( "type" ), "NULL" );
980 }
981 else
982 {
983 catElem.setAttribute( QStringLiteral( "value" ), cat.value().toString() );
984 catElem.setAttribute( QStringLiteral( "type" ), stringForType( static_cast<QMetaType::Type>( cat.value().userType() ) ) );
985 }
986 }
987 catElem.setAttribute( QStringLiteral( "symbol" ), symbolName );
988 catElem.setAttribute( QStringLiteral( "label" ), cat.label() );
989 catElem.setAttribute( QStringLiteral( "render" ), cat.renderState() ? "true" : "false" );
990 catElem.setAttribute( QStringLiteral( "uuid" ), cat.uuid() );
991 catsElem.appendChild( catElem );
992 i++;
993 }
994 rendererElem.appendChild( catsElem );
995
996 // save symbols
997 QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc, context );
998 rendererElem.appendChild( symbolsElem );
999 }
1000
1001 // save source symbol
1002 if ( mSourceSymbol )
1003 {
1004 QgsSymbolMap sourceSymbols;
1005 sourceSymbols.insert( QStringLiteral( "0" ), mSourceSymbol.get() );
1006 QDomElement sourceSymbolElem = QgsSymbolLayerUtils::saveSymbols( sourceSymbols, QStringLiteral( "source-symbol" ), doc, context );
1007 rendererElem.appendChild( sourceSymbolElem );
1008 }
1009
1010 // save source color ramp
1011 if ( mSourceColorRamp )
1012 {
1013 QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
1014 rendererElem.appendChild( colorRampElem );
1015 }
1016
1017 QDomElement rotationElem = doc.createElement( QStringLiteral( "rotation" ) );
1018 rendererElem.appendChild( rotationElem );
1019
1020 QDomElement sizeScaleElem = doc.createElement( QStringLiteral( "sizescale" ) );
1021 rendererElem.appendChild( sizeScaleElem );
1022
1024 {
1025 QDomElement ddsLegendElem = doc.createElement( QStringLiteral( "data-defined-size-legend" ) );
1026 mDataDefinedSizeLegend->writeXml( ddsLegendElem, context );
1027 rendererElem.appendChild( ddsLegendElem );
1028 }
1029
1030 saveRendererData( doc, rendererElem, context );
1031
1032 return rendererElem;
1033}
1034
1035
1036QgsLegendSymbolList QgsCategorizedSymbolRenderer::baseLegendSymbolItems() const
1037{
1039 for ( const QgsRendererCategory &cat : mCategories )
1040 {
1041 lst << QgsLegendSymbolItem( cat.symbol(), cat.label(), cat.uuid(), true );
1042 }
1043 return lst;
1044}
1045
1047{
1048
1049 auto _displayString = [ ]( const QVariant & v, int precision ) -> QString
1050 {
1051
1052 if ( QgsVariantUtils::isNull( v ) )
1053 {
1055 }
1056
1057 const bool isNumeric {v.userType() == QMetaType::Type::Double || v.userType() == QMetaType::Type::Int || v.userType() == QMetaType::Type::UInt || v.userType() == QMetaType::Type::LongLong || v.userType() == QMetaType::Type::ULongLong};
1058
1059 // Special treatment for numeric types if group separator is set or decimalPoint is not a dot
1060 if ( v.userType() == QMetaType::Type::Double )
1061 {
1062 // if value doesn't contain a double (a default value expression for instance),
1063 // apply no transformation
1064 bool ok;
1065 v.toDouble( &ok );
1066 if ( !ok )
1067 return v.toString();
1068
1069 // Locales with decimal point != '.' or that require group separator: use QLocale
1070 if ( QLocale().decimalPoint() != '.' ||
1071 !( QLocale().numberOptions() & QLocale::NumberOption::OmitGroupSeparator ) )
1072 {
1073 if ( precision > 0 )
1074 {
1075 if ( -1 < v.toDouble() && v.toDouble() < 1 )
1076 {
1077 return QLocale().toString( v.toDouble(), 'g', precision );
1078 }
1079 else
1080 {
1081 return QLocale().toString( v.toDouble(), 'f', precision );
1082 }
1083 }
1084 else
1085 {
1086 // Precision is not set, let's guess it from the
1087 // standard conversion to string
1088 const QString s( v.toString() );
1089 const int dotPosition( s.indexOf( '.' ) );
1090 int precision;
1091 if ( dotPosition < 0 && s.indexOf( 'e' ) < 0 )
1092 {
1093 precision = 0;
1094 return QLocale().toString( v.toDouble(), 'f', precision );
1095 }
1096 else
1097 {
1098 if ( dotPosition < 0 ) precision = 0;
1099 else precision = s.length() - dotPosition - 1;
1100
1101 if ( -1 < v.toDouble() && v.toDouble() < 1 )
1102 {
1103 return QLocale().toString( v.toDouble(), 'g', precision );
1104 }
1105 else
1106 {
1107 return QLocale().toString( v.toDouble(), 'f', precision );
1108 }
1109 }
1110 }
1111 }
1112 // Default for doubles with precision
1113 else if ( precision > 0 )
1114 {
1115 if ( -1 < v.toDouble() && v.toDouble() < 1 )
1116 {
1117 return QString::number( v.toDouble(), 'g', precision );
1118 }
1119 else
1120 {
1121 return QString::number( v.toDouble(), 'f', precision );
1122 }
1123 }
1124 }
1125 // Other numeric types than doubles
1126 else if ( isNumeric &&
1127 !( QLocale().numberOptions() & QLocale::NumberOption::OmitGroupSeparator ) )
1128 {
1129 bool ok;
1130 const qlonglong converted( v.toLongLong( &ok ) );
1131 if ( ok )
1132 return QLocale().toString( converted );
1133 }
1134 else if ( v.userType() == QMetaType::Type::QByteArray )
1135 {
1136 return QObject::tr( "BLOB" );
1137 }
1138
1139 // Fallback if special rules do not apply
1140 return v.toString();
1141 };
1142
1143 if ( v.userType() == QMetaType::Type::QStringList || v.userType() == QMetaType::Type::QVariantList )
1144 {
1145 // Note that this code is never hit because the joining of lists (merged categories) happens
1146 // in data(); I'm leaving this here anyway because it is tested and it may be useful for
1147 // other purposes in the future.
1148 QString result;
1149 const QVariantList list = v.toList();
1150 for ( const QVariant &var : list )
1151 {
1152 if ( !result.isEmpty() )
1153 {
1154 result.append( ';' );
1155 }
1156 result.append( _displayString( var, precision ) );
1157 }
1158 return result;
1159 }
1160 else
1161 {
1162 return _displayString( v, precision );
1163 }
1164}
1165
1167{
1169 {
1170 // check that all symbols that have the same size expression
1171 QgsProperty ddSize;
1172 for ( const QgsRendererCategory &category : mCategories )
1173 {
1174 const QgsMarkerSymbol *symbol = static_cast<const QgsMarkerSymbol *>( category.symbol() );
1175 if ( ddSize )
1176 {
1177 QgsProperty sSize( symbol->dataDefinedSize() );
1178 if ( sSize != ddSize )
1179 {
1180 // no common size expression
1181 return baseLegendSymbolItems();
1182 }
1183 }
1184 else
1185 {
1186 ddSize = symbol->dataDefinedSize();
1187 }
1188 }
1189
1190 if ( ddSize && ddSize.isActive() )
1191 {
1193
1195 ddSizeLegend.updateFromSymbolAndProperty( static_cast<const QgsMarkerSymbol *>( mSourceSymbol.get() ), ddSize );
1196 lst += ddSizeLegend.legendSymbolList();
1197
1198 lst += baseLegendSymbolItems();
1199 return lst;
1200 }
1201 }
1202
1203 return baseLegendSymbolItems();
1204}
1205
1207{
1208 const QVariant value = valueForFeature( feature, context );
1209
1210 for ( const QgsRendererCategory &cat : mCategories )
1211 {
1212 bool match = false;
1213 if ( cat.value().userType() == QMetaType::Type::QVariantList )
1214 {
1215 const QVariantList list = cat.value().toList();
1216 for ( const QVariant &v : list )
1217 {
1218 if ( value == v )
1219 {
1220 match = true;
1221 break;
1222 }
1223 }
1224 }
1225 else
1226 {
1227 // Numeric NULL cat value is stored as an empty string
1228 if ( QgsVariantUtils::isNull( value ) && ( value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::Int ||
1229 value.userType() == QMetaType::Type::UInt || value.userType() == QMetaType::Type::LongLong ||
1230 value.userType() == QMetaType::Type::ULongLong || value.userType() == QMetaType::Type::Bool ) )
1231 {
1232 match = cat.value().toString().isEmpty();
1233 }
1234 else
1235 {
1236 match = value == cat.value();
1237 }
1238 }
1239
1240 if ( match )
1241 {
1242 if ( cat.renderState() || mCounting )
1243 return QSet< QString >() << cat.uuid();
1244 else
1245 return QSet< QString >();
1246 }
1247 }
1248
1249 return QSet< QString >();
1250}
1251
1252QString QgsCategorizedSymbolRenderer::legendKeyToExpression( const QString &key, QgsVectorLayer *layer, bool &ok ) const
1253{
1254 ok = false;
1255 int i = 0;
1256 for ( i = 0; i < mCategories.size(); i++ )
1257 {
1258 if ( mCategories[i].uuid() == key )
1259 {
1260 ok = true;
1261 break;
1262 }
1263 }
1264
1265 if ( !ok )
1266 {
1267 ok = false;
1268 return QString();
1269 }
1270
1271 const int fieldIndex = layer ? layer->fields().lookupField( mAttrName ) : -1;
1272 const bool isNumeric = layer && fieldIndex >= 0 ? layer->fields().at( fieldIndex ).isNumeric() : false;
1273 const QMetaType::Type fieldType = layer && fieldIndex >= 0 ? layer->fields().at( fieldIndex ).type() : QMetaType::Type::UnknownType;
1274 const QString attributeComponent = QgsExpression::quoteFieldExpression( mAttrName, layer );
1275
1276 ok = true;
1277 const QgsRendererCategory &cat = mCategories[i];
1278 if ( cat.value().userType() == QMetaType::Type::QVariantList )
1279 {
1280 const QVariantList list = cat.value().toList();
1281 QStringList parts;
1282 parts.reserve( list.size() );
1283 for ( const QVariant &v : list )
1284 {
1285 parts.append( QgsExpression::quotedValue( v ) );
1286 }
1287
1288 return QStringLiteral( "%1 IN (%2)" ).arg( attributeComponent, parts.join( QLatin1String( ", " ) ) );
1289 }
1290 else
1291 {
1292 // Numeric NULL cat value is stored as an empty string
1293 QVariant value = cat.value();
1294 if ( isNumeric && value.toString().isEmpty() )
1295 {
1296 value = QVariant();
1297 }
1298
1299 if ( QgsVariantUtils::isNull( value ) )
1300 return QStringLiteral( "%1 IS NULL" ).arg( attributeComponent );
1301 else if ( fieldType == QMetaType::Type::UnknownType )
1302 return QStringLiteral( "%1 = %2" ).arg( attributeComponent, QgsExpression::quotedValue( value ) );
1303 else
1304 return QStringLiteral( "%1 = %2" ).arg( attributeComponent, QgsExpression::quotedValue( value, fieldType ) );
1305 }
1306}
1307
1312
1314{
1315 return mSourceSymbol.get();
1316}
1317
1322
1327
1332
1337
1339{
1340 setSourceColorRamp( ramp );
1341 double num = mCategories.count() - 1;
1342 double count = 0;
1343
1344 QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp );
1345 if ( randomRamp )
1346 {
1347 //ramp is a random colors ramp, so inform it of the total number of required colors
1348 //this allows the ramp to pregenerate a set of visually distinctive colors
1349 randomRamp->setTotalColorCount( mCategories.count() );
1350 }
1351
1352 for ( const QgsRendererCategory &cat : mCategories )
1353 {
1354 double value = count / num;
1355 cat.symbol()->setColor( mSourceColorRamp->color( value ) );
1356 count += 1;
1357 }
1358}
1359
1361{
1362 int i = 0;
1363 for ( const QgsRendererCategory &cat : mCategories )
1364 {
1365 QgsSymbol *symbol = sym->clone();
1366 symbol->setColor( cat.symbol()->color() );
1367 updateCategorySymbol( i, symbol );
1368 ++i;
1369 }
1370 setSourceSymbol( sym->clone() );
1371}
1372
1374{
1375 return true;
1376}
1377
1379{
1380 for ( const QgsRendererCategory &category : std::as_const( mCategories ) )
1381 {
1382 if ( category.uuid() == key )
1383 {
1384 return category.renderState();
1385 }
1386 }
1387
1388 return true;
1389}
1390
1392{
1393 bool ok = false;
1394 int i = 0;
1395 for ( i = 0; i < mCategories.size(); i++ )
1396 {
1397 if ( mCategories[i].uuid() == key )
1398 {
1399 ok = true;
1400 break;
1401 }
1402 }
1403
1404 if ( ok )
1405 updateCategorySymbol( i, symbol );
1406 else
1407 delete symbol;
1408}
1409
1410void QgsCategorizedSymbolRenderer::checkLegendSymbolItem( const QString &key, bool state )
1411{
1412 for ( int i = 0; i < mCategories.size(); i++ )
1413 {
1414 if ( mCategories[i].uuid() == key )
1415 {
1416 updateCategoryRenderState( i, state );
1417 break;
1418 }
1419 }
1420}
1421
1423{
1424 std::unique_ptr< QgsCategorizedSymbolRenderer > r;
1425 if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
1426 {
1427 r.reset( static_cast<QgsCategorizedSymbolRenderer *>( renderer->clone() ) );
1428 }
1429 else if ( renderer->type() == QLatin1String( "graduatedSymbol" ) )
1430 {
1431 const QgsGraduatedSymbolRenderer *graduatedSymbolRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1432 if ( graduatedSymbolRenderer )
1433 {
1434 r.reset( new QgsCategorizedSymbolRenderer( QString(), QgsCategoryList() ) );
1435 if ( graduatedSymbolRenderer->sourceSymbol() )
1436 r->setSourceSymbol( graduatedSymbolRenderer->sourceSymbol()->clone() );
1437 if ( graduatedSymbolRenderer->sourceColorRamp() )
1438 {
1439 r->setSourceColorRamp( graduatedSymbolRenderer->sourceColorRamp()->clone() );
1440 }
1441 r->setClassAttribute( graduatedSymbolRenderer->classAttribute() );
1442 }
1443 }
1444 else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
1445 {
1446 const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer );
1447 if ( pointDistanceRenderer )
1448 r.reset( convertFromRenderer( pointDistanceRenderer->embeddedRenderer() ) );
1449 }
1450 else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
1451 {
1452 const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer );
1453 if ( invertedPolygonRenderer )
1454 r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1455 }
1456 else if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
1457 {
1458 const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
1462 req.setNoAttributes();
1463 QgsFeatureIterator it = layer->getFeatures( req );
1464 QgsFeature feature;
1465 while ( it.nextFeature( feature ) && categories.size() < 2000 )
1466 {
1467 if ( feature.embeddedSymbol() )
1468 categories.append( QgsRendererCategory( feature.id(), feature.embeddedSymbol()->clone(), QString::number( feature.id() ) ) );
1469 }
1470 categories.append( QgsRendererCategory( QVariant(), embeddedRenderer->defaultSymbol()->clone(), QString() ) );
1471 r.reset( new QgsCategorizedSymbolRenderer( QStringLiteral( "$id" ), categories ) );
1472 }
1473
1474 // If not one of the specifically handled renderers, then just grab the symbol from the renderer
1475 // Could have applied this to specific renderer types (singleSymbol, graduatedSymbol)
1476
1477 if ( !r )
1478 {
1479 r = std::make_unique< QgsCategorizedSymbolRenderer >( QString(), QgsCategoryList() );
1480 QgsRenderContext context;
1481 QgsSymbolList symbols = const_cast<QgsFeatureRenderer *>( renderer )->symbols( context );
1482 if ( !symbols.isEmpty() )
1483 {
1484 QgsSymbol *newSymbol = symbols.at( 0 )->clone();
1487 r->setSourceSymbol( newSymbol );
1488 }
1489 }
1490
1491 renderer->copyRendererData( r.get() );
1492
1493 return r.release();
1494}
1495
1500
1505
1506int QgsCategorizedSymbolRenderer::matchToSymbols( QgsStyle *style, Qgis::SymbolType type, QVariantList &unmatchedCategories, QStringList &unmatchedSymbols, const bool caseSensitive, const bool useTolerantMatch )
1507{
1508 if ( !style )
1509 return 0;
1510
1511 int matched = 0;
1512 unmatchedSymbols = style->symbolNames();
1513 const QSet< QString > allSymbolNames( unmatchedSymbols.begin(), unmatchedSymbols.end() );
1514
1515 const thread_local QRegularExpression tolerantMatchRe( QStringLiteral( "[^\\w\\d ]" ), QRegularExpression::UseUnicodePropertiesOption );
1516
1517 for ( int catIdx = 0; catIdx < mCategories.count(); ++catIdx )
1518 {
1519 const QVariant value = mCategories.at( catIdx ).value();
1520 const QString val = value.toString().trimmed();
1521 std::unique_ptr< QgsSymbol > symbol( style->symbol( val ) );
1522 // case-sensitive match
1523 if ( symbol && symbol->type() == type )
1524 {
1525 matched++;
1526 unmatchedSymbols.removeAll( val );
1527 updateCategorySymbol( catIdx, symbol.release() );
1528 continue;
1529 }
1530
1531 if ( !caseSensitive || useTolerantMatch )
1532 {
1533 QString testVal = val;
1534 if ( useTolerantMatch )
1535 testVal.replace( tolerantMatchRe, QString() );
1536
1537 bool foundMatch = false;
1538 for ( const QString &name : allSymbolNames )
1539 {
1540 QString testName = name.trimmed();
1541 if ( useTolerantMatch )
1542 testName.replace( tolerantMatchRe, QString() );
1543
1544 if ( testName == testVal || ( !caseSensitive && testName.trimmed().compare( testVal, Qt::CaseInsensitive ) == 0 ) )
1545 {
1546 // found a case-insensitive match
1547 std::unique_ptr< QgsSymbol > symbol( style->symbol( name ) );
1548 if ( symbol && symbol->type() == type )
1549 {
1550 matched++;
1551 unmatchedSymbols.removeAll( name );
1552 updateCategorySymbol( catIdx, symbol.release() );
1553 foundMatch = true;
1554 break;
1555 }
1556 }
1557 }
1558 if ( foundMatch )
1559 continue;
1560 }
1561
1562 unmatchedCategories << value;
1563 }
1564
1565 return matched;
1566}
1567
1568QgsCategoryList QgsCategorizedSymbolRenderer::createCategories( const QList<QVariant> &values, const QgsSymbol *symbol, QgsVectorLayer *layer, const QString &attributeName )
1569{
1570 QgsCategoryList cats;
1571 QVariantList vals = values;
1572 // sort the categories first
1573 QgsSymbolLayerUtils::sortVariantList( vals, Qt::AscendingOrder );
1574
1575 if ( layer && !attributeName.isNull() )
1576 {
1577 const QgsFields fields = layer->fields();
1578 for ( const QVariant &value : vals )
1579 {
1580 QgsSymbol *newSymbol = symbol->clone();
1582 if ( !QgsVariantUtils::isNull( value ) )
1583 {
1584 const int fieldIdx = fields.lookupField( attributeName );
1585 QString categoryName = displayString( value );
1586 if ( fieldIdx != -1 )
1587 {
1588 const QgsField field = fields.at( fieldIdx );
1589 const QgsEditorWidgetSetup setup = field.editorWidgetSetup();
1591 categoryName = formatter->representValue( layer, fieldIdx, setup.config(), QVariant(), value );
1592 }
1593 cats.append( QgsRendererCategory( value, newSymbol, categoryName, true ) );
1594 }
1595 }
1596 }
1597
1598 // add null (default) value
1599 QgsSymbol *newSymbol = symbol->clone();
1601 cats.append( QgsRendererCategory( QVariant(), newSymbol, QString(), true ) );
1602
1603 return cats;
1604}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ EmbeddedSymbols
Retrieve any embedded feature symbology.
QFlags< FeatureRendererFlag > FeatureRendererFlags
Flags controlling behavior of vector feature renderers.
Definition qgis.h:802
@ AffectsLabeling
If present, indicates that the renderer will participate in the map labeling problem.
SymbolType
Symbol types.
Definition qgis.h:574
@ Marker
Marker symbol.
@ AffectsLabeling
If present, indicates that the symbol will participate in the map labeling problem.
static QString nullRepresentation()
Returns the string used to represent the value NULL throughout QGIS.
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
A vector of attributes.
A feature renderer which represents features using a list of renderer categories.
void sortByValue(Qt::SortOrder order=Qt::AscendingOrder)
Sorts the existing categories by their value.
QString filter(const QgsFields &fields=QgsFields()) override
If a renderer does not require all the features this method may be overridden and return an expressio...
QgsSymbol * symbolForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
To be overridden.
void updateSymbols(QgsSymbol *sym)
Update all the symbols but leave categories and colors.
bool updateCategoryRenderState(int catIndex, bool render)
Changes the render state for the category with the specified index.
void setSourceColorRamp(QgsColorRamp *ramp)
Sets the source color ramp.
void stopRender(QgsRenderContext &context) override
Must be called when a render cycle has finished, to allow the renderer to clean up.
QgsSymbol * sourceSymbol()
Returns the renderer's source symbol, which is the base symbol used for the each categories' symbol b...
const QgsCategoryList & categories() const
Returns a list of all categories recognized by the renderer.
QString legendKeyToExpression(const QString &key, QgsVectorLayer *layer, bool &ok) const override
Attempts to convert the specified legend rule key to a QGIS expression matching the features displaye...
int matchToSymbols(QgsStyle *style, Qgis::SymbolType type, QVariantList &unmatchedCategories, QStringList &unmatchedSymbols, bool caseSensitive=true, bool useTolerantMatch=false)
Replaces category symbols with the symbols from a style that have a matching name and symbol type.
std::unique_ptr< QgsColorRamp > mSourceColorRamp
Q_DECL_DEPRECATED QgsSymbol * symbolForValue(const QVariant &value) const
Returns the matching symbol corresponding to an attribute value.
std::unique_ptr< QgsSymbol > mSourceSymbol
static QgsCategorizedSymbolRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer, QgsVectorLayer *layer=nullptr)
Creates a new QgsCategorizedSymbolRenderer from an existing renderer.
void updateColorRamp(QgsColorRamp *ramp)
Update the color ramp used and all symbols colors.
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration of appearance of legend when using data-defined size for marker symbols.
static QgsCategoryList createCategories(const QVariantList &values, const QgsSymbol *symbol, QgsVectorLayer *layer=nullptr, const QString &fieldName=QString())
Create categories for a list of values.
QHash< QString, QgsSymbol * > mSymbolHash
hashtable for faster access to symbols
bool filterNeedsGeometry() const override
Returns true if this renderer requires the geometry to apply the filter.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns a list of attributes required by this renderer.
void setSourceSymbol(QgsSymbol *sym)
Sets the source symbol for the renderer, which is the base symbol used for the each categories' symbo...
void startRender(QgsRenderContext &context, const QgsFields &fields) override
Must be called when a new render cycle is started.
QgsSymbolList symbols(QgsRenderContext &context) const override
Returns list of symbols used by the renderer.
int categoryIndexForValue(const QVariant &val)
Returns the index for the category with the specified value (or -1 if not found).
static QgsFeatureRenderer * create(QDomElement &element, const QgsReadWriteContext &context)
Creates a categorized renderer from an XML element.
bool updateCategorySymbol(int catIndex, QgsSymbol *symbol)
Changes the symbol for the category with the specified index.
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified symbology visitor, causing it to visit all symbols associated with the renderer...
void setLegendSymbolItem(const QString &key, QgsSymbol *symbol) override
Sets the symbol to be used for a legend symbol item.
std::unique_ptr< QgsExpression > mExpression
std::unique_ptr< QgsDataDefinedSizeLegend > mDataDefinedSizeLegend
bool legendSymbolItemChecked(const QString &key) override
Returns true if the legend symbology item with the specified key is checked.
bool legendSymbolItemsCheckable() const override
Returns true if symbology items in legend are checkable.
void addCategory(const QgsRendererCategory &category)
Adds a new category to the renderer.
QgsCategorizedSymbolRenderer(const QString &attrName=QString(), const QgsCategoryList &categories=QgsCategoryList())
Constructor for QgsCategorizedSymbolRenderer.
void sortByLabel(Qt::SortOrder order=Qt::AscendingOrder)
Sorts the existing categories by their label.
QgsSymbol * originalSymbolForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns symbol for feature.
int mAttrNum
attribute index (derived from attribute name in startRender)
QgsLegendSymbolList legendSymbolItems() const override
Returns a list of symbology items for the legend.
void moveCategory(int from, int to)
Moves an existing category at index position from to index position to.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) override
Stores renderer properties to an XML element.
bool deleteCategory(int catIndex)
Deletes the category with the specified index from the renderer.
Qgis::FeatureRendererFlags flags() const override
Returns flags associated with the renderer.
Q_DECL_DEPRECATED QgsSymbol * skipRender()
void checkLegendSymbolItem(const QString &key, bool state=true) override
Sets whether the legend symbology item with the specified ley should be checked.
QString dump() const override
Returns debug information about this renderer.
QSet< QString > legendKeysForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns legend keys matching a specified feature.
QgsColorRamp * sourceColorRamp()
Returns the source color ramp, from which each categories' color is derived.
bool updateCategoryValue(int catIndex, const QVariant &value)
Changes the value for the category with the specified index.
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props=QVariantMap()) const override
Used from subclasses to create SLD Rule elements following SLD v1.1 specs.
QgsCategorizedSymbolRenderer * clone() const override
Create a deep copy of this renderer.
void deleteAllCategories()
Deletes all existing categories from the renderer.
void setDataDefinedSizeLegend(QgsDataDefinedSizeLegend *settings)
Configures appearance of legend when renderer is configured to use data-defined size for marker symbo...
bool updateCategoryLabel(int catIndex, const QString &label)
Changes the label for the category with the specified index.
static QString displayString(const QVariant &value, int precision=-1)
Returns a localized representation of value with the given precision, if precision is -1 then precisi...
~QgsCategorizedSymbolRenderer() override
int categoryIndexForLabel(const QString &val)
Returns the index of the category with the specified label (or -1 if the label was not found,...
Abstract base class for color ramps.
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
Object that keeps configuration of appearance of marker symbol's data-defined size in legend.
static QgsDataDefinedSizeLegend * readXml(const QDomElement &elem, const QgsReadWriteContext &context) SIP_FACTORY
Creates instance from given element and returns it (caller takes ownership). Returns nullptr on error...
void updateFromSymbolAndProperty(const QgsMarkerSymbol *symbol, const QgsProperty &ddSize)
Updates the list of classes, source symbol and title label from given symbol and property.
QgsLegendSymbolList legendSymbolList() const
Generates legend symbol items according to the configuration.
Holder for the widget type and its configuration for a field.
QString type() const
Returns the widget type to use.
QVariantMap config() const
Returns the widget configuration.
A vector feature renderer which uses embedded feature symbology to render per-feature symbols.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required.
static QString quoteFieldExpression(const QString &expression, const QgsVectorLayer *layer)
Validate if the expression is a field in the layer and ensure it is quoted.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
bool isField() const
Checks whether an expression consists only of a single field reference.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
bool needsGeometry() const
Returns true if the expression uses feature geometry for some computation.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Abstract base class for all 2D vector feature renderers.
virtual void stopRender(QgsRenderContext &context)
Must be called when a render cycle has finished, to allow the renderer to clean up.
QString type() const
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
static void convertSymbolRotation(QgsSymbol *symbol, const QString &field)
Converts old rotation expressions to symbol level data defined angles.
void saveRendererData(QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context)
Saves generic renderer data into the specified element.
virtual const QgsFeatureRenderer * embeddedRenderer() const
Returns the current embedded renderer (subrenderer) for this feature renderer.
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)
Must be called when a new render cycle is started.
static void convertSymbolSizeScale(QgsSymbol *symbol, Qgis::ScaleMethod method, const QString &field)
Converts old sizeScale expressions to symbol level data defined sizes.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsAttributes attributes
Definition qgsfeature.h:67
QgsFeatureId id
Definition qgsfeature.h:66
const QgsSymbol * embeddedSymbol() const
Returns the feature's embedded symbology, or nullptr if the feature has no embedded symbol.
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
A field formatter helps to handle and display values for a field.
virtual QString representValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const
Create a pretty String representation of the value.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
QMetaType::Type type
Definition qgsfield.h:60
bool isNumeric
Definition qgsfield.h:56
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition qgsfield.cpp:746
Container of fields for a vector layer.
Definition qgsfields.h:46
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
A vector feature renderer which uses numeric attributes to classify features into different ranges.
QgsSymbol * sourceSymbol()
Returns the renderer's source symbol, which is the base symbol used for the each classes' symbol befo...
QgsColorRamp * sourceColorRamp()
Returns the source color ramp, from which each classes' color is derived.
QString classAttribute() const
Returns the attribute name (or expression) used for the classification.
void setSourceSymbol(QgsSymbol *sym)
Sets the source symbol for the renderer, which is the base symbol used for the each classes' symbol b...
A polygon-only feature renderer used to display features inverted.
Stores information about one class/rule of a vector layer renderer in a unified way that can be used ...
A marker symbol type, for rendering Point and MultiPoint geometries.
QgsProperty dataDefinedSize() const
Returns data defined size for whole symbol (including all symbol layers).
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
An abstract base class for distance based point renderers (e.g., clusterer and displacement renderers...
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
A store for object properties.
bool isActive() const
Returns whether the property is currently active.
A color ramp consisting of random colors, constrained within component ranges.
virtual void setTotalColorCount(int colorCount)
Sets the desired total number of unique colors for the resultant ramp.
A container for the context for various read/write operations on objects.
Contains information about the context of a rendering operation.
double rendererScale() const
Returns the renderer map scale.
QgsExpressionContext & expressionContext()
Gets the expression context.
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
void setRenderState(bool render)
Sets whether the category is currently enabled and should be rendered.
std::unique_ptr< QgsSymbol > mSymbol
QgsSymbol * symbol() const
Returns the symbol which will be used to render this category.
void setSymbol(QgsSymbol *s)
Sets the symbol which will be used to render this category.
QString uuid() const
Returns the unique identifier for this category.
QgsRendererCategory()=default
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QString dump() const
Returns a string representing the categories settings, used for debugging purposes only.
void setLabel(const QString &label)
Sets the label for this category, which is used to represent the category within legends and the laye...
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, QVariantMap props) const
Converts the category to a matching SLD rule, within the specified DOM document and element.
void setValue(const QVariant &value)
Sets the value corresponding to this category.
QVariant value() const
Returns the value corresponding to this category.
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
QgsRendererCategory & operator=(QgsRendererCategory cat)
Holds SLD export options and other information related to SLD export of a QGIS layer style.
void setExtraProperties(const QVariantMap &properties)
Sets the open ended set of properties that can drive/inform the SLD encoding.
QVariantMap extraProperties() const
Returns the open ended set of properties that can drive/inform the SLD encoding.
A color ramp entity for QgsStyle databases.
Definition qgsstyle.h:1429
An interface for classes which can visit style entity (e.g.
virtual bool visit(const QgsStyleEntityVisitorInterface::StyleLeaf &entity)
Called when the visitor will visit a style entity.
A symbol entity for QgsStyle databases.
Definition qgsstyle.h:1397
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:88
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition qgsstyle.cpp:318
QStringList symbolNames() const
Returns a list of names of symbols.
Definition qgsstyle.cpp:340
static void sortVariantList(QList< QVariant > &list, Qt::SortOrder order)
Sorts the passed list in requested order.
static void applyScaleDependency(QDomDocument &doc, QDomElement &ruleElem, QVariantMap &props)
Checks if the properties contain scaleMinDenom and scaleMaxDenom, if available, they are added into t...
static std::unique_ptr< QgsColorRamp > loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
static Q_DECL_DEPRECATED bool createFunctionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates an OGC function element.
static bool hasSldSymbolizer(const QDomElement &element)
Returns true if a DOM element contains an SLD Symbolizer element.
static void clearSymbolLayerMasks(QgsSymbol *symbol)
Remove recursively masks from all symbol symbol layers.
static Qgis::ScaleMethod decodeScaleMethod(const QString &str)
Decodes a symbol scale method from a string.
static QDomElement saveColorRamp(const QString &name, const QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp's settings to an XML element.
static void clearSymbolMap(QgsSymbolMap &symbols)
static void resetSymbolLayerIds(QgsSymbol *symbol)
Regenerate recursively unique id from all symbol symbol layers.
static QgsSymbolMap loadSymbols(QDomElement &element, const QgsReadWriteContext &context)
Reads a collection of symbols from XML and returns them in a map. Caller is responsible for deleting ...
static QDomElement saveSymbols(QgsSymbolMap &symbols, const QString &tagName, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a collection of symbols to XML with specified tagName for the top-level element.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
void setColor(const QColor &color) const
Sets the color for the symbol.
QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns a list of attributes required to render this feature.
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
Definition qgis.cpp:286
bool qgsVariantGreaterThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is greater than the second.
Definition qgis.cpp:291
bool labelGreaterThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
bool valueLessThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
bool valueGreaterThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
bool labelLessThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
QList< QgsRendererCategory > QgsCategoryList
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40
#define RENDERER_TAG_NAME
Definition qgsrenderer.h:54
QMap< QString, QgsSymbol * > QgsSymbolMap
Definition qgsrenderer.h:49
QList< QgsSymbol * > QgsSymbolList
Definition qgsrenderer.h:48
int precision
Contains information relating to the style entity currently being visited.