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