QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
qgsgraduatedsymbolrendererwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgraduatedsymbolrendererwidget.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <QKeyEvent>
17#include <QMenu>
18#include <QMessageBox>
19#include <QStandardItemModel>
20#include <QStandardItem>
21#include <QPen>
22#include <QPainter>
23#include <QClipboard>
24#include <QCompleter>
25
27#include "qgspanelwidget.h"
28
31#include "qgssymbol.h"
32#include "qgssymbollayerutils.h"
33#include "qgscolorrampimpl.h"
34#include "qgscolorrampbutton.h"
35#include "qgsstyle.h"
37#include "qgsvectorlayer.h"
40#include "qgslogger.h"
41#include "qgsludialog.h"
42#include "qgsproject.h"
44#include "qgsmapcanvas.h"
46#include "qgsapplication.h"
50#include "qgsgui.h"
51#include "qgsprocessinggui.h"
56#include "qgsdoublevalidator.h"
57#include "qgsmarkersymbol.h"
58
59
60// ------------------------------ Model ------------------------------------
61
63
64QgsGraduatedSymbolRendererModel::QgsGraduatedSymbolRendererModel( QObject *parent ) : QAbstractItemModel( parent )
65 , mMimeFormat( QStringLiteral( "application/x-qgsgraduatedsymbolrendererv2model" ) )
66{
67}
68
69void QgsGraduatedSymbolRendererModel::setRenderer( QgsGraduatedSymbolRenderer *renderer )
70{
71 if ( mRenderer )
72 {
73 if ( !mRenderer->ranges().isEmpty() )
74 {
75 beginRemoveRows( QModelIndex(), 0, mRenderer->ranges().size() - 1 );
76 mRenderer = nullptr;
77 endRemoveRows();
78 }
79 else
80 {
81 mRenderer = nullptr;
82 }
83 }
84 if ( renderer )
85 {
86 if ( !renderer->ranges().isEmpty() )
87 {
88 beginInsertRows( QModelIndex(), 0, renderer->ranges().size() - 1 );
89 mRenderer = renderer;
90 endInsertRows();
91 }
92 else
93 {
94 mRenderer = renderer;
95 }
96 }
97}
98
99void QgsGraduatedSymbolRendererModel::addClass( QgsSymbol *symbol )
100{
101 if ( !mRenderer ) return;
102 int idx = mRenderer->ranges().size();
103 beginInsertRows( QModelIndex(), idx, idx );
104 mRenderer->addClass( symbol );
105 endInsertRows();
106}
107
108void QgsGraduatedSymbolRendererModel::addClass( const QgsRendererRange &range )
109{
110 if ( !mRenderer )
111 {
112 return;
113 }
114 int idx = mRenderer->ranges().size();
115 beginInsertRows( QModelIndex(), idx, idx );
116 mRenderer->addClass( range );
117 endInsertRows();
118}
119
120QgsRendererRange QgsGraduatedSymbolRendererModel::rendererRange( const QModelIndex &index )
121{
122 if ( !index.isValid() || !mRenderer || mRenderer->ranges().size() <= index.row() )
123 {
124 return QgsRendererRange();
125 }
126
127 return mRenderer->ranges().value( index.row() );
128}
129
130Qt::ItemFlags QgsGraduatedSymbolRendererModel::flags( const QModelIndex &index ) const
131{
132 if ( !index.isValid() )
133 {
134 return Qt::ItemIsDropEnabled;
135 }
136
137 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
138
139 if ( index.column() == 2 )
140 {
141 flags |= Qt::ItemIsEditable;
142 }
143
144 return flags;
145}
146
147Qt::DropActions QgsGraduatedSymbolRendererModel::supportedDropActions() const
148{
149 return Qt::MoveAction;
150}
151
152QVariant QgsGraduatedSymbolRendererModel::data( const QModelIndex &index, int role ) const
153{
154 if ( !index.isValid() || !mRenderer ) return QVariant();
155
156 const QgsRendererRange range = mRenderer->ranges().value( index.row() );
157
158 if ( role == Qt::CheckStateRole && index.column() == 0 )
159 {
160 return range.renderState() ? Qt::Checked : Qt::Unchecked;
161 }
162 else if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
163 {
164 switch ( index.column() )
165 {
166 case 1:
167 {
168 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
169 if ( decimalPlaces < 0 ) decimalPlaces = 0;
170 return QString( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) + " - " + QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
171 }
172 case 2:
173 return range.label();
174 default:
175 return QVariant();
176 }
177 }
178 else if ( role == Qt::DecorationRole && index.column() == 0 && range.symbol() )
179 {
180 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
182 }
183 else if ( role == Qt::TextAlignmentRole )
184 {
185 return ( index.column() == 0 ) ? static_cast<Qt::Alignment::Int>( Qt::AlignHCenter ) : static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
186 }
187 else if ( role == Qt::EditRole )
188 {
189 switch ( index.column() )
190 {
191 // case 1: return rangeStr;
192 case 2:
193 return range.label();
194 default:
195 return QVariant();
196 }
197 }
198
199 return QVariant();
200}
201
202bool QgsGraduatedSymbolRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
203{
204 if ( !index.isValid() )
205 return false;
206
207 if ( index.column() == 0 && role == Qt::CheckStateRole )
208 {
209 mRenderer->updateRangeRenderState( index.row(), value == Qt::Checked );
210 emit dataChanged( index, index );
211 return true;
212 }
213
214 if ( role != Qt::EditRole )
215 return false;
216
217 switch ( index.column() )
218 {
219 case 1: // range
220 return false; // range is edited in popup dialog
221 case 2: // label
222 mRenderer->updateRangeLabel( index.row(), value.toString() );
223 break;
224 default:
225 return false;
226 }
227
228 emit dataChanged( index, index );
229 return true;
230}
231
232QVariant QgsGraduatedSymbolRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
233{
234 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
235 {
236 QStringList lst;
237 lst << tr( "Symbol" ) << tr( "Values" ) << tr( "Legend" );
238 return lst.value( section );
239 }
240 return QVariant();
241}
242
243int QgsGraduatedSymbolRendererModel::rowCount( const QModelIndex &parent ) const
244{
245 if ( parent.isValid() || !mRenderer )
246 {
247 return 0;
248 }
249 return mRenderer->ranges().size();
250}
251
252int QgsGraduatedSymbolRendererModel::columnCount( const QModelIndex &index ) const
253{
254 Q_UNUSED( index )
255 return 3;
256}
257
258QModelIndex QgsGraduatedSymbolRendererModel::index( int row, int column, const QModelIndex &parent ) const
259{
260 if ( hasIndex( row, column, parent ) )
261 {
262 return createIndex( row, column );
263 }
264 return QModelIndex();
265}
266
267QModelIndex QgsGraduatedSymbolRendererModel::parent( const QModelIndex &index ) const
268{
269 Q_UNUSED( index )
270 return QModelIndex();
271}
272
273QStringList QgsGraduatedSymbolRendererModel::mimeTypes() const
274{
275 QStringList types;
276 types << mMimeFormat;
277 return types;
278}
279
280QMimeData *QgsGraduatedSymbolRendererModel::mimeData( const QModelIndexList &indexes ) const
281{
282 QMimeData *mimeData = new QMimeData();
283 QByteArray encodedData;
284
285 QDataStream stream( &encodedData, QIODevice::WriteOnly );
286
287 // Create list of rows
288 const auto constIndexes = indexes;
289 for ( const QModelIndex &index : constIndexes )
290 {
291 if ( !index.isValid() || index.column() != 0 )
292 continue;
293
294 stream << index.row();
295 }
296 mimeData->setData( mMimeFormat, encodedData );
297 return mimeData;
298}
299
300bool QgsGraduatedSymbolRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
301{
302 Q_UNUSED( row )
303 Q_UNUSED( column )
304 if ( action != Qt::MoveAction ) return true;
305
306 if ( !data->hasFormat( mMimeFormat ) ) return false;
307
308 QByteArray encodedData = data->data( mMimeFormat );
309 QDataStream stream( &encodedData, QIODevice::ReadOnly );
310
311 QVector<int> rows;
312 while ( !stream.atEnd() )
313 {
314 int r;
315 stream >> r;
316 rows.append( r );
317 }
318
319 int to = parent.row();
320 // to is -1 if dragged outside items, i.e. below any item,
321 // then move to the last position
322 if ( to == -1 ) to = mRenderer->ranges().size(); // out of rang ok, will be decreased
323 for ( int i = rows.size() - 1; i >= 0; i-- )
324 {
325 QgsDebugMsg( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ) );
326 int t = to;
327 // moveCategory first removes and then inserts
328 if ( rows[i] < t ) t--;
329 mRenderer->moveClass( rows[i], t );
330 // current moved under another, shift its index up
331 for ( int j = 0; j < i; j++ )
332 {
333 if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
334 }
335 // removed under 'to' so the target shifted down
336 if ( rows[i] < to ) to--;
337 }
338 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
339 emit rowsMoved();
340 return false;
341}
342
343void QgsGraduatedSymbolRendererModel::deleteRows( QList<int> rows )
344{
345 for ( int i = rows.size() - 1; i >= 0; i-- )
346 {
347 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
348 mRenderer->deleteClass( rows[i] );
349 endRemoveRows();
350 }
351}
352
353void QgsGraduatedSymbolRendererModel::removeAllRows()
354{
355 beginRemoveRows( QModelIndex(), 0, mRenderer->ranges().size() - 1 );
356 mRenderer->deleteAllClasses();
357 endRemoveRows();
358}
359
360void QgsGraduatedSymbolRendererModel::sort( int column, Qt::SortOrder order )
361{
362 if ( column == 0 )
363 {
364 return;
365 }
366 if ( column == 1 )
367 {
368 mRenderer->sortByValue( order );
369 }
370 else if ( column == 2 )
371 {
372 mRenderer->sortByLabel( order );
373 }
374 emit rowsMoved();
375 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
376}
377
378void QgsGraduatedSymbolRendererModel::updateSymbology()
379{
380 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
381}
382
383void QgsGraduatedSymbolRendererModel::updateLabels()
384{
385 emit dataChanged( createIndex( 0, 2 ), createIndex( mRenderer->ranges().size(), 2 ) );
386}
387
388// ------------------------------ View style --------------------------------
389QgsGraduatedSymbolRendererViewStyle::QgsGraduatedSymbolRendererViewStyle( QWidget *parent )
390 : QgsProxyStyle( parent )
391{}
392
393void QgsGraduatedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
394{
395 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
396 {
397 QStyleOption opt( *option );
398 opt.rect.setLeft( 0 );
399 // draw always as line above, because we move item to that index
400 opt.rect.setHeight( 0 );
401 if ( widget ) opt.rect.setRight( widget->width() );
402 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
403 return;
404 }
405 QProxyStyle::drawPrimitive( element, option, painter, widget );
406}
407
409
410// ------------------------------ Widget ------------------------------------
411
413{
414 return new QgsGraduatedSymbolRendererWidget( layer, style, renderer );
415}
416
417QgsExpressionContext QgsGraduatedSymbolRendererWidget::createExpressionContext() const
418{
419 QgsExpressionContext expContext;
423
424 if ( auto *lMapCanvas = mContext.mapCanvas() )
425 {
426 expContext << QgsExpressionContextUtils::mapSettingsScope( lMapCanvas->mapSettings() )
427 << new QgsExpressionContextScope( lMapCanvas->expressionContextScope() );
428 if ( const QgsExpressionContextScopeGenerator *generator = dynamic_cast< const QgsExpressionContextScopeGenerator * >( lMapCanvas->temporalController() ) )
429 {
430 expContext << generator->createExpressionContextScope();
431 }
432 }
433 else
434 {
436 }
437
438 if ( auto *lVectorLayer = vectorLayer() )
439 expContext << QgsExpressionContextUtils::layerScope( lVectorLayer );
440
441 // additional scopes
442 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
443 for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
444 {
445 expContext.appendScope( new QgsExpressionContextScope( scope ) );
446 }
447
448 return expContext;
449}
450
452 : QgsRendererWidget( layer, style )
453{
454 // try to recognize the previous renderer
455 // (null renderer means "no previous renderer")
456 if ( renderer )
457 {
459 }
460 if ( !mRenderer )
461 {
462 mRenderer = std::make_unique< QgsGraduatedSymbolRenderer >( QString(), QgsRangeList() );
463 if ( renderer )
464 renderer->copyRendererData( mRenderer.get() );
465 }
466
467 // setup user interface
468 setupUi( this );
469
470 mSymmetryPointValidator = new QgsDoubleValidator( this );
471 cboSymmetryPoint->setEditable( true );
472 cboSymmetryPoint->setValidator( mSymmetryPointValidator );
473
474 const QMap<QString, QString> methods = QgsApplication::classificationMethodRegistry()->methodNames();
475 for ( QMap<QString, QString>::const_iterator it = methods.constBegin(); it != methods.constEnd(); ++it )
476 {
477 QIcon icon = QgsApplication::classificationMethodRegistry()->icon( it.value() );
478 cboGraduatedMode->addItem( icon, it.key(), it.value() );
479 }
480
481 connect( methodComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged );
482 this->layout()->setContentsMargins( 0, 0, 0, 0 );
483
484 mModel = new QgsGraduatedSymbolRendererModel( this );
485
486 mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
487 mExpressionWidget->setLayer( mLayer );
488
489 btnChangeGraduatedSymbol->setLayer( mLayer );
490 btnChangeGraduatedSymbol->registerExpressionContextGenerator( this );
491
492 mSizeUnitWidget->setUnits(
493 {
494 Qgis::RenderUnit::Millimeters,
495 Qgis::RenderUnit::MapUnits,
496 Qgis::RenderUnit::Pixels,
497 Qgis::RenderUnit::Points,
498 Qgis::RenderUnit::Inches
499 } );
500
501 spinPrecision->setMinimum( QgsClassificationMethod::MIN_PRECISION );
502 spinPrecision->setMaximum( QgsClassificationMethod::MAX_PRECISION );
503 spinPrecision->setClearValue( 4 );
504
505 spinGraduatedClasses->setShowClearButton( false );
506
507 btnColorRamp->setShowRandomColorRamp( true );
508
509 // set project default color ramp
510 std::unique_ptr< QgsColorRamp > colorRamp( QgsProject::instance()->styleSettings()->defaultColorRamp() );
511 if ( colorRamp )
512 {
513 btnColorRamp->setColorRamp( colorRamp.get() );
514 }
515 else
516 {
517 QgsColorRamp *ramp = new QgsGradientColorRamp( QColor( 255, 255, 255 ), QColor( 255, 0, 0 ) );
518 btnColorRamp->setColorRamp( ramp );
519 delete ramp;
520 }
521
522
523 viewGraduated->setStyle( new QgsGraduatedSymbolRendererViewStyle( viewGraduated ) );
524
525 mGraduatedSymbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
526 if ( mGraduatedSymbol )
527 {
528 btnChangeGraduatedSymbol->setSymbolType( mGraduatedSymbol->type() );
529 btnChangeGraduatedSymbol->setSymbol( mGraduatedSymbol->clone() );
530
531 methodComboBox->blockSignals( true );
532 methodComboBox->addItem( tr( "Color" ), ColorMode );
533 switch ( mGraduatedSymbol->type() )
534 {
536 {
537 methodComboBox->addItem( tr( "Size" ), SizeMode );
538 minSizeSpinBox->setValue( 1 );
539 maxSizeSpinBox->setValue( 8 );
540 break;
541 }
543 {
544 methodComboBox->addItem( tr( "Size" ), SizeMode );
545 minSizeSpinBox->setValue( .1 );
546 maxSizeSpinBox->setValue( 2 );
547 break;
548 }
550 {
551 //set button and label invisible to avoid display of a single item combobox
552 methodComboBox->hide();
553 labelMethod->hide();
554 break;
555 }
557 break;
558 }
559 methodComboBox->blockSignals( false );
560 }
561
562 connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsGraduatedSymbolRendererWidget::graduatedColumnChanged );
563 connect( viewGraduated, &QAbstractItemView::doubleClicked, this, &QgsGraduatedSymbolRendererWidget::rangesDoubleClicked );
564 connect( viewGraduated, &QAbstractItemView::clicked, this, &QgsGraduatedSymbolRendererWidget::rangesClicked );
565 connect( viewGraduated, &QTreeView::customContextMenuRequested, this, &QgsGraduatedSymbolRendererWidget::contextMenuViewCategories );
566
567 connect( btnGraduatedClassify, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
568 connect( btnChangeGraduatedSymbol, &QgsSymbolButton::changed, this, &QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol );
569 connect( btnGraduatedDelete, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteClasses );
570 connect( btnDeleteAllClasses, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteAllClasses );
571 connect( btnGraduatedAdd, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::addClass );
572 connect( cbxLinkBoundaries, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::toggleBoundariesLink );
573 connect( mSizeUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed );
574
575 connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::updateMethodParameters );
576
578
579 // initialize from previously set renderer
581
582 // default to collapsed symmetric group for ui simplicity
583 mGroupBoxSymmetric->setCollapsed( true ); //
584
585 // menus for data-defined rotation/size
586 QMenu *advMenu = new QMenu( this );
587
588 mActionLevels = advMenu->addAction( tr( "Symbol Levels…" ), this, &QgsGraduatedSymbolRendererWidget::showSymbolLevels );
589 if ( mGraduatedSymbol && mGraduatedSymbol->type() == Qgis::SymbolType::Marker )
590 {
591 QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
592 // only from Qt 5.6 there is convenience addAction() with new style connection
593 connect( actionDdsLegend, &QAction::triggered, this, &QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend );
594 }
595
596 btnAdvanced->setMenu( advMenu );
597
598 mHistogramWidget->setLayer( mLayer );
599 mHistogramWidget->setRenderer( mRenderer.get() );
601 connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), mHistogramWidget, &QgsHistogramWidget::setSourceFieldExp );
602
603 mExpressionWidget->registerExpressionContextGenerator( this );
604
605 mUpdateTimer.setSingleShot( true );
606 mUpdateTimer.connect( &mUpdateTimer, &QTimer::timeout, this, &QgsGraduatedSymbolRendererWidget::classifyGraduatedImpl );
607}
608
609void QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed()
610{
611 if ( !mGraduatedSymbol )
612 return;
613 mGraduatedSymbol->setOutputUnit( mSizeUnitWidget->unit() );
614 mGraduatedSymbol->setMapUnitScale( mSizeUnitWidget->getMapUnitScale() );
615 mRenderer->updateSymbols( mGraduatedSymbol.get() );
617}
618
620{
621 delete mModel;
622 mParameterWidgetWrappers.clear();
623}
624
626{
627 return mRenderer.get();
628}
629
631{
633 btnChangeGraduatedSymbol->setMapCanvas( context.mapCanvas() );
634 btnChangeGraduatedSymbol->setMessageBar( context.messageBar() );
635}
636
638{
639 delete mActionLevels;
640 mActionLevels = nullptr;
641}
642
643// Connect/disconnect event handlers which trigger updating renderer
645{
646 connect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
647 connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
649 connect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
650 connect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
651 connect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
652 connect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
653 connect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
654
655 connect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
656 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
657
658 connect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
659 connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
660 connect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
661 connect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
662
663 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
664 {
666 }
667}
668
669// Connect/disconnect event handlers which trigger updating renderer
671{
672 disconnect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
673 disconnect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
675 disconnect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
676 disconnect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
677 disconnect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
678 disconnect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
679 disconnect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
680
681 disconnect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
682 disconnect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
683
684 disconnect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
685 disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
686 disconnect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
687 disconnect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
688
689 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
690 {
692 }
693}
694
696{
698 mBlockUpdates++;
699
700 const QgsClassificationMethod *method = mRenderer->classificationMethod();
701
702 const QgsRangeList ranges = mRenderer->ranges();
703
704 // use the breaks for symmetry point
705 int precision = spinPrecision->value() + 2;
706 while ( cboSymmetryPoint->count() )
707 cboSymmetryPoint->removeItem( 0 );
708 for ( int i = 0; i < ranges.count() - 1; i++ )
709 cboSymmetryPoint->addItem( QLocale().toString( ranges.at( i ).upperValue(), 'f', precision ), ranges.at( i ).upperValue() );
710
711 if ( method )
712 {
713 int idx = cboGraduatedMode->findData( method->id() );
714 if ( idx >= 0 )
715 cboGraduatedMode->setCurrentIndex( idx );
716
717 mGroupBoxSymmetric->setVisible( method->symmetricModeAvailable() );
718 mGroupBoxSymmetric->setChecked( method->symmetricModeEnabled() );
719 cbxAstride->setChecked( method->symmetryAstride() );
720 if ( method->symmetricModeEnabled() )
721 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( method->symmetryPoint(), 'f', method->labelPrecision() + 2 ) );
722
723 txtLegendFormat->setText( method->labelFormat() );
724 spinPrecision->setValue( method->labelPrecision() );
725 cbxTrimTrailingZeroes->setChecked( method->labelTrimTrailingZeroes() );
726
728 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
729 {
730 const QgsProcessingParameterDefinition *def = ppww->parameterDefinition();
731 QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
732 ppww->setParameterValue( value, context );
733 }
734 }
735
736 // Only update class count if different - otherwise typing value gets very messy
737 int nclasses = ranges.count();
738 if ( nclasses && ( updateCount || ( method && ( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) ) ) )
739 {
740 spinGraduatedClasses->setValue( ranges.count() );
741 }
742 if ( method )
743 {
744 spinGraduatedClasses->setEnabled( !( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) );
745 }
746 else
747 {
748 spinGraduatedClasses->setEnabled( true );
749 }
750
751 // set column
752 QString attrName = mRenderer->classAttribute();
753 mExpressionWidget->setField( attrName );
754 mHistogramWidget->setSourceFieldExp( attrName );
755
756 // set source symbol
757 if ( mRenderer->sourceSymbol() )
758 {
759 mGraduatedSymbol.reset( mRenderer->sourceSymbol()->clone() );
760 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
761 }
762
763 mModel->setRenderer( mRenderer.get() );
764 viewGraduated->setModel( mModel );
765
766 connect( viewGraduated->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsGraduatedSymbolRendererWidget::selectionChanged );
767
768 if ( mGraduatedSymbol )
769 {
770 mSizeUnitWidget->blockSignals( true );
771 mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
772 mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
773 mSizeUnitWidget->blockSignals( false );
774 }
775
776 // set source color ramp
777 methodComboBox->blockSignals( true );
778 switch ( mRenderer->graduatedMethod() )
779 {
780 case Qgis::GraduatedMethod::Color:
781 {
782 methodComboBox->setCurrentIndex( methodComboBox->findData( ColorMode ) );
783 if ( mRenderer->sourceColorRamp() )
784 {
785 btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
786 }
787 break;
788 }
789 case Qgis::GraduatedMethod::Size:
790 {
791 methodComboBox->setCurrentIndex( methodComboBox->findData( SizeMode ) );
792 if ( !mRenderer->ranges().isEmpty() ) // avoid overriding default size with zeros
793 {
794 minSizeSpinBox->setValue( mRenderer->minSymbolSize() );
795 maxSizeSpinBox->setValue( mRenderer->maxSymbolSize() );
796 }
797 break;
798 }
799 }
800 toggleMethodWidgets( static_cast< MethodMode>( methodComboBox->currentData().toInt() ) );
801 methodComboBox->blockSignals( false );
802
803 viewGraduated->resizeColumnToContents( 0 );
804 viewGraduated->resizeColumnToContents( 1 );
805 viewGraduated->resizeColumnToContents( 2 );
806
807 mHistogramWidget->refresh();
808
810 mBlockUpdates--;
811
812 emit widgetChanged();
813}
814
816{
817 mRenderer->setClassAttribute( field );
818}
819
820void QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged( int )
821{
822 const MethodMode newMethod = static_cast< MethodMode >( methodComboBox->currentData().toInt() );
823 toggleMethodWidgets( newMethod );
824 switch ( newMethod )
825 {
826 case ColorMode:
827 {
828 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Color );
829 QgsColorRamp *ramp = btnColorRamp->colorRamp();
830
831 if ( !ramp )
832 {
833 QMessageBox::critical( this, tr( "Select Method" ), tr( "No color ramp defined." ) );
834 return;
835 }
836 mRenderer->setSourceColorRamp( ramp );
838 break;
839 }
840
841 case SizeMode:
842 {
843 lblColorRamp->setVisible( false );
844 btnColorRamp->setVisible( false );
845 lblSize->setVisible( true );
846 minSizeSpinBox->setVisible( true );
847 lblSize->setVisible( true );
848 maxSizeSpinBox->setVisible( true );
849 mSizeUnitWidget->setVisible( true );
850
851 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Size );
852 reapplySizes();
853 break;
854 }
855 }
856}
857
858void QgsGraduatedSymbolRendererWidget::updateMethodParameters()
859{
860 clearParameterWidgets();
861
862 const QString methodId = cboGraduatedMode->currentData().toString();
864 Q_ASSERT( method );
865
866 // need more context?
868
869 for ( const QgsProcessingParameterDefinition *def : method->parameterDefinitions() )
870 {
872 mParametersLayout->addRow( ppww->createWrappedLabel(), ppww->createWrappedWidget( context ) );
873
874 QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
875 ppww->setParameterValue( value, context );
876
878
879 mParameterWidgetWrappers.push_back( std::unique_ptr<QgsAbstractProcessingParameterWidgetWrapper>( ppww ) );
880 }
881
882 spinGraduatedClasses->setEnabled( !( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) );
883}
884
885void QgsGraduatedSymbolRendererWidget::toggleMethodWidgets( MethodMode mode )
886{
887 switch ( mode )
888 {
889 case ColorMode:
890 {
891 lblColorRamp->setVisible( true );
892 btnColorRamp->setVisible( true );
893 lblSize->setVisible( false );
894 minSizeSpinBox->setVisible( false );
895 lblSizeTo->setVisible( false );
896 maxSizeSpinBox->setVisible( false );
897 mSizeUnitWidget->setVisible( false );
898 break;
899 }
900
901 case SizeMode:
902 {
903 lblColorRamp->setVisible( false );
904 btnColorRamp->setVisible( false );
905 lblSize->setVisible( true );
906 minSizeSpinBox->setVisible( true );
907 lblSizeTo->setVisible( true );
908 maxSizeSpinBox->setVisible( true );
909 mSizeUnitWidget->setVisible( true );
910 break;
911 }
912 }
913}
914
915void QgsGraduatedSymbolRendererWidget::clearParameterWidgets()
916{
917 while ( mParametersLayout->rowCount() )
918 {
919 QFormLayout::TakeRowResult row = mParametersLayout->takeRow( 0 );
920 for ( QLayoutItem *item : QList<QLayoutItem *>( {row.labelItem, row.fieldItem} ) )
921 if ( item )
922 {
923 if ( item->widget() )
924 item->widget()->deleteLater();
925 delete item;
926 }
927 }
928 mParameterWidgetWrappers.clear();
929}
930
932{
933 if ( !mModel )
934 return;
935
936 mModel->updateSymbology();
937
939 spinGraduatedClasses->setValue( mRenderer->ranges().count() );
941
942 emit widgetChanged();
943}
944
946{
947 for ( const QgsLegendSymbolItem &legendSymbol : levels )
948 {
949 QgsSymbol *sym = legendSymbol.symbol();
950 for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
951 {
952 mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
953 }
954 }
955 mRenderer->setUsingSymbolLevels( enabled );
956 mModel->updateSymbology();
957 emit widgetChanged();
958}
959
960void QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
961{
962 QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
963 if ( !dlg )
964 return;
965
966 delete dlg->symbol();
967}
968
969void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget()
970{
971 QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
972 mGraduatedSymbol.reset( dlg->symbol()->clone() );
973
975}
976
978{
979 mSizeUnitWidget->blockSignals( true );
980 mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
981 mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
982 mSizeUnitWidget->blockSignals( false );
983
984 QItemSelectionModel *m = viewGraduated->selectionModel();
985 QModelIndexList selectedIndexes = m->selectedRows( 1 );
986 if ( !selectedIndexes.isEmpty() )
987 {
988 const auto constSelectedIndexes = selectedIndexes;
989 for ( const QModelIndex &idx : constSelectedIndexes )
990 {
991 if ( idx.isValid() )
992 {
993 int rangeIdx = idx.row();
994 QgsSymbol *newRangeSymbol = mGraduatedSymbol->clone();
995 if ( selectedIndexes.count() > 1 )
996 {
997 //if updating multiple ranges, retain the existing range colors
998 newRangeSymbol->setColor( mRenderer->ranges().at( rangeIdx ).symbol()->color() );
999 }
1000 mRenderer->updateRangeSymbol( rangeIdx, newRangeSymbol );
1001 }
1002 }
1003 }
1004 else
1005 {
1006 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1007 }
1008
1010 emit widgetChanged();
1011}
1012
1013void QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished( )
1014{
1015 const QString text = cboSymmetryPoint->lineEdit()->text();
1016 int index = cboSymmetryPoint->findText( text );
1017 if ( index != -1 )
1018 {
1019 cboSymmetryPoint->setCurrentIndex( index );
1020 }
1021 else
1022 {
1023 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), text );
1025 }
1026}
1027
1028
1030{
1031 mUpdateTimer.start( 500 );
1032}
1033
1034void QgsGraduatedSymbolRendererWidget::classifyGraduatedImpl( )
1035{
1036
1037 if ( mBlockUpdates )
1038 return;
1039
1040 QgsTemporaryCursorOverride override( Qt::WaitCursor );
1041 QString attrName = mExpressionWidget->currentField();
1042 int nclasses = spinGraduatedClasses->value();
1043
1044 const QString methodId = cboGraduatedMode->currentData().toString();
1046 Q_ASSERT( method );
1047
1048 int attrNum = mLayer->fields().lookupField( attrName );
1049
1050 QVariant minVal;
1051 QVariant maxVal;
1052 mLayer->minimumAndMaximumValue( attrNum, minVal, maxVal );
1053
1054 double minimum = minVal.toDouble();
1055 double maximum = maxVal.toDouble();
1056 mSymmetryPointValidator->setBottom( minimum );
1057 mSymmetryPointValidator->setTop( maximum );
1058 mSymmetryPointValidator->setMaxDecimals( spinPrecision->value() );
1059
1062 {
1063 // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
1064 // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
1065 double currentValue = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1066 if ( currentValue < ( minimum + ( maximum - minimum ) / 100. ) || currentValue > ( maximum - ( maximum - minimum ) / 100. ) )
1067 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( minimum + ( maximum - minimum ) / 2., 'f', method->labelPrecision() + 2 ) );
1068 }
1069
1070 if ( mGroupBoxSymmetric->isChecked() )
1071 {
1072 double symmetryPoint = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1073 bool astride = cbxAstride->isChecked();
1074 method->setSymmetricMode( true, symmetryPoint, astride );
1075 }
1076
1077 QVariantMap parameterValues;
1078 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
1079 parameterValues.insert( ppww->parameterDefinition()->name(), ppww->parameterValue() );
1080 method->setParameterValues( parameterValues );
1081
1082 // set method to renderer
1083 mRenderer->setClassificationMethod( method );
1084
1085 // create and set new renderer
1086 mRenderer->setClassAttribute( attrName );
1087
1088 // If complexity >= oN^2, warn for big dataset (more than 50k records)
1089 // and give the user the chance to cancel
1090 if ( method->codeComplexity() > 1 && mLayer->featureCount() > 50000 )
1091 {
1092 if ( QMessageBox::Cancel == QMessageBox::question( this, tr( "Apply Classification" ), tr( "Natural break classification (Jenks) is O(n2) complexity, your classification may take a long time.\nPress cancel to abort breaks calculation or OK to continue." ), QMessageBox::Cancel, QMessageBox::Ok ) )
1093 {
1094 return;
1095 }
1096 }
1097
1098 if ( methodComboBox->currentData() == ColorMode )
1099 {
1100 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1101 if ( !ramp )
1102 {
1103 QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
1104 return;
1105 }
1106 mRenderer->setSourceColorRamp( ramp.release() );
1107 }
1108 else
1109 {
1110 mRenderer->setSourceColorRamp( nullptr );
1111 }
1112
1113 mRenderer->updateClasses( mLayer, nclasses );
1114
1115 if ( methodComboBox->currentData() == SizeMode )
1116 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1117
1118 mRenderer->calculateLabelPrecision();
1119 // PrettyBreaks and StdDev calculation don't generate exact
1120 // number of classes - leave user interface unchanged for these
1121 updateUiFromRenderer( false );
1122}
1123
1125{
1126 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
1127 if ( !ramp )
1128 return;
1129
1130 mRenderer->updateColorRamp( ramp.release() );
1131 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1133}
1134
1136{
1137 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1138 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1140}
1141
1142#if 0
1143int QgsRendererPropertiesDialog::currentRangeRow()
1144{
1145 QModelIndex idx = viewGraduated->selectionModel()->currentIndex();
1146 if ( !idx.isValid() )
1147 return -1;
1148 return idx.row();
1149}
1150#endif
1151
1153{
1154 QList<int> rows;
1155 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1156
1157 const auto constSelectedRows = selectedRows;
1158 for ( const QModelIndex &r : constSelectedRows )
1159 {
1160 if ( r.isValid() )
1161 {
1162 rows.append( r.row() );
1163 }
1164 }
1165 return rows;
1166}
1167
1169{
1171 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1172 QModelIndexList::const_iterator sIt = selectedRows.constBegin();
1173
1174 for ( ; sIt != selectedRows.constEnd(); ++sIt )
1175 {
1176 selectedRanges.append( mModel->rendererRange( *sIt ) );
1177 }
1178 return selectedRanges;
1179}
1180
1182{
1183 if ( idx.isValid() && idx.column() == 0 )
1184 changeRangeSymbol( idx.row() );
1185 if ( idx.isValid() && idx.column() == 1 )
1186 changeRange( idx.row() );
1187}
1188
1190{
1191 if ( !idx.isValid() )
1192 mRowSelected = -1;
1193 else
1194 mRowSelected = idx.row();
1195}
1196
1198{
1199}
1200
1202{
1203 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1204 std::unique_ptr< QgsSymbol > newSymbol( range.symbol()->clone() );
1206 if ( panel && panel->dockMode() )
1207 {
1208 // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the
1209 // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector
1210 QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
1211 dlg->setContext( mContext );
1212 dlg->setPanelTitle( range.label() );
1213 connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget );
1214 connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector );
1215 openPanel( dlg );
1216 }
1217 else
1218 {
1219 QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1220 dlg.setContext( mContext );
1221 if ( !dlg.exec() || !newSymbol )
1222 {
1223 return;
1224 }
1225
1226 mGraduatedSymbol = std::move( newSymbol );
1227 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
1229 }
1230}
1231
1233{
1234 QgsLUDialog dialog( this );
1235
1236 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1237 // Add arbitrary 2 to number of decimal places to retain a bit extra.
1238 // Ensures users can see if legend is not completely honest!
1239 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1240 if ( decimalPlaces < 0 ) decimalPlaces = 0;
1241 dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
1242 dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
1243
1244 if ( dialog.exec() == QDialog::Accepted )
1245 {
1246 mRenderer->updateRangeUpperValue( rangeIdx, dialog.upperValueDouble() );
1247 mRenderer->updateRangeLowerValue( rangeIdx, dialog.lowerValueDouble() );
1248
1249 //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1250 if ( cbxLinkBoundaries->isChecked() )
1251 {
1252 if ( rangeIdx > 0 )
1253 {
1254 mRenderer->updateRangeUpperValue( rangeIdx - 1, dialog.lowerValueDouble() );
1255 }
1256
1257 if ( rangeIdx < mRenderer->ranges().size() - 1 )
1258 {
1259 mRenderer->updateRangeLowerValue( rangeIdx + 1, dialog.upperValueDouble() );
1260 }
1261 }
1262 }
1263 mHistogramWidget->refresh();
1264 emit widgetChanged();
1265}
1266
1268{
1269 mModel->addClass( mGraduatedSymbol.get() );
1270 mHistogramWidget->refresh();
1271 emit widgetChanged();
1272
1273}
1274
1276{
1277 QList<int> classIndexes = selectedClasses();
1278 mModel->deleteRows( classIndexes );
1279 mHistogramWidget->refresh();
1280 emit widgetChanged();
1281}
1282
1284{
1285 mModel->removeAllRows();
1286 mHistogramWidget->refresh();
1287 emit widgetChanged();
1288}
1289
1291{
1292 const QgsRangeList &ranges = mRenderer->ranges();
1293 bool ordered = true;
1294 for ( int i = 1; i < ranges.size(); ++i )
1295 {
1296 if ( ranges[i] < ranges[i - 1] )
1297 {
1298 ordered = false;
1299 break;
1300 }
1301 }
1302 return ordered;
1303}
1304
1306{
1307 //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1308 //This is done by updating all lower ranges to the upper value of the range above
1309 if ( linked )
1310 {
1311 if ( ! rowsOrdered() )
1312 {
1313 int result = QMessageBox::warning(
1314 this,
1315 tr( "Link Class Boundaries" ),
1316 tr( "Rows will be reordered before linking boundaries. Continue?" ),
1317 QMessageBox::Ok | QMessageBox::Cancel );
1318 if ( result != QMessageBox::Ok )
1319 {
1320 cbxLinkBoundaries->setChecked( false );
1321 return;
1322 }
1323 mRenderer->sortByValue();
1324 }
1325
1326 // Ok to proceed
1327 for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1328 {
1329 mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1330 }
1332 }
1333}
1334
1336{
1337 if ( item->column() == 2 )
1338 {
1339 QString label = item->text();
1340 int idx = item->row();
1341 mRenderer->updateRangeLabel( idx, label );
1342 }
1343}
1344
1346{
1347 mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
1348 mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
1349 mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
1350 mRenderer->updateRangeLabels();
1351 mModel->updateLabels();
1352}
1353
1354
1356{
1357 QList<QgsSymbol *> selectedSymbols;
1358
1359 QItemSelectionModel *m = viewGraduated->selectionModel();
1360 QModelIndexList selectedIndexes = m->selectedRows( 1 );
1361 if ( !selectedIndexes.isEmpty() )
1362 {
1363 const QgsRangeList &ranges = mRenderer->ranges();
1364 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1365 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1366 {
1367 QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1368 if ( list.size() < 3 )
1369 {
1370 continue;
1371 }
1372 // Not strictly necessary because the range should have been sanitized already
1373 // after user input, but being permissive never hurts
1374 bool ok = false;
1375 double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1376 if ( ! ok )
1377 lowerBound = 0.0;
1378 double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1379 if ( ! ok )
1380 upperBound = 0.0;
1381 QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1382 if ( s )
1383 {
1384 selectedSymbols.append( s );
1385 }
1386 }
1387 }
1388 return selectedSymbols;
1389}
1390
1391QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1392{
1393 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1394 if ( decimalPlaces < 0 )
1395 decimalPlaces = 0;
1396 double precision = 1.0 / std::pow( 10, decimalPlaces );
1397
1398 for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1399 {
1400 if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1401 {
1402 return it->symbol();
1403 }
1404 }
1405 return nullptr;
1406}
1407
1409{
1410 if ( mModel )
1411 {
1412 mModel->updateSymbology();
1413 }
1414 mHistogramWidget->refresh();
1415 emit widgetChanged();
1416}
1417
1419{
1420 showSymbolLevelsDialog( mRenderer.get() );
1421}
1422
1424{
1425 viewGraduated->selectionModel()->clear();
1426 if ( ! rowsOrdered() )
1427 {
1428 cbxLinkBoundaries->setChecked( false );
1429 }
1430 emit widgetChanged();
1431}
1432
1434{
1435 emit widgetChanged();
1436}
1437
1439{
1440 if ( !event )
1441 {
1442 return;
1443 }
1444
1445 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1446 {
1447 mCopyBuffer.clear();
1448 mCopyBuffer = selectedRanges();
1449 }
1450 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1451 {
1452 QgsRangeList::const_iterator rIt = mCopyBuffer.constBegin();
1453 for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1454 {
1455 mModel->addClass( *rIt );
1456 }
1457 emit widgetChanged();
1458 }
1459}
1460
1461void QgsGraduatedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1462{
1463 const QgsRangeList ranges = selectedRanges();
1464 if ( !ranges.isEmpty() )
1465 {
1466 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( ranges.at( 0 ).symbol()->clone() );
1467 }
1468 else if ( mRenderer->sourceSymbol() )
1469 {
1470 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1471 }
1472 btnChangeGraduatedSymbol->setDialogTitle( ranges.size() == 1 ? ranges.at( 0 ).label() : tr( "Symbol Settings" ) );
1473}
1474
1475void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1476{
1477 QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1478 QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1479 if ( panel )
1480 {
1481 connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1482 {
1483 mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1484 emit widgetChanged();
1485 } );
1486 openPanel( panel ); // takes ownership of the panel
1487 }
1488}
1489
1490void QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol()
1491{
1492 mGraduatedSymbol.reset( btnChangeGraduatedSymbol->symbol()->clone() );
1494}
1495
1497{
1498 std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1499 if ( !tempSymbol )
1500 return;
1501
1502 const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1503 for ( const QModelIndex &index : selectedRows )
1504 {
1505 if ( !index.isValid() )
1506 continue;
1507
1508 const int row = index.row();
1509 if ( !mRenderer || mRenderer->ranges().size() <= row )
1510 continue;
1511
1512 if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
1513 continue;
1514
1515 std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
1516 if ( selectedRows.count() > 1 )
1517 {
1518 //if updating multiple ranges, retain the existing category colors
1519 newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
1520 }
1521
1522 mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
1523 }
1524 emit widgetChanged();
1525}
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ Hybrid
Hybrid symbol.
A widget wrapper for Processing parameter value widgets.
QVariant parameterValue() const
Returns the current value of the parameter.
QLabel * createWrappedLabel()
Creates and returns a new label to accompany widgets created by the wrapper.
QWidget * createWrappedWidget(QgsProcessingContext &context)
Creates and return a new wrapped widget which allows customization of the parameter's value.
void widgetValueHasChanged(QgsAbstractProcessingParameterWidgetWrapper *wrapper)
Emitted whenever the parameter value (as defined by the wrapped widget) is changed.
const QgsProcessingParameterDefinition * parameterDefinition() const
Returns the parameter definition associated with this wrapper.
void setParameterValue(const QVariant &value, QgsProcessingContext &context)
Sets the current value for the parameter.
static QgsClassificationMethodRegistry * classificationMethodRegistry()
Returns the application's classification methods registry, used in graduated renderer.
QgsClassificationMethod * method(const QString &id)
Returns a new instance of the method for the given id.
QIcon icon(const QString &id) const
Returns the icon for a given method id.
QMap< QString, QString > methodNames() const
Returns a map <name, id> of all registered methods.
QgsClassificationMethod is an abstract class for implementations of classification methods.
double symmetryPoint() const
Returns the symmetry point for symmetric mode.
int codeComplexity() const
Code complexity as the exponent in Big O notation.
bool symmetricModeEnabled() const
Returns if the symmetric mode is enabled.
int labelPrecision() const
Returns the precision for the formatting of the labels.
virtual QString id() const =0
The id of the method as saved in the project, must be unique in registry.
QVariantMap parameterValues() const
Returns the values of the processing parameters.
void setSymmetricMode(bool enabled, double symmetryPoint=0, bool symmetryAstride=false)
Defines if the symmetric mode is enables and configures its parameters.
bool symmetryAstride() const
Returns if the symmetric mode is astride if true, it will remove the symmetry point break so that the...
QString labelFormat() const
Returns the format of the label for the classes.
void setParameterValues(const QVariantMap &values)
Defines the values of the additional parameters.
bool labelTrimTrailingZeroes() const
Returns if the trailing 0 are trimmed in the label.
QgsProcessingParameterDefinitions parameterDefinitions() const
Returns the list of parameters.
bool symmetricModeAvailable() const
Returns if the method supports symmetric calculation.
QgsClassificationMethod::MethodProperties flags() const
Returns the classification flags.
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
Abstract base class for color ramps.
Definition: qgscolorramp.h:30
Widget for configuration of appearance of legend for marker symbols with data-defined size.
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration as set up in the dialog (may be nullptr). Ownership is passed to the caller.
QgsDoubleValidator is a QLineEdit Validator that combines QDoubleValidator and QRegularExpressionVali...
static double toDouble(const QString &input, bool *ok)
Converts input string to double value.
void setTop(double top)
Set top range limit.
void setMaxDecimals(int maxDecimals)
Sets the number of decimals accepted by the validator to maxDecimals.
void setBottom(double bottom)
Set top range limit.
Abstract interface for generating an expression context scope.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
Definition: qgsrenderer.cpp:46
The QgsFieldExpressionWidget class reates a widget to choose fields and edit expressions It contains ...
void fieldChanged(const QString &fieldName)
Emitted when the currently selected field changes.
@ Date
Date or datetime fields.
@ Numeric
All numeric fields.
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:359
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
void rangesModified(bool rangesAdded)
Emitted when the user modifies the graduated ranges using the histogram widget.
void deleteClasses()
Removes currently selected classes.
QList< QgsSymbol * > selectedSymbols() override
Subclasses may provide the capability of changing multiple symbols at once by implementing the follow...
void disableSymbolLevels() override
Disables symbol level modification on the widget.
static QgsRendererWidget * create(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
QgsGraduatedSymbolRendererWidget(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
void setContext(const QgsSymbolWidgetContext &context) override
Sets the context in which the renderer widget is shown, e.g., the associated map canvas and expressio...
void refreshRanges(bool reset)
Refreshes the ranges for the renderer.
QgsFeatureRenderer * renderer() override
Returns pointer to the renderer (no transfer of ownership)
void setSymbolLevels(const QgsLegendSymbolList &levels, bool enabled) override
Sets the symbol levels for the renderer defined in the widget.
QgsSymbol * findSymbolForRange(double lowerBound, double upperBound, const QgsRangeList &ranges) const
void deleteAllClasses()
Removes all classes from the classification.
void addClass()
Adds a class manually to the classification.
void toggleBoundariesLink(bool linked)
Toggle the link between classes boundaries.
void applyChangeToSymbol()
Applies current symbol to selected ranges, or to all ranges if none is selected.
QList< int > selectedClasses()
Returns a list of indexes for the classes under selection.
static QgsGraduatedSymbolRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer)
creates a QgsGraduatedSymbolRenderer from an existing renderer.
const QgsRangeList & ranges() const
Returns a list of all ranges used in the classification.
static QgsProcessingGuiRegistry * processingGuiRegistry()
Returns the global processing gui registry, used for registering the GUI behavior of processing algor...
Definition: qgsgui.cpp:138
void setSourceFieldExp(const QString &fieldOrExp)
Sets the source field or expression to use for values in the histogram.
double upperValueDouble() const
Returns the upper value.
Definition: qgsludialog.cpp:45
double lowerValueDouble() const
Returns the lower value.
Definition: qgsludialog.cpp:35
void setLowerValue(const QString &val)
Definition: qgsludialog.cpp:50
void setUpperValue(const QString &val)
Definition: qgsludialog.cpp:61
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
The QgsMapSettings class contains configuration for rendering of the map.
A marker symbol type, for rendering Point and MultiPoint geometries.
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void widgetChanged()
Emitted when the widget state changes.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
bool dockMode()
Returns the dock mode state.
Contains information about the context in which a processing algorithm is executed.
QgsAbstractProcessingParameterWidgetWrapper * createParameterWidgetWrapper(const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type)
Creates a new parameter widget wrapper for the given parameter.
@ Standard
Standard algorithm dialog.
Base class for the definition of processing parameters.
QVariant defaultValueForGui() const
Returns the default value to use for the parameter in a GUI.
QString name() const
Returns the name of the parameter.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:477
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
Definition: qgsproxystyle.h:31
QString label() const
Returns the label used for the range.
QgsSymbol * symbol() const
Returns the symbol used for the range.
bool renderState() const
Returns true if the range should be rendered.
double upperValue() const
Returns the upper bound of the range.
double lowerValue() const
Returns the lower bound of the range.
Base class for renderer settings widgets.
void showSymbolLevelsDialog(QgsFeatureRenderer *r)
Show a dialog with renderer's symbol level settings.
QgsSymbolWidgetContext mContext
Context in which widget is shown.
virtual void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the renderer widget is shown, e.g., the associated map canvas and expressio...
QgsDataDefinedSizeLegendWidget * createDataDefinedSizeLegendWidget(const QgsMarkerSymbol *symbol, const QgsDataDefinedSizeLegend *ddsLegend)
Creates widget to setup data-defined size legend.
void contextMenuViewCategories(QPoint p)
const QgsVectorLayer * vectorLayer() const
Returns the vector layer associated with the widget.
QgsSymbolWidgetContext context() const
Returns the context in which the renderer widget is shown, e.g., the associated map canvas and expres...
QgsVectorLayer * mLayer
void changed()
Emitted when the symbol's settings are changed.
static QgsSymbol * symbolFromMimeData(const QMimeData *data)
Attempts to parse mime data as a symbol.
static QIcon symbolPreviewIcon(const QgsSymbol *symbol, QSize size, int padding=0, QgsLegendPatchShape *shape=nullptr)
Returns an icon preview for a color ramp.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
Symbol selector widget that can be used to select and build a symbol.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
QgsSymbol * symbol()
Returns the symbol that is currently active in the widget.
Contains settings which reflect the context in which a symbol (or renderer) widget is shown,...
QList< QgsExpressionContextScope > additionalExpressionContextScopes() const
Returns the list of additional expression context scopes to show as available within the layer.
QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
QgsMessageBar * messageBar() const
Returns the message bar associated with the widget.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
void setColor(const QColor &color) const
Sets the color for the symbol.
Definition: qgssymbol.cpp:898
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:215
static QgsSymbol * defaultSymbol(Qgis::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Definition: qgssymbol.cpp:704
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
Definition: qgsguiutils.h:221
Represents a vector layer which manages a vector based data sets.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
void minimumAndMaximumValue(int index, QVariant &minimum, QVariant &maximum) const
Calculates both the minimum and maximum value for an attribute column.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
double qgsPermissiveToDouble(QString string, bool &ok)
Converts a string to a double in a permissive way, e.g., allowing for incorrect numbers of digits bet...
Definition: qgis.cpp:71
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3509
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:3435
const QgsField & field
Definition: qgsfield.h:501
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsRendererRange > QgsRangeList
int precision