QGIS API Documentation 3.43.0-Master (c67cf405802)
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
43#include <QDomDocument>
44#include <QDomElement>
45#include <QSettings> // for legend
46#include <QRegularExpression>
47#include <QUuid>
48
49QgsRendererCategory::QgsRendererCategory( const QVariant &value, QgsSymbol *symbol, const QString &label, bool render, const QString &uuid )
50 : mValue( value )
51 , mSymbol( symbol )
52 , mLabel( label )
53 , mRender( render )
54{
55 mUuid = !uuid.isEmpty() ? uuid : QUuid::createUuid().toString();
56}
57
59 : mValue( cat.mValue )
60 , mSymbol( cat.mSymbol ? cat.mSymbol->clone() : nullptr )
61 , mLabel( cat.mLabel )
62 , mRender( cat.mRender )
63 , mUuid( cat.mUuid )
64{
65}
66
68{
69 mValue = cat.mValue;
70 mSymbol.reset( cat.mSymbol ? cat.mSymbol->clone() : nullptr );
71 mLabel = cat.mLabel;
72 mRender = cat.mRender;
73 mUuid = cat.mUuid;
74 return *this;
75}
76
78
80{
81 return mUuid;
82}
83
85{
86 return mValue;
87}
88
90{
91 return mSymbol.get();
92}
93
95{
96 return mLabel;
97}
98
100{
101 return mRender;
102}
103
104void QgsRendererCategory::setValue( const QVariant &value )
105{
106 mValue = value;
107}
108
110{
111 if ( mSymbol.get() != s ) mSymbol.reset( s );
112}
113
114void QgsRendererCategory::setLabel( const QString &label )
115{
116 mLabel = label;
117}
118
120{
121 mRender = render;
122}
123
125{
126 return QStringLiteral( "%1::%2::%3:%4\n" ).arg( mValue.toString(), mLabel, mSymbol->dump() ).arg( mRender );
127}
128
129void QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
130{
131 if ( !mSymbol.get() || props.value( QStringLiteral( "attribute" ), QString() ).toString().isEmpty() )
132 return;
133
134 QString attrName = props[ QStringLiteral( "attribute" )].toString();
135
136 // try to determine if attribute name is actually a field reference or expression.
137 // If it's a field reference, we need to quote it.
138 // Because we don't have access to the layer or fields here, we treat a parser error
139 // as just an unquoted field name (eg a field name with spaces)
140 const QgsExpression attrExpression = QgsExpression( attrName );
141 if ( attrExpression.hasParserError() )
142 {
143 attrName = QgsExpression::quotedColumnRef( attrName );
144 }
145 else if ( attrExpression.isField() )
146 {
148 qgis::down_cast<const QgsExpressionNodeColumnRef *>( attrExpression.rootNode() )->name()
149 );
150 }
151
152 QDomElement ruleElem = doc.createElement( QStringLiteral( "se:Rule" ) );
153
154 QDomElement nameElem = doc.createElement( QStringLiteral( "se:Name" ) );
155 nameElem.appendChild( doc.createTextNode( mLabel ) );
156 ruleElem.appendChild( nameElem );
157
158 QDomElement descrElem = doc.createElement( QStringLiteral( "se:Description" ) );
159 QDomElement titleElem = doc.createElement( QStringLiteral( "se:Title" ) );
160 QString descrStr = QStringLiteral( "%1 is '%2'" ).arg( attrName, mValue.toString() );
161 titleElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : descrStr ) );
162 descrElem.appendChild( titleElem );
163 ruleElem.appendChild( descrElem );
164
165 // create the ogc:Filter for the range
166 QString filterFunc;
167 if ( mValue.userType() == QMetaType::Type::QVariantList )
168 {
169 const QVariantList list = mValue.toList();
170 if ( list.size() == 1 )
171 {
172 filterFunc = QStringLiteral( "%1 = %2" ).arg( attrName, QgsExpression::quotedValue( list.at( 0 ) ) );
173 }
174 else
175 {
176 QStringList valuesList;
177 valuesList.reserve( list.size() );
178 for ( const QVariant &v : list )
179 {
180 valuesList << QgsExpression::quotedValue( v );
181 }
182 filterFunc = QStringLiteral( "%1 IN (%2)" ).arg( attrName,
183 valuesList.join( ',' ) );
184 }
185 }
186 else if ( QgsVariantUtils::isNull( mValue ) || mValue.toString().isEmpty() )
187 {
188 filterFunc = QStringLiteral( "ELSE" );
189 }
190 else
191 {
192 filterFunc = QStringLiteral( "%1 = %2" ).arg( attrName, QgsExpression::quotedValue( mValue ) );
193 }
194
195 QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, filterFunc );
196
197 // add the mix/max scale denoms if we got any from the callers
198 QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props );
199
200 mSymbol->toSld( doc, ruleElem, props );
201 if ( !QgsSymbolLayerUtils::hasSldSymbolizer( ruleElem ) )
202 {
203 // symbol could not be converted to SLD, or is an "empty" symbol. In this case we do not generate a rule, as
204 // SLD spec requires a Symbolizer element to be present
205 return;
206 }
207
208 element.appendChild( ruleElem );
209}
210
212
214 : QgsFeatureRenderer( QStringLiteral( "categorizedSymbol" ) )
215 , mAttrName( attrName )
216{
217 //important - we need a deep copy of the categories list, not a shared copy. This is required because
218 //QgsRendererCategory::symbol() is marked const, and so retrieving the symbol via this method does not
219 //trigger a detachment and copy of mCategories BUT that same method CAN be used to modify a symbol in place
220 for ( const QgsRendererCategory &cat : categories )
221 {
222 if ( !cat.symbol() )
223 {
224 QgsDebugError( QStringLiteral( "invalid symbol in a category! ignoring..." ) );
225 }
226 mCategories << cat;
227 }
228}
229
231{
233 QgsCategoryList::const_iterator catIt = mCategories.constBegin();
234 for ( ; catIt != mCategories.constEnd(); ++catIt )
235 {
236 if ( QgsSymbol *catSymbol = catIt->symbol() )
237 {
238 if ( catSymbol->flags().testFlag( Qgis::SymbolFlag::AffectsLabeling ) )
240 }
241 }
242
243 return res;
244}
245
247
249{
250 mSymbolHash.clear();
251
252 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
253 {
254 const QVariant val = cat.value();
255 if ( val.userType() == QMetaType::Type::QVariantList )
256 {
257 const QVariantList list = val.toList();
258 for ( const QVariant &v : list )
259 {
260 mSymbolHash.insert( v.toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : nullptr );
261 }
262 }
263 else
264 {
265 mSymbolHash.insert( val.toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : nullptr );
266 }
267 }
268}
269
274
276{
277 bool found = false;
278 return symbolForValue( value, found );
279}
280
281QgsSymbol *QgsCategorizedSymbolRenderer::symbolForValue( const QVariant &value, bool &foundMatchingSymbol ) const
282{
283 foundMatchingSymbol = false;
284
285 // TODO: special case for int, double
286 QHash<QString, QgsSymbol *>::const_iterator it = mSymbolHash.constFind( QgsVariantUtils::isNull( value ) ? QString() : value.toString() );
287 if ( it == mSymbolHash.constEnd() )
288 {
289 if ( mSymbolHash.isEmpty() )
290 {
291 QgsDebugError( QStringLiteral( "there are no hashed symbols!!!" ) );
292 }
293 else
294 {
295 QgsDebugMsgLevel( "attribute value not found: " + value.toString(), 3 );
296 }
297 return nullptr;
298 }
299
300 foundMatchingSymbol = true;
301
302 return *it;
303}
304
306{
307 return originalSymbolForFeature( feature, context );
308}
309
310QVariant QgsCategorizedSymbolRenderer::valueForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
311{
312 QgsAttributes attrs = feature.attributes();
313 QVariant value;
314 if ( mAttrNum == -1 )
315 {
316 Q_ASSERT( mExpression );
317
318 value = mExpression->evaluate( &context.expressionContext() );
319 }
320 else
321 {
322 value = attrs.value( mAttrNum );
323 }
324
325 return value;
326}
327
329{
330 QVariant value = valueForFeature( feature, context );
331
332 bool foundCategory = false;
333 // find the right symbol for the category
334 QgsSymbol *symbol = symbolForValue( value, foundCategory );
335
336 if ( !foundCategory )
337 {
338 // if no symbol found, use default symbol
339 return symbolForValue( QVariant( "" ), foundCategory );
340 }
341
342 return symbol;
343}
344
345
347{
348 for ( int i = 0; i < mCategories.count(); i++ )
349 {
350 if ( mCategories[i].value() == val )
351 return i;
352 }
353 return -1;
354}
355
357{
358 int idx = -1;
359 for ( int i = 0; i < mCategories.count(); i++ )
360 {
361 if ( mCategories[i].label() == val )
362 {
363 if ( idx != -1 )
364 return -1;
365 else
366 idx = i;
367 }
368 }
369 return idx;
370}
371
372bool QgsCategorizedSymbolRenderer::updateCategoryValue( int catIndex, const QVariant &value )
373{
374 if ( catIndex < 0 || catIndex >= mCategories.size() )
375 return false;
376 mCategories[catIndex].setValue( value );
377 return true;
378}
379
381{
382 if ( catIndex < 0 || catIndex >= mCategories.size() )
383 return false;
384 mCategories[catIndex].setSymbol( symbol );
385 return true;
386}
387
388bool QgsCategorizedSymbolRenderer::updateCategoryLabel( int catIndex, const QString &label )
389{
390 if ( catIndex < 0 || catIndex >= mCategories.size() )
391 return false;
392 mCategories[catIndex].setLabel( label );
393 return true;
394}
395
397{
398 if ( catIndex < 0 || catIndex >= mCategories.size() )
399 return false;
400 mCategories[catIndex].setRenderState( render );
401 return true;
402}
403
405{
406 if ( !cat.symbol() )
407 {
408 QgsDebugError( QStringLiteral( "invalid symbol in a category! ignoring..." ) );
409 return;
410 }
411
412 mCategories.append( cat );
413}
414
416{
417 if ( catIndex < 0 || catIndex >= mCategories.size() )
418 return false;
419
420 mCategories.removeAt( catIndex );
421 return true;
422}
423
428
430{
431 if ( from < 0 || from >= mCategories.size() || to < 0 || to >= mCategories.size() ) return;
432 mCategories.move( from, to );
433}
434
436{
437 return qgsVariantLessThan( c1.value(), c2.value() );
438}
440{
441 return qgsVariantGreaterThan( c1.value(), c2.value() );
442}
443
445{
446 if ( order == Qt::AscendingOrder )
447 {
448 std::sort( mCategories.begin(), mCategories.end(), valueLessThan );
449 }
450 else
451 {
452 std::sort( mCategories.begin(), mCategories.end(), valueGreaterThan );
453 }
454}
455
457{
458 return QString::localeAwareCompare( c1.label(), c2.label() ) < 0;
459}
460
462{
463 return !labelLessThan( c1, c2 );
464}
465
467{
468 if ( order == Qt::AscendingOrder )
469 {
470 std::sort( mCategories.begin(), mCategories.end(), labelLessThan );
471 }
472 else
473 {
474 std::sort( mCategories.begin(), mCategories.end(), labelGreaterThan );
475 }
476}
477
479{
480 QgsFeatureRenderer::startRender( context, fields );
481
482 mCounting = context.rendererScale() == 0.0;
483
484 // make sure that the hash table is up to date
485 rebuildHash();
486
487 // find out classification attribute index from name
488 mAttrNum = fields.lookupField( mAttrName );
489 if ( mAttrNum == -1 )
490 {
491 mExpression.reset( new QgsExpression( mAttrName ) );
492 mExpression->prepare( &context.expressionContext() );
493 }
494
495 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
496 {
497 cat.symbol()->startRender( context, fields );
498 }
499}
500
502{
504
505 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
506 {
507 cat.symbol()->stopRender( context );
508 }
509 mExpression.reset();
510}
511
513{
514 QSet<QString> attributes;
515
516 // mAttrName can contain either attribute name or an expression.
517 // Sometimes it is not possible to distinguish between those two,
518 // e.g. "a - b" can be both a valid attribute name or expression.
519 // Since we do not have access to fields here, try both options.
520 attributes << mAttrName;
521
522 QgsExpression testExpr( mAttrName );
523 if ( !testExpr.hasParserError() )
524 attributes.unite( testExpr.referencedColumns() );
525
526 QgsCategoryList::const_iterator catIt = mCategories.constBegin();
527 for ( ; catIt != mCategories.constEnd(); ++catIt )
528 {
529 QgsSymbol *catSymbol = catIt->symbol();
530 if ( catSymbol )
531 {
532 attributes.unite( catSymbol->usedAttributes( context ) );
533 }
534 }
535 return attributes;
536}
537
539{
540 QgsExpression testExpr( mAttrName );
541 if ( !testExpr.hasParserError() )
542 {
543 QgsExpressionContext context;
544 context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
545 testExpr.prepare( &context );
546 return testExpr.needsGeometry();
547 }
548 return false;
549}
550
552{
553 QString s = QStringLiteral( "CATEGORIZED: idx %1\n" ).arg( mAttrName );
554 for ( int i = 0; i < mCategories.count(); i++ )
555 s += mCategories[i].dump();
556 return s;
557}
558
573
574void QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
575{
576 QVariantMap newProps = props;
577 newProps[ QStringLiteral( "attribute" )] = mAttrName;
578
579 // create a Rule for each range
580 for ( QgsCategoryList::const_iterator it = mCategories.constBegin(); it != mCategories.constEnd(); ++it )
581 {
582 it->toSld( doc, element, newProps );
583 }
584}
585
587{
588 int attrNum = fields.lookupField( mAttrName );
589 bool isExpression = ( attrNum == -1 );
590
591 bool hasDefault = false;
592 bool defaultActive = false;
593 bool allActive = true;
594 bool noneActive = true;
595
596 //we need to build lists of both inactive and active values, as either list may be required
597 //depending on whether the default category is active or not
598 QString activeValues;
599 QString inactiveValues;
600
601 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
602 {
603 if ( cat.value() == "" || QgsVariantUtils::isNull( cat.value() ) )
604 {
605 hasDefault = true;
606 defaultActive = cat.renderState();
607 }
608
609 noneActive = noneActive && !cat.renderState();
610 allActive = allActive && cat.renderState();
611
612 const bool isList = cat.value().userType() == QMetaType::Type::QVariantList;
613 QString value = QgsExpression::quotedValue( cat.value(), static_cast<QMetaType::Type>( cat.value().userType() ) );
614
615 if ( !cat.renderState() )
616 {
617 if ( value != "" )
618 {
619 if ( isList )
620 {
621 const QVariantList list = cat.value().toList();
622 for ( const QVariant &v : list )
623 {
624 if ( !inactiveValues.isEmpty() )
625 inactiveValues.append( ',' );
626
627 inactiveValues.append( QgsExpression::quotedValue( v, isExpression ? static_cast<QMetaType::Type>( v.userType() ) : fields.at( attrNum ).type() ) );
628 }
629 }
630 else
631 {
632 if ( !inactiveValues.isEmpty() )
633 inactiveValues.append( ',' );
634
635 inactiveValues.append( value );
636 }
637 }
638 }
639 else
640 {
641 if ( value != "" )
642 {
643 if ( isList )
644 {
645 const QVariantList list = cat.value().toList();
646 for ( const QVariant &v : list )
647 {
648 if ( !activeValues.isEmpty() )
649 activeValues.append( ',' );
650
651 activeValues.append( QgsExpression::quotedValue( v, isExpression ? static_cast<QMetaType::Type>( v.userType() ) : fields.at( attrNum ).type() ) );
652 }
653 }
654 else
655 {
656 if ( !activeValues.isEmpty() )
657 activeValues.append( ',' );
658
659 activeValues.append( value );
660 }
661 }
662 }
663 }
664
665 QString attr = isExpression ? mAttrName : QStringLiteral( "\"%1\"" ).arg( mAttrName );
666
667 if ( allActive && hasDefault )
668 {
669 return QString();
670 }
671 else if ( noneActive )
672 {
673 return QStringLiteral( "FALSE" );
674 }
675 else if ( defaultActive )
676 {
677 return QStringLiteral( "(%1) NOT IN (%2) OR (%1) IS NULL" ).arg( attr, inactiveValues );
678 }
679 else
680 {
681 return QStringLiteral( "(%1) IN (%2)" ).arg( attr, activeValues );
682 }
683}
684
686{
687 Q_UNUSED( context )
688 QgsSymbolList lst;
689 lst.reserve( mCategories.count() );
690 for ( const QgsRendererCategory &cat : mCategories )
691 {
692 lst.append( cat.symbol() );
693 }
694 return lst;
695}
696
698{
699 for ( const QgsRendererCategory &cat : mCategories )
700 {
701 QgsStyleSymbolEntity entity( cat.symbol() );
702 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, cat.value().toString(), cat.label() ) ) )
703 return false;
704 }
705
706 if ( mSourceColorRamp )
707 {
709 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity ) ) )
710 return false;
711 }
712
713 return true;
714}
715
717{
718 QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
719 if ( symbolsElem.isNull() )
720 return nullptr;
721
722 QDomElement catsElem = element.firstChildElement( QStringLiteral( "categories" ) );
723 if ( catsElem.isNull() )
724 return nullptr;
725
726 QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
727 QgsCategoryList cats;
728
729 // Value from string (long, ulong, double and string)
730 const auto valueFromString = []( const QString & value, const QString & valueType ) -> QVariant
731 {
732 if ( valueType == QLatin1String( "double" ) )
733 {
734 bool ok;
735 const auto val { value.toDouble( &ok ) };
736 if ( ok )
737 {
738 return val;
739 }
740 }
741 else if ( valueType == QLatin1String( "ulong" ) )
742 {
743 bool ok;
744 const auto val { value.toULongLong( &ok ) };
745 if ( ok )
746 {
747 return val;
748 }
749 }
750 else if ( valueType == QLatin1String( "long" ) )
751 {
752 bool ok;
753 const auto val { value.toLongLong( &ok ) };
754 if ( ok )
755 {
756 return val;
757 }
758 }
759 else if ( valueType == QLatin1String( "bool" ) )
760 {
761 if ( value.toLower() == QLatin1String( "false" ) )
762 return false;
763 if ( value.toLower() == QLatin1String( "true" ) )
764 return true;
765 }
766 else if ( valueType == QLatin1String( "NULL" ) )
767 {
768 // This is the default ("fallback") category
769 return QVariant();
770 }
771 return value;
772 };
773
774 QDomElement catElem = catsElem.firstChildElement();
775 int i = 0;
776 QSet<QString> usedUuids;
777 while ( !catElem.isNull() )
778 {
779 if ( catElem.tagName() == QLatin1String( "category" ) )
780 {
781 QVariant value;
782 if ( catElem.hasAttribute( QStringLiteral( "value" ) ) )
783 {
784 value = valueFromString( catElem.attribute( QStringLiteral( "value" ) ), catElem.attribute( QStringLiteral( "type" ), QString() ) ) ;
785 }
786 else
787 {
788 QVariantList values;
789 QDomElement valElem = catElem.firstChildElement();
790 while ( !valElem.isNull() )
791 {
792 if ( valElem.tagName() == QLatin1String( "val" ) )
793 {
794 values << valueFromString( valElem.attribute( QStringLiteral( "value" ) ), valElem.attribute( QStringLiteral( "type" ), QString() ) );
795 }
796 valElem = valElem.nextSiblingElement();
797 }
798 if ( !values.isEmpty() )
799 value = values;
800 }
801 QString symbolName = catElem.attribute( QStringLiteral( "symbol" ) );
802 QString label = catElem.attribute( QStringLiteral( "label" ) );
803 bool render = catElem.attribute( QStringLiteral( "render" ) ) != QLatin1String( "false" );
804 QString uuid = catElem.attribute( QStringLiteral( "uuid" ), QString::number( i++ ) );
805 while ( usedUuids.contains( uuid ) )
806 {
807 uuid = QUuid::createUuid().toString();
808 }
809 if ( symbolMap.contains( symbolName ) )
810 {
811 QgsSymbol *symbol = symbolMap.take( symbolName );
812 cats.append( QgsRendererCategory( value, symbol, label, render, uuid ) );
813 usedUuids << uuid;
814 }
815 }
816 catElem = catElem.nextSiblingElement();
817 }
818
819 QString attrName = element.attribute( QStringLiteral( "attr" ) );
820
822
823 // delete symbols if there are any more
825
826 // try to load source symbol (optional)
827 QDomElement sourceSymbolElem = element.firstChildElement( QStringLiteral( "source-symbol" ) );
828 if ( !sourceSymbolElem.isNull() )
829 {
830 QgsSymbolMap sourceSymbolMap = QgsSymbolLayerUtils::loadSymbols( sourceSymbolElem, context );
831 if ( sourceSymbolMap.contains( QStringLiteral( "0" ) ) )
832 {
833 r->setSourceSymbol( sourceSymbolMap.take( QStringLiteral( "0" ) ) );
834 }
835 QgsSymbolLayerUtils::clearSymbolMap( sourceSymbolMap );
836 }
837
838 // try to load color ramp (optional)
839 QDomElement sourceColorRampElem = element.firstChildElement( QStringLiteral( "colorramp" ) );
840 if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
841 {
842 r->setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ).release() );
843 }
844
845 QDomElement rotationElem = element.firstChildElement( QStringLiteral( "rotation" ) );
846 if ( !rotationElem.isNull() && !rotationElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
847 {
848 for ( const QgsRendererCategory &cat : r->mCategories )
849 {
850 convertSymbolRotation( cat.symbol(), rotationElem.attribute( QStringLiteral( "field" ) ) );
851 }
852 if ( r->mSourceSymbol )
853 {
854 convertSymbolRotation( r->mSourceSymbol.get(), rotationElem.attribute( QStringLiteral( "field" ) ) );
855 }
856 }
857
858 QDomElement sizeScaleElem = element.firstChildElement( QStringLiteral( "sizescale" ) );
859 if ( !sizeScaleElem.isNull() && !sizeScaleElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
860 {
861 for ( const QgsRendererCategory &cat : r->mCategories )
862 {
863 convertSymbolSizeScale( cat.symbol(),
864 QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
865 sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
866 }
867 if ( r->mSourceSymbol && r->mSourceSymbol->type() == Qgis::SymbolType::Marker )
868 {
870 QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
871 sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
872 }
873 }
874
875 QDomElement ddsLegendSizeElem = element.firstChildElement( QStringLiteral( "data-defined-size-legend" ) );
876 if ( !ddsLegendSizeElem.isNull() )
877 {
878 r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readXml( ddsLegendSizeElem, context ) );
879 }
880
881 // TODO: symbol levels
882 return r;
883}
884
885QDomElement QgsCategorizedSymbolRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
886{
887 // clazy:skip
888 QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
889 rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "categorizedSymbol" ) );
890 rendererElem.setAttribute( QStringLiteral( "attr" ), mAttrName );
891
892 // String for type
893 // We just need string, bool, and three numeric types: double, ulong and long for unsigned, signed and float/double
894 const auto stringForType = []( const QMetaType::Type type ) -> QString
895 {
896 if ( type == QMetaType::Type::QChar || type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
897 {
898 return QStringLiteral( "long" );
899 }
900 else if ( type == QMetaType::Type::UInt || type == QMetaType::Type::ULongLong )
901 {
902 return QStringLiteral( "ulong" );
903 }
904 else if ( type == QMetaType::Type::Double )
905 {
906 return QStringLiteral( "double" ) ;
907 }
908 else if ( type == QMetaType::Type::Bool )
909 {
910 return QStringLiteral( "bool" );
911 }
912 else // Default: string
913 {
914 return QStringLiteral( "string" );
915 }
916 };
917
918 // categories
919 if ( !mCategories.isEmpty() )
920 {
921 int i = 0;
923 QDomElement catsElem = doc.createElement( QStringLiteral( "categories" ) );
924 QgsCategoryList::const_iterator it = mCategories.constBegin();
925 for ( ; it != mCategories.constEnd(); ++it )
926 {
927 const QgsRendererCategory &cat = *it;
928 QString symbolName = QString::number( i );
929 symbols.insert( symbolName, cat.symbol() );
930
931 QDomElement catElem = doc.createElement( QStringLiteral( "category" ) );
932 if ( cat.value().userType() == QMetaType::Type::QVariantList )
933 {
934 const QVariantList list = cat.value().toList();
935 for ( const QVariant &v : list )
936 {
937 QDomElement valueElem = doc.createElement( QStringLiteral( "val" ) );
938 valueElem.setAttribute( QStringLiteral( "value" ), v.toString() );
939 valueElem.setAttribute( QStringLiteral( "type" ), stringForType( static_cast<QMetaType::Type>( v.userType() ) ) );
940 catElem.appendChild( valueElem );
941 }
942 }
943 else
944 {
945 if ( QgsVariantUtils::isNull( cat.value() ) )
946 {
947 // We need to save NULL value as specific kind, it is the default ("fallback") category
948 catElem.setAttribute( QStringLiteral( "value" ), "NULL" );
949 catElem.setAttribute( QStringLiteral( "type" ), "NULL" );
950 }
951 else
952 {
953 catElem.setAttribute( QStringLiteral( "value" ), cat.value().toString() );
954 catElem.setAttribute( QStringLiteral( "type" ), stringForType( static_cast<QMetaType::Type>( cat.value().userType() ) ) );
955 }
956 }
957 catElem.setAttribute( QStringLiteral( "symbol" ), symbolName );
958 catElem.setAttribute( QStringLiteral( "label" ), cat.label() );
959 catElem.setAttribute( QStringLiteral( "render" ), cat.renderState() ? "true" : "false" );
960 catElem.setAttribute( QStringLiteral( "uuid" ), cat.uuid() );
961 catsElem.appendChild( catElem );
962 i++;
963 }
964 rendererElem.appendChild( catsElem );
965
966 // save symbols
967 QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc, context );
968 rendererElem.appendChild( symbolsElem );
969 }
970
971 // save source symbol
972 if ( mSourceSymbol )
973 {
974 QgsSymbolMap sourceSymbols;
975 sourceSymbols.insert( QStringLiteral( "0" ), mSourceSymbol.get() );
976 QDomElement sourceSymbolElem = QgsSymbolLayerUtils::saveSymbols( sourceSymbols, QStringLiteral( "source-symbol" ), doc, context );
977 rendererElem.appendChild( sourceSymbolElem );
978 }
979
980 // save source color ramp
981 if ( mSourceColorRamp )
982 {
983 QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
984 rendererElem.appendChild( colorRampElem );
985 }
986
987 QDomElement rotationElem = doc.createElement( QStringLiteral( "rotation" ) );
988 rendererElem.appendChild( rotationElem );
989
990 QDomElement sizeScaleElem = doc.createElement( QStringLiteral( "sizescale" ) );
991 rendererElem.appendChild( sizeScaleElem );
992
994 {
995 QDomElement ddsLegendElem = doc.createElement( QStringLiteral( "data-defined-size-legend" ) );
996 mDataDefinedSizeLegend->writeXml( ddsLegendElem, context );
997 rendererElem.appendChild( ddsLegendElem );
998 }
999
1000 saveRendererData( doc, rendererElem, context );
1001
1002 return rendererElem;
1003}
1004
1005
1006QgsLegendSymbolList QgsCategorizedSymbolRenderer::baseLegendSymbolItems() const
1007{
1009 for ( const QgsRendererCategory &cat : mCategories )
1010 {
1011 lst << QgsLegendSymbolItem( cat.symbol(), cat.label(), cat.uuid(), true );
1012 }
1013 return lst;
1014}
1015
1017{
1018
1019 auto _displayString = [ ]( const QVariant & v, int precision ) -> QString
1020 {
1021
1022 if ( QgsVariantUtils::isNull( v ) )
1023 {
1025 }
1026
1027 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};
1028
1029 // Special treatment for numeric types if group separator is set or decimalPoint is not a dot
1030 if ( v.userType() == QMetaType::Type::Double )
1031 {
1032 // if value doesn't contain a double (a default value expression for instance),
1033 // apply no transformation
1034 bool ok;
1035 v.toDouble( &ok );
1036 if ( !ok )
1037 return v.toString();
1038
1039 // Locales with decimal point != '.' or that require group separator: use QLocale
1040 if ( QLocale().decimalPoint() != '.' ||
1041 !( QLocale().numberOptions() & QLocale::NumberOption::OmitGroupSeparator ) )
1042 {
1043 if ( precision > 0 )
1044 {
1045 if ( -1 < v.toDouble() && v.toDouble() < 1 )
1046 {
1047 return QLocale().toString( v.toDouble(), 'g', precision );
1048 }
1049 else
1050 {
1051 return QLocale().toString( v.toDouble(), 'f', precision );
1052 }
1053 }
1054 else
1055 {
1056 // Precision is not set, let's guess it from the
1057 // standard conversion to string
1058 const QString s( v.toString() );
1059 const int dotPosition( s.indexOf( '.' ) );
1060 int precision;
1061 if ( dotPosition < 0 && s.indexOf( 'e' ) < 0 )
1062 {
1063 precision = 0;
1064 return QLocale().toString( v.toDouble(), 'f', precision );
1065 }
1066 else
1067 {
1068 if ( dotPosition < 0 ) precision = 0;
1069 else precision = s.length() - dotPosition - 1;
1070
1071 if ( -1 < v.toDouble() && v.toDouble() < 1 )
1072 {
1073 return QLocale().toString( v.toDouble(), 'g', precision );
1074 }
1075 else
1076 {
1077 return QLocale().toString( v.toDouble(), 'f', precision );
1078 }
1079 }
1080 }
1081 }
1082 // Default for doubles with precision
1083 else if ( precision > 0 )
1084 {
1085 if ( -1 < v.toDouble() && v.toDouble() < 1 )
1086 {
1087 return QString::number( v.toDouble(), 'g', precision );
1088 }
1089 else
1090 {
1091 return QString::number( v.toDouble(), 'f', precision );
1092 }
1093 }
1094 }
1095 // Other numeric types than doubles
1096 else if ( isNumeric &&
1097 !( QLocale().numberOptions() & QLocale::NumberOption::OmitGroupSeparator ) )
1098 {
1099 bool ok;
1100 const qlonglong converted( v.toLongLong( &ok ) );
1101 if ( ok )
1102 return QLocale().toString( converted );
1103 }
1104 else if ( v.userType() == QMetaType::Type::QByteArray )
1105 {
1106 return QObject::tr( "BLOB" );
1107 }
1108
1109 // Fallback if special rules do not apply
1110 return v.toString();
1111 };
1112
1113 if ( v.userType() == QMetaType::Type::QStringList || v.userType() == QMetaType::Type::QVariantList )
1114 {
1115 // Note that this code is never hit because the joining of lists (merged categories) happens
1116 // in data(); I'm leaving this here anyway because it is tested and it may be useful for
1117 // other purposes in the future.
1118 QString result;
1119 const QVariantList list = v.toList();
1120 for ( const QVariant &var : list )
1121 {
1122 if ( !result.isEmpty() )
1123 {
1124 result.append( ';' );
1125 }
1126 result.append( _displayString( var, precision ) );
1127 }
1128 return result;
1129 }
1130 else
1131 {
1132 return _displayString( v, precision );
1133 }
1134}
1135
1137{
1139 {
1140 // check that all symbols that have the same size expression
1141 QgsProperty ddSize;
1142 for ( const QgsRendererCategory &category : mCategories )
1143 {
1144 const QgsMarkerSymbol *symbol = static_cast<const QgsMarkerSymbol *>( category.symbol() );
1145 if ( ddSize )
1146 {
1147 QgsProperty sSize( symbol->dataDefinedSize() );
1148 if ( sSize != ddSize )
1149 {
1150 // no common size expression
1151 return baseLegendSymbolItems();
1152 }
1153 }
1154 else
1155 {
1156 ddSize = symbol->dataDefinedSize();
1157 }
1158 }
1159
1160 if ( ddSize && ddSize.isActive() )
1161 {
1163
1165 ddSizeLegend.updateFromSymbolAndProperty( static_cast<const QgsMarkerSymbol *>( mSourceSymbol.get() ), ddSize );
1166 lst += ddSizeLegend.legendSymbolList();
1167
1168 lst += baseLegendSymbolItems();
1169 return lst;
1170 }
1171 }
1172
1173 return baseLegendSymbolItems();
1174}
1175
1177{
1178 const QVariant value = valueForFeature( feature, context );
1179
1180 for ( const QgsRendererCategory &cat : mCategories )
1181 {
1182 bool match = false;
1183 if ( cat.value().userType() == QMetaType::Type::QVariantList )
1184 {
1185 const QVariantList list = cat.value().toList();
1186 for ( const QVariant &v : list )
1187 {
1188 if ( value == v )
1189 {
1190 match = true;
1191 break;
1192 }
1193 }
1194 }
1195 else
1196 {
1197 // Numeric NULL cat value is stored as an empty string
1198 if ( QgsVariantUtils::isNull( value ) && ( value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::Int ||
1199 value.userType() == QMetaType::Type::UInt || value.userType() == QMetaType::Type::LongLong ||
1200 value.userType() == QMetaType::Type::ULongLong || value.userType() == QMetaType::Type::Bool ) )
1201 {
1202 match = cat.value().toString().isEmpty();
1203 }
1204 else
1205 {
1206 match = value == cat.value();
1207 }
1208 }
1209
1210 if ( match )
1211 {
1212 if ( cat.renderState() || mCounting )
1213 return QSet< QString >() << cat.uuid();
1214 else
1215 return QSet< QString >();
1216 }
1217 }
1218
1219 return QSet< QString >();
1220}
1221
1222QString QgsCategorizedSymbolRenderer::legendKeyToExpression( const QString &key, QgsVectorLayer *layer, bool &ok ) const
1223{
1224 ok = false;
1225 int i = 0;
1226 for ( i = 0; i < mCategories.size(); i++ )
1227 {
1228 if ( mCategories[i].uuid() == key )
1229 {
1230 ok = true;
1231 break;
1232 }
1233 }
1234
1235 if ( !ok )
1236 {
1237 ok = false;
1238 return QString();
1239 }
1240
1241 const int fieldIndex = layer ? layer->fields().lookupField( mAttrName ) : -1;
1242 const bool isNumeric = layer && fieldIndex >= 0 ? layer->fields().at( fieldIndex ).isNumeric() : false;
1243 const QMetaType::Type fieldType = layer && fieldIndex >= 0 ? layer->fields().at( fieldIndex ).type() : QMetaType::Type::UnknownType;
1244 const QString attributeComponent = QgsExpression::quoteFieldExpression( mAttrName, layer );
1245
1246 ok = true;
1247 const QgsRendererCategory &cat = mCategories[i];
1248 if ( cat.value().userType() == QMetaType::Type::QVariantList )
1249 {
1250 const QVariantList list = cat.value().toList();
1251 QStringList parts;
1252 parts.reserve( list.size() );
1253 for ( const QVariant &v : list )
1254 {
1255 parts.append( QgsExpression::quotedValue( v ) );
1256 }
1257
1258 return QStringLiteral( "%1 IN (%2)" ).arg( attributeComponent, parts.join( QLatin1String( ", " ) ) );
1259 }
1260 else
1261 {
1262 // Numeric NULL cat value is stored as an empty string
1263 QVariant value = cat.value();
1264 if ( isNumeric && value.toString().isEmpty() )
1265 {
1266 value = QVariant();
1267 }
1268
1269 if ( QgsVariantUtils::isNull( value ) )
1270 return QStringLiteral( "%1 IS NULL" ).arg( attributeComponent );
1271 else if ( fieldType == QMetaType::Type::UnknownType )
1272 return QStringLiteral( "%1 = %2" ).arg( attributeComponent, QgsExpression::quotedValue( value ) );
1273 else
1274 return QStringLiteral( "%1 = %2" ).arg( attributeComponent, QgsExpression::quotedValue( value, fieldType ) );
1275 }
1276}
1277
1282
1284{
1285 return mSourceSymbol.get();
1286}
1287
1292
1297
1302
1307
1309{
1310 setSourceColorRamp( ramp );
1311 double num = mCategories.count() - 1;
1312 double count = 0;
1313
1314 QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp );
1315 if ( randomRamp )
1316 {
1317 //ramp is a random colors ramp, so inform it of the total number of required colors
1318 //this allows the ramp to pregenerate a set of visually distinctive colors
1319 randomRamp->setTotalColorCount( mCategories.count() );
1320 }
1321
1322 for ( const QgsRendererCategory &cat : mCategories )
1323 {
1324 double value = count / num;
1325 cat.symbol()->setColor( mSourceColorRamp->color( value ) );
1326 count += 1;
1327 }
1328}
1329
1331{
1332 int i = 0;
1333 for ( const QgsRendererCategory &cat : mCategories )
1334 {
1335 QgsSymbol *symbol = sym->clone();
1336 symbol->setColor( cat.symbol()->color() );
1337 updateCategorySymbol( i, symbol );
1338 ++i;
1339 }
1340 setSourceSymbol( sym->clone() );
1341}
1342
1344{
1345 return true;
1346}
1347
1349{
1350 for ( const QgsRendererCategory &category : std::as_const( mCategories ) )
1351 {
1352 if ( category.uuid() == key )
1353 {
1354 return category.renderState();
1355 }
1356 }
1357
1358 return true;
1359}
1360
1362{
1363 bool ok = false;
1364 int i = 0;
1365 for ( i = 0; i < mCategories.size(); i++ )
1366 {
1367 if ( mCategories[i].uuid() == key )
1368 {
1369 ok = true;
1370 break;
1371 }
1372 }
1373
1374 if ( ok )
1375 updateCategorySymbol( i, symbol );
1376 else
1377 delete symbol;
1378}
1379
1380void QgsCategorizedSymbolRenderer::checkLegendSymbolItem( const QString &key, bool state )
1381{
1382 for ( int i = 0; i < mCategories.size(); i++ )
1383 {
1384 if ( mCategories[i].uuid() == key )
1385 {
1386 updateCategoryRenderState( i, state );
1387 break;
1388 }
1389 }
1390}
1391
1393{
1394 std::unique_ptr< QgsCategorizedSymbolRenderer > r;
1395 if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
1396 {
1397 r.reset( static_cast<QgsCategorizedSymbolRenderer *>( renderer->clone() ) );
1398 }
1399 else if ( renderer->type() == QLatin1String( "graduatedSymbol" ) )
1400 {
1401 const QgsGraduatedSymbolRenderer *graduatedSymbolRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1402 if ( graduatedSymbolRenderer )
1403 {
1404 r.reset( new QgsCategorizedSymbolRenderer( QString(), QgsCategoryList() ) );
1405 if ( graduatedSymbolRenderer->sourceSymbol() )
1406 r->setSourceSymbol( graduatedSymbolRenderer->sourceSymbol()->clone() );
1407 if ( graduatedSymbolRenderer->sourceColorRamp() )
1408 {
1409 r->setSourceColorRamp( graduatedSymbolRenderer->sourceColorRamp()->clone() );
1410 }
1411 r->setClassAttribute( graduatedSymbolRenderer->classAttribute() );
1412 }
1413 }
1414 else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
1415 {
1416 const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer );
1417 if ( pointDistanceRenderer )
1418 r.reset( convertFromRenderer( pointDistanceRenderer->embeddedRenderer() ) );
1419 }
1420 else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
1421 {
1422 const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer );
1423 if ( invertedPolygonRenderer )
1424 r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1425 }
1426 else if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
1427 {
1428 const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
1432 req.setNoAttributes();
1433 QgsFeatureIterator it = layer->getFeatures( req );
1434 QgsFeature feature;
1435 while ( it.nextFeature( feature ) && categories.size() < 2000 )
1436 {
1437 if ( feature.embeddedSymbol() )
1438 categories.append( QgsRendererCategory( feature.id(), feature.embeddedSymbol()->clone(), QString::number( feature.id() ) ) );
1439 }
1440 categories.append( QgsRendererCategory( QVariant(), embeddedRenderer->defaultSymbol()->clone(), QString() ) );
1441 r.reset( new QgsCategorizedSymbolRenderer( QStringLiteral( "$id" ), categories ) );
1442 }
1443
1444 // If not one of the specifically handled renderers, then just grab the symbol from the renderer
1445 // Could have applied this to specific renderer types (singleSymbol, graduatedSymbol)
1446
1447 if ( !r )
1448 {
1449 r = std::make_unique< QgsCategorizedSymbolRenderer >( QString(), QgsCategoryList() );
1450 QgsRenderContext context;
1451 QgsSymbolList symbols = const_cast<QgsFeatureRenderer *>( renderer )->symbols( context );
1452 if ( !symbols.isEmpty() )
1453 {
1454 QgsSymbol *newSymbol = symbols.at( 0 )->clone();
1457 r->setSourceSymbol( newSymbol );
1458 }
1459 }
1460
1461 renderer->copyRendererData( r.get() );
1462
1463 return r.release();
1464}
1465
1470
1475
1476int QgsCategorizedSymbolRenderer::matchToSymbols( QgsStyle *style, Qgis::SymbolType type, QVariantList &unmatchedCategories, QStringList &unmatchedSymbols, const bool caseSensitive, const bool useTolerantMatch )
1477{
1478 if ( !style )
1479 return 0;
1480
1481 int matched = 0;
1482 unmatchedSymbols = style->symbolNames();
1483 const QSet< QString > allSymbolNames( unmatchedSymbols.begin(), unmatchedSymbols.end() );
1484
1485 const thread_local QRegularExpression tolerantMatchRe( QStringLiteral( "[^\\w\\d ]" ), QRegularExpression::UseUnicodePropertiesOption );
1486
1487 for ( int catIdx = 0; catIdx < mCategories.count(); ++catIdx )
1488 {
1489 const QVariant value = mCategories.at( catIdx ).value();
1490 const QString val = value.toString().trimmed();
1491 std::unique_ptr< QgsSymbol > symbol( style->symbol( val ) );
1492 // case-sensitive match
1493 if ( symbol && symbol->type() == type )
1494 {
1495 matched++;
1496 unmatchedSymbols.removeAll( val );
1497 updateCategorySymbol( catIdx, symbol.release() );
1498 continue;
1499 }
1500
1501 if ( !caseSensitive || useTolerantMatch )
1502 {
1503 QString testVal = val;
1504 if ( useTolerantMatch )
1505 testVal.replace( tolerantMatchRe, QString() );
1506
1507 bool foundMatch = false;
1508 for ( const QString &name : allSymbolNames )
1509 {
1510 QString testName = name.trimmed();
1511 if ( useTolerantMatch )
1512 testName.replace( tolerantMatchRe, QString() );
1513
1514 if ( testName == testVal || ( !caseSensitive && testName.trimmed().compare( testVal, Qt::CaseInsensitive ) == 0 ) )
1515 {
1516 // found a case-insensitive match
1517 std::unique_ptr< QgsSymbol > symbol( style->symbol( name ) );
1518 if ( symbol && symbol->type() == type )
1519 {
1520 matched++;
1521 unmatchedSymbols.removeAll( name );
1522 updateCategorySymbol( catIdx, symbol.release() );
1523 foundMatch = true;
1524 break;
1525 }
1526 }
1527 }
1528 if ( foundMatch )
1529 continue;
1530 }
1531
1532 unmatchedCategories << value;
1533 }
1534
1535 return matched;
1536}
1537
1538QgsCategoryList QgsCategorizedSymbolRenderer::createCategories( const QList<QVariant> &values, const QgsSymbol *symbol, QgsVectorLayer *layer, const QString &attributeName )
1539{
1540 QgsCategoryList cats;
1541 QVariantList vals = values;
1542 // sort the categories first
1543 QgsSymbolLayerUtils::sortVariantList( vals, Qt::AscendingOrder );
1544
1545 if ( layer && !attributeName.isNull() )
1546 {
1547 const QgsFields fields = layer->fields();
1548 for ( const QVariant &value : vals )
1549 {
1550 QgsSymbol *newSymbol = symbol->clone();
1552 if ( !QgsVariantUtils::isNull( value ) )
1553 {
1554 const int fieldIdx = fields.lookupField( attributeName );
1555 QString categoryName = displayString( value );
1556 if ( fieldIdx != -1 )
1557 {
1558 const QgsField field = fields.at( fieldIdx );
1559 const QgsEditorWidgetSetup setup = field.editorWidgetSetup();
1561 categoryName = formatter->representValue( layer, fieldIdx, setup.config(), QVariant(), value );
1562 }
1563 cats.append( QgsRendererCategory( value, newSymbol, categoryName, true ) );
1564 }
1565 }
1566 }
1567
1568 // add null (default) value
1569 QgsSymbol *newSymbol = symbol->clone();
1571 cats.append( QgsRendererCategory( QVariant(), newSymbol, QString(), true ) );
1572
1573 return cats;
1574}
@ 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.
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.
QVariantMap config() const
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)
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)
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...
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)
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 bool createFunctionElement(QDomDocument &doc, QDomElement &element, const QString &function)
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:129
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:197
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:53
QMap< QString, QgsSymbol * > QgsSymbolMap
Definition qgsrenderer.h:48
QList< QgsSymbol * > QgsSymbolList
Definition qgsrenderer.h:47
int precision
Contains information relating to the style entity currently being visited.