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