QGIS API Documentation 3.43.0-Master (3ee7834ace6)
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#include <QPointer>
26#include <QScreen>
27
29#include "moc_qgsgraduatedsymbolrendererwidget.cpp"
30#include "qgspanelwidget.h"
31
34#include "qgssymbol.h"
35#include "qgssymbollayerutils.h"
36#include "qgscolorrampimpl.h"
37#include "qgscolorrampbutton.h"
38#include "qgsstyle.h"
40#include "qgsvectorlayer.h"
42#include "qgslogger.h"
43#include "qgsludialog.h"
44#include "qgsproject.h"
46#include "qgsmapcanvas.h"
48#include "qgsapplication.h"
52#include "qgsgui.h"
53#include "qgsprocessinggui.h"
58#include "qgsdoublevalidator.h"
59#include "qgsmarkersymbol.h"
60
61
62// ------------------------------ Model ------------------------------------
63
65
66QgsGraduatedSymbolRendererModel::QgsGraduatedSymbolRendererModel( QObject *parent, QScreen *screen )
67 : QAbstractItemModel( parent )
68 , mMimeFormat( QStringLiteral( "application/x-qgsgraduatedsymbolrendererv2model" ) )
69 , mScreen( screen )
70{
71}
72
73void QgsGraduatedSymbolRendererModel::setRenderer( QgsGraduatedSymbolRenderer *renderer )
74{
75 if ( mRenderer )
76 {
77 if ( !mRenderer->ranges().isEmpty() )
78 {
79 beginRemoveRows( QModelIndex(), 0, mRenderer->ranges().size() - 1 );
80 mRenderer = nullptr;
81 endRemoveRows();
82 }
83 else
84 {
85 mRenderer = nullptr;
86 }
87 }
88 if ( renderer )
89 {
90 if ( !renderer->ranges().isEmpty() )
91 {
92 beginInsertRows( QModelIndex(), 0, renderer->ranges().size() - 1 );
93 mRenderer = renderer;
94 endInsertRows();
95 }
96 else
97 {
98 mRenderer = renderer;
99 }
100 }
101}
102
103void QgsGraduatedSymbolRendererModel::addClass( QgsSymbol *symbol )
104{
105 if ( !mRenderer )
106 return;
107 int idx = mRenderer->ranges().size();
108 beginInsertRows( QModelIndex(), idx, idx );
109 mRenderer->addClass( symbol );
110 endInsertRows();
111}
112
113void QgsGraduatedSymbolRendererModel::addClass( const QgsRendererRange &range )
114{
115 if ( !mRenderer )
116 {
117 return;
118 }
119 int idx = mRenderer->ranges().size();
120 beginInsertRows( QModelIndex(), idx, idx );
121 mRenderer->addClass( range );
122 endInsertRows();
123}
124
125QgsRendererRange QgsGraduatedSymbolRendererModel::rendererRange( const QModelIndex &index )
126{
127 if ( !index.isValid() || !mRenderer || mRenderer->ranges().size() <= index.row() )
128 {
129 return QgsRendererRange();
130 }
131
132 return mRenderer->ranges().value( index.row() );
133}
134
135Qt::ItemFlags QgsGraduatedSymbolRendererModel::flags( const QModelIndex &index ) const
136{
137 // Flat list, to ease drop handling valid indexes are not dropEnabled
138 if ( !index.isValid() )
139 {
140 return Qt::ItemIsDropEnabled;
141 }
142
143 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
144
145 if ( index.column() == 2 )
146 {
147 flags |= Qt::ItemIsEditable;
148 }
149
150 return flags;
151}
152
153Qt::DropActions QgsGraduatedSymbolRendererModel::supportedDropActions() const
154{
155 return Qt::MoveAction;
156}
157
158QVariant QgsGraduatedSymbolRendererModel::data( const QModelIndex &index, int role ) const
159{
160 if ( !index.isValid() || !mRenderer )
161 return QVariant();
162
163 const QgsRendererRange range = mRenderer->ranges().value( index.row() );
164
165 if ( role == Qt::CheckStateRole && index.column() == 0 )
166 {
167 return range.renderState() ? Qt::Checked : Qt::Unchecked;
168 }
169 else if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
170 {
171 switch ( index.column() )
172 {
173 case 1:
174 {
175 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
176 if ( decimalPlaces < 0 )
177 decimalPlaces = 0;
178 return QString( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) + " - " + QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
179 }
180 case 2:
181 return range.label();
182 default:
183 return QVariant();
184 }
185 }
186 else if ( role == Qt::DecorationRole && index.column() == 0 && range.symbol() )
187 {
188 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
189 return QgsSymbolLayerUtils::symbolPreviewIcon( range.symbol(), QSize( iconSize, iconSize ), 0, nullptr, QgsScreenProperties( mScreen.data() ) );
190 }
191 else if ( role == Qt::TextAlignmentRole )
192 {
193 return ( index.column() == 0 ) ? static_cast<Qt::Alignment::Int>( Qt::AlignHCenter ) : static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
194 }
195 else if ( role == Qt::EditRole )
196 {
197 switch ( index.column() )
198 {
199 // case 1: return rangeStr;
200 case 2:
201 return range.label();
202 default:
203 return QVariant();
204 }
205 }
206
207 return QVariant();
208}
209
210bool QgsGraduatedSymbolRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
211{
212 if ( !index.isValid() )
213 return false;
214
215 if ( index.column() == 0 && role == Qt::CheckStateRole )
216 {
217 mRenderer->updateRangeRenderState( index.row(), value == Qt::Checked );
218 emit dataChanged( index, index );
219 return true;
220 }
221
222 if ( role != Qt::EditRole )
223 return false;
224
225 switch ( index.column() )
226 {
227 case 1: // range
228 return false; // range is edited in popup dialog
229 case 2: // label
230 mRenderer->updateRangeLabel( index.row(), value.toString() );
231 break;
232 default:
233 return false;
234 }
235
236 emit dataChanged( index, index );
237 return true;
238}
239
240QVariant QgsGraduatedSymbolRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
241{
242 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
243 {
244 QStringList lst;
245 lst << tr( "Symbol" ) << tr( "Values" ) << tr( "Legend" );
246 return lst.value( section );
247 }
248 return QVariant();
249}
250
251int QgsGraduatedSymbolRendererModel::rowCount( const QModelIndex &parent ) const
252{
253 if ( parent.isValid() || !mRenderer )
254 {
255 return 0;
256 }
257 return mRenderer->ranges().size();
258}
259
260int QgsGraduatedSymbolRendererModel::columnCount( const QModelIndex &index ) const
261{
262 Q_UNUSED( index )
263 return 3;
264}
265
266QModelIndex QgsGraduatedSymbolRendererModel::index( int row, int column, const QModelIndex &parent ) const
267{
268 if ( hasIndex( row, column, parent ) )
269 {
270 return createIndex( row, column );
271 }
272 return QModelIndex();
273}
274
275QModelIndex QgsGraduatedSymbolRendererModel::parent( const QModelIndex &index ) const
276{
277 Q_UNUSED( index )
278 return QModelIndex();
279}
280
281QStringList QgsGraduatedSymbolRendererModel::mimeTypes() const
282{
283 QStringList types;
284 types << mMimeFormat;
285 return types;
286}
287
288QMimeData *QgsGraduatedSymbolRendererModel::mimeData( const QModelIndexList &indexes ) const
289{
290 QMimeData *mimeData = new QMimeData();
291 QByteArray encodedData;
292
293 QDataStream stream( &encodedData, QIODevice::WriteOnly );
294
295 // Create list of rows
296 const auto constIndexes = indexes;
297 for ( const QModelIndex &index : constIndexes )
298 {
299 if ( !index.isValid() || index.column() != 0 )
300 continue;
301
302 stream << index.row();
303 }
304 mimeData->setData( mMimeFormat, encodedData );
305 return mimeData;
306}
307
308bool QgsGraduatedSymbolRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
309{
310 Q_UNUSED( column )
311 Q_UNUSED( parent ) // Unused because only invalid indexes have Qt::ItemIsDropEnabled
312 if ( action != Qt::MoveAction )
313 return true;
314
315 if ( !data->hasFormat( mMimeFormat ) )
316 return false;
317
318 QByteArray encodedData = data->data( mMimeFormat );
319 QDataStream stream( &encodedData, QIODevice::ReadOnly );
320
321 QVector<int> rows;
322 while ( !stream.atEnd() )
323 {
324 int r;
325 stream >> r;
326 rows.append( r );
327 }
328
329 // Items may come unsorted depending on selecion order
330 std::sort( rows.begin(), rows.end() );
331
332 int to = row;
333
334 // to is -1 if dragged outside items, i.e. below any item,
335 // then move to the last position
336 if ( to == -1 )
337 to = mRenderer->ranges().size(); // out of rang ok, will be decreased
338 for ( int i = rows.size() - 1; i >= 0; i-- )
339 {
340 QgsDebugMsgLevel( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ), 2 );
341 int t = to;
342 // moveCategory first removes and then inserts
343 if ( rows[i] < t )
344 t--;
345 mRenderer->moveClass( rows[i], t );
346 // current moved under another, shift its index up
347 for ( int j = 0; j < i; j++ )
348 {
349 if ( to < rows[j] && rows[i] > rows[j] )
350 rows[j] += 1;
351 }
352 // removed under 'to' so the target shifted down
353 if ( rows[i] < to )
354 to--;
355 }
356 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
357 emit rowsMoved();
358 return false;
359}
360
361void QgsGraduatedSymbolRendererModel::deleteRows( QList<int> rows )
362{
363 for ( int i = rows.size() - 1; i >= 0; i-- )
364 {
365 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
366 mRenderer->deleteClass( rows[i] );
367 endRemoveRows();
368 }
369}
370
371void QgsGraduatedSymbolRendererModel::removeAllRows()
372{
373 beginRemoveRows( QModelIndex(), 0, mRenderer->ranges().size() - 1 );
374 mRenderer->deleteAllClasses();
375 endRemoveRows();
376}
377
378void QgsGraduatedSymbolRendererModel::sort( int column, Qt::SortOrder order )
379{
380 if ( column == 0 )
381 {
382 return;
383 }
384 if ( column == 1 )
385 {
386 mRenderer->sortByValue( order );
387 }
388 else if ( column == 2 )
389 {
390 mRenderer->sortByLabel( order );
391 }
392 emit rowsMoved();
393 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
394}
395
396void QgsGraduatedSymbolRendererModel::updateSymbology()
397{
398 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
399}
400
401void QgsGraduatedSymbolRendererModel::updateLabels()
402{
403 emit dataChanged( createIndex( 0, 2 ), createIndex( mRenderer->ranges().size(), 2 ) );
404}
405
406// ------------------------------ View style --------------------------------
407QgsGraduatedSymbolRendererViewStyle::QgsGraduatedSymbolRendererViewStyle( QWidget *parent )
408 : QgsProxyStyle( parent )
409{}
410
411void QgsGraduatedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
412{
413 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
414 {
415 QStyleOption opt( *option );
416 opt.rect.setLeft( 0 );
417 // draw always as line above, because we move item to that index
418 opt.rect.setHeight( 0 );
419 if ( widget )
420 opt.rect.setRight( widget->width() );
421 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
422 return;
423 }
424 QProxyStyle::drawPrimitive( element, option, painter, widget );
425}
426
428
429// ------------------------------ Widget ------------------------------------
430
435
437{
438 QgsExpressionContext expContext;
439
440 if ( auto *lMapCanvas = mContext.mapCanvas() )
441 {
442 expContext = lMapCanvas->createExpressionContext();
443 }
444 else
445 {
450 }
451
452 if ( auto *lVectorLayer = vectorLayer() )
453 expContext << QgsExpressionContextUtils::layerScope( lVectorLayer );
454
455 // additional scopes
456 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
457 for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
458 {
459 expContext.appendScope( new QgsExpressionContextScope( scope ) );
460 }
461
462 return expContext;
463}
464
466 : QgsRendererWidget( layer, style )
467{
468 // try to recognize the previous renderer
469 // (null renderer means "no previous renderer")
470 if ( renderer )
471 {
473 }
474 if ( !mRenderer )
475 {
476 mRenderer = std::make_unique<QgsGraduatedSymbolRenderer>( QString(), QgsRangeList() );
477 if ( renderer )
478 renderer->copyRendererData( mRenderer.get() );
479 }
480
481 // setup user interface
482 setupUi( this );
483
484 mSymmetryPointValidator = new QgsDoubleValidator( this );
485 cboSymmetryPoint->setEditable( true );
486 cboSymmetryPoint->setValidator( mSymmetryPointValidator );
487
488 const QMap<QString, QString> methods = QgsApplication::classificationMethodRegistry()->methodNames();
489 for ( QMap<QString, QString>::const_iterator it = methods.constBegin(); it != methods.constEnd(); ++it )
490 {
491 QIcon icon = QgsApplication::classificationMethodRegistry()->icon( it.value() );
492 cboGraduatedMode->addItem( icon, it.key(), it.value() );
493 }
494
495 connect( methodComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged );
496 this->layout()->setContentsMargins( 0, 0, 0, 0 );
497
498 mModel = new QgsGraduatedSymbolRendererModel( this, screen() );
499
500 mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
501 mExpressionWidget->setLayer( mLayer );
502
503 btnChangeGraduatedSymbol->setLayer( mLayer );
504 btnChangeGraduatedSymbol->registerExpressionContextGenerator( this );
505
506 mSizeUnitWidget->setUnits(
512 }
513 );
514
515 spinPrecision->setMinimum( QgsClassificationMethod::MIN_PRECISION );
516 spinPrecision->setMaximum( QgsClassificationMethod::MAX_PRECISION );
517 spinPrecision->setClearValue( 4 );
518
519 spinGraduatedClasses->setShowClearButton( false );
520
521 btnColorRamp->setShowRandomColorRamp( true );
522
523 // set project default color ramp
524 std::unique_ptr<QgsColorRamp> colorRamp( QgsProject::instance()->styleSettings()->defaultColorRamp() );
525 if ( colorRamp )
526 {
527 btnColorRamp->setColorRamp( colorRamp.get() );
528 }
529 else
530 {
531 QgsColorRamp *ramp = new QgsGradientColorRamp( QColor( 255, 255, 255 ), QColor( 255, 0, 0 ) );
532 btnColorRamp->setColorRamp( ramp );
533 delete ramp;
534 }
535
536
537 viewGraduated->setStyle( new QgsGraduatedSymbolRendererViewStyle( viewGraduated ) );
538
539 mGraduatedSymbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
540 if ( mGraduatedSymbol )
541 {
542 btnChangeGraduatedSymbol->setSymbolType( mGraduatedSymbol->type() );
543 btnChangeGraduatedSymbol->setSymbol( mGraduatedSymbol->clone() );
544
545 methodComboBox->blockSignals( true );
546 methodComboBox->addItem( tr( "Color" ), ColorMode );
547 switch ( mGraduatedSymbol->type() )
548 {
550 {
551 methodComboBox->addItem( tr( "Size" ), SizeMode );
552 minSizeSpinBox->setValue( 1 );
553 maxSizeSpinBox->setValue( 8 );
554 break;
555 }
557 {
558 methodComboBox->addItem( tr( "Size" ), SizeMode );
559 minSizeSpinBox->setValue( .1 );
560 maxSizeSpinBox->setValue( 2 );
561 break;
562 }
564 {
565 //set button and label invisible to avoid display of a single item combobox
566 methodComboBox->hide();
567 labelMethod->hide();
568 break;
569 }
571 break;
572 }
573 methodComboBox->blockSignals( false );
574 }
575
576 connect( mExpressionWidget, static_cast<void ( QgsFieldExpressionWidget::* )( const QString & )>( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsGraduatedSymbolRendererWidget::graduatedColumnChanged );
577 connect( viewGraduated, &QAbstractItemView::doubleClicked, this, &QgsGraduatedSymbolRendererWidget::rangesDoubleClicked );
578 connect( viewGraduated, &QAbstractItemView::clicked, this, &QgsGraduatedSymbolRendererWidget::rangesClicked );
579 connect( viewGraduated, &QTreeView::customContextMenuRequested, this, &QgsGraduatedSymbolRendererWidget::contextMenuViewCategories );
580
581 connect( btnGraduatedClassify, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
582 connect( btnChangeGraduatedSymbol, &QgsSymbolButton::changed, this, &QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol );
583 connect( btnGraduatedDelete, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteClasses );
584 connect( btnDeleteAllClasses, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteAllClasses );
585 connect( btnGraduatedAdd, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::addClass );
586 connect( cbxLinkBoundaries, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::toggleBoundariesLink );
587 connect( mSizeUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed );
588
589 connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::updateMethodParameters );
590
591 // need to update widget according to current graduated mode
592 updateMethodParameters();
593
595
596 // initialize from previously set renderer
598
599 // default to collapsed symmetric group for ui simplicity
600 mGroupBoxSymmetric->setCollapsed( true ); //
601
602 // menus for data-defined rotation/size
603 QMenu *advMenu = new QMenu( this );
604
605 mActionLevels = advMenu->addAction( tr( "Symbol Levels…" ), this, &QgsGraduatedSymbolRendererWidget::showSymbolLevels );
606 if ( mGraduatedSymbol && mGraduatedSymbol->type() == Qgis::SymbolType::Marker )
607 {
608 QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
609 // only from Qt 5.6 there is convenience addAction() with new style connection
610 connect( actionDdsLegend, &QAction::triggered, this, &QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend );
611 }
612
613 btnAdvanced->setMenu( advMenu );
614
615 mHistogramWidget->setLayer( mLayer );
616 mHistogramWidget->setRenderer( mRenderer.get() );
618 connect( mExpressionWidget, static_cast<void ( QgsFieldExpressionWidget::* )( const QString & )>( &QgsFieldExpressionWidget::fieldChanged ), mHistogramWidget, &QgsHistogramWidget::setSourceFieldExp );
619
620 mExpressionWidget->registerExpressionContextGenerator( this );
621
622 mUpdateTimer.setSingleShot( true );
623 mUpdateTimer.connect( &mUpdateTimer, &QTimer::timeout, this, &QgsGraduatedSymbolRendererWidget::classifyGraduatedImpl );
624}
625
626void QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed()
627{
628 if ( !mGraduatedSymbol )
629 return;
630 mGraduatedSymbol->setOutputUnit( mSizeUnitWidget->unit() );
631 mGraduatedSymbol->setMapUnitScale( mSizeUnitWidget->getMapUnitScale() );
632 mRenderer->updateSymbols( mGraduatedSymbol.get() );
634}
635
637{
638 delete mModel;
639 mParameterWidgetWrappers.clear();
640}
641
643{
644 return mRenderer.get();
645}
646
648{
650 btnChangeGraduatedSymbol->setMapCanvas( context.mapCanvas() );
651 btnChangeGraduatedSymbol->setMessageBar( context.messageBar() );
652}
653
655{
656 delete mActionLevels;
657 mActionLevels = nullptr;
658}
659
660// Connect/disconnect event handlers which trigger updating renderer
662{
663 connect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
664 connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
666 connect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
667 connect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
668 connect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
669 connect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
670 connect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
671
672 connect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
673 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
674
675 connect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
676 connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
677 connect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
678 connect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
679
680 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
681 {
683 }
684}
685
686// Connect/disconnect event handlers which trigger updating renderer
688{
689 disconnect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
690 disconnect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
692 disconnect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
693 disconnect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
694 disconnect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
695 disconnect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
696 disconnect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
697
698 disconnect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
699 disconnect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
700
701 disconnect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
702 disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
703 disconnect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
704 disconnect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
705
706 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
707 {
709 }
710}
711
713{
715 mBlockUpdates++;
716
717 const QgsClassificationMethod *method = mRenderer->classificationMethod();
718
719 const QgsRangeList ranges = mRenderer->ranges();
720
721 // use the breaks for symmetry point
722 int precision = spinPrecision->value() + 2;
723 while ( cboSymmetryPoint->count() )
724 cboSymmetryPoint->removeItem( 0 );
725 for ( int i = 0; i < ranges.count() - 1; i++ )
726 cboSymmetryPoint->addItem( QLocale().toString( ranges.at( i ).upperValue(), 'f', precision ), ranges.at( i ).upperValue() );
727
728 if ( method )
729 {
730 int idx = cboGraduatedMode->findData( method->id() );
731 if ( idx >= 0 )
732 cboGraduatedMode->setCurrentIndex( idx );
733
734 mGroupBoxSymmetric->setVisible( method->symmetricModeAvailable() );
735 mGroupBoxSymmetric->setChecked( method->symmetricModeEnabled() );
736 cbxAstride->setChecked( method->symmetryAstride() );
737 if ( method->symmetricModeEnabled() )
738 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( method->symmetryPoint(), 'f', method->labelPrecision() + 2 ) );
739
740 txtLegendFormat->setText( method->labelFormat() );
741 spinPrecision->setValue( method->labelPrecision() );
742 cbxTrimTrailingZeroes->setChecked( method->labelTrimTrailingZeroes() );
743
745 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
746 {
747 const QgsProcessingParameterDefinition *def = ppww->parameterDefinition();
748 QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
749 ppww->setParameterValue( value, context );
750 }
751 }
752
753 // Only update class count if different - otherwise typing value gets very messy
754 int nclasses = ranges.count();
755 if ( nclasses && ( updateCount || ( method && ( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) ) ) )
756 {
757 spinGraduatedClasses->setValue( ranges.count() );
758 }
759 if ( method )
760 {
761 spinGraduatedClasses->setEnabled( !( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) );
762 }
763 else
764 {
765 spinGraduatedClasses->setEnabled( true );
766 }
767
768 // set column
769 QString attrName = mRenderer->classAttribute();
770 mExpressionWidget->setField( attrName );
771 mHistogramWidget->setSourceFieldExp( attrName );
772
773 // set source symbol
774 if ( mRenderer->sourceSymbol() )
775 {
776 mGraduatedSymbol.reset( mRenderer->sourceSymbol()->clone() );
777 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
778 }
779
780 mModel->setRenderer( mRenderer.get() );
781 viewGraduated->setModel( mModel );
782
783 connect( viewGraduated->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsGraduatedSymbolRendererWidget::selectionChanged );
784
785 if ( mGraduatedSymbol )
786 {
787 mSizeUnitWidget->blockSignals( true );
788 mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
789 mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
790 mSizeUnitWidget->blockSignals( false );
791 }
792
793 // set source color ramp
794 methodComboBox->blockSignals( true );
795 switch ( mRenderer->graduatedMethod() )
796 {
798 {
799 methodComboBox->setCurrentIndex( methodComboBox->findData( ColorMode ) );
800 if ( mRenderer->sourceColorRamp() )
801 {
802 btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
803 }
804 break;
805 }
807 {
808 methodComboBox->setCurrentIndex( methodComboBox->findData( SizeMode ) );
809 if ( !mRenderer->ranges().isEmpty() ) // avoid overriding default size with zeros
810 {
811 minSizeSpinBox->setValue( mRenderer->minSymbolSize() );
812 maxSizeSpinBox->setValue( mRenderer->maxSymbolSize() );
813 }
814 break;
815 }
816 }
817 toggleMethodWidgets( static_cast<MethodMode>( methodComboBox->currentData().toInt() ) );
818 methodComboBox->blockSignals( false );
819
820 viewGraduated->resizeColumnToContents( 0 );
821 viewGraduated->resizeColumnToContents( 1 );
822 viewGraduated->resizeColumnToContents( 2 );
823
824 mHistogramWidget->refresh();
825
827 mBlockUpdates--;
828
829 emit widgetChanged();
830}
831
833{
834 mRenderer->setClassAttribute( field );
835}
836
837void QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged( int )
838{
839 const MethodMode newMethod = static_cast<MethodMode>( methodComboBox->currentData().toInt() );
840 toggleMethodWidgets( newMethod );
841 switch ( newMethod )
842 {
843 case ColorMode:
844 {
845 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Color );
846 QgsColorRamp *ramp = btnColorRamp->colorRamp();
847
848 if ( !ramp )
849 {
850 QMessageBox::critical( this, tr( "Select Method" ), tr( "No color ramp defined." ) );
851 return;
852 }
853 mRenderer->setSourceColorRamp( ramp );
855 break;
856 }
857
858 case SizeMode:
859 {
860 lblColorRamp->setVisible( false );
861 btnColorRamp->setVisible( false );
862 lblSize->setVisible( true );
863 minSizeSpinBox->setVisible( true );
864 lblSize->setVisible( true );
865 maxSizeSpinBox->setVisible( true );
866 mSizeUnitWidget->setVisible( true );
867
868 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Size );
869 reapplySizes();
870 break;
871 }
872 }
873}
874
875void QgsGraduatedSymbolRendererWidget::updateMethodParameters()
876{
877 clearParameterWidgets();
878
879 const QString methodId = cboGraduatedMode->currentData().toString();
880 mClassificationMethod = QgsApplication::classificationMethodRegistry()->method( methodId );
881 Q_ASSERT( mClassificationMethod.get() );
882
883 // need more context?
885
886 for ( const QgsProcessingParameterDefinition *def : mClassificationMethod->parameterDefinitions() )
887 {
889 mParametersLayout->addRow( ppww->createWrappedLabel(), ppww->createWrappedWidget( context ) );
890
891 QVariant value = mClassificationMethod->parameterValues().value( def->name(), def->defaultValueForGui() );
892 ppww->setParameterValue( value, context );
893
895
896 mParameterWidgetWrappers.push_back( std::unique_ptr<QgsAbstractProcessingParameterWidgetWrapper>( ppww ) );
897 }
898
899 spinGraduatedClasses->setEnabled( !( mClassificationMethod->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) );
900}
901
902void QgsGraduatedSymbolRendererWidget::toggleMethodWidgets( MethodMode mode )
903{
904 switch ( mode )
905 {
906 case ColorMode:
907 {
908 lblColorRamp->setVisible( true );
909 btnColorRamp->setVisible( true );
910 lblSize->setVisible( false );
911 minSizeSpinBox->setVisible( false );
912 lblSizeTo->setVisible( false );
913 maxSizeSpinBox->setVisible( false );
914 mSizeUnitWidget->setVisible( false );
915 break;
916 }
917
918 case SizeMode:
919 {
920 lblColorRamp->setVisible( false );
921 btnColorRamp->setVisible( false );
922 lblSize->setVisible( true );
923 minSizeSpinBox->setVisible( true );
924 lblSizeTo->setVisible( true );
925 maxSizeSpinBox->setVisible( true );
926 mSizeUnitWidget->setVisible( true );
927 break;
928 }
929 }
930}
931
932void QgsGraduatedSymbolRendererWidget::clearParameterWidgets()
933{
934 while ( mParametersLayout->rowCount() )
935 {
936 QFormLayout::TakeRowResult row = mParametersLayout->takeRow( 0 );
937 for ( QLayoutItem *item : { row.labelItem, row.fieldItem } )
938 if ( item )
939 {
940 QWidget *widget = item->widget();
941 delete item;
942 if ( widget )
943 delete widget;
944 }
945 }
946 mParameterWidgetWrappers.clear();
947}
948
950{
951 if ( !mModel )
952 return;
953
954 mModel->updateSymbology();
955
957 spinGraduatedClasses->setValue( mRenderer->ranges().count() );
959
960 emit widgetChanged();
961}
962
964{
965 for ( const QgsLegendSymbolItem &legendSymbol : levels )
966 {
967 QgsSymbol *sym = legendSymbol.symbol();
968 for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
969 {
970 mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
971 }
972 }
973 mRenderer->setUsingSymbolLevels( enabled );
974 mModel->updateSymbology();
975 emit widgetChanged();
976}
977
978void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget )
979{
980 mGraduatedSymbol.reset( widget->symbol()->clone() );
981
983}
984
986{
987 mSizeUnitWidget->blockSignals( true );
988 mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
989 mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
990 mSizeUnitWidget->blockSignals( false );
991
992 QItemSelectionModel *m = viewGraduated->selectionModel();
993 QModelIndexList selectedIndexes = m->selectedRows( 1 );
994 if ( !selectedIndexes.isEmpty() )
995 {
996 const auto constSelectedIndexes = selectedIndexes;
997 for ( const QModelIndex &idx : constSelectedIndexes )
998 {
999 if ( idx.isValid() )
1000 {
1001 int rangeIdx = idx.row();
1002 QgsSymbol *newRangeSymbol = mGraduatedSymbol->clone();
1003 if ( selectedIndexes.count() > 1 )
1004 {
1005 //if updating multiple ranges, retain the existing range colors
1006 newRangeSymbol->setColor( mRenderer->ranges().at( rangeIdx ).symbol()->color() );
1007 }
1008 mRenderer->updateRangeSymbol( rangeIdx, newRangeSymbol );
1009 }
1010 }
1011 }
1012 else
1013 {
1014 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1015 }
1016
1018 emit widgetChanged();
1019}
1020
1021void QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished()
1022{
1023 const QString text = cboSymmetryPoint->lineEdit()->text();
1024 int index = cboSymmetryPoint->findText( text );
1025 if ( index != -1 )
1026 {
1027 cboSymmetryPoint->setCurrentIndex( index );
1028 }
1029 else
1030 {
1031 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), text );
1033 }
1034}
1035
1036
1038{
1039 mUpdateTimer.start( 500 );
1040}
1041
1042void QgsGraduatedSymbolRendererWidget::classifyGraduatedImpl()
1043{
1044 if ( mBlockUpdates || !mClassificationMethod )
1045 return;
1046
1047 QgsTemporaryCursorOverride override( Qt::WaitCursor );
1048 QString attrName = mExpressionWidget->currentField();
1049 int nclasses = spinGraduatedClasses->value();
1050
1051 int attrNum = mLayer->fields().lookupField( attrName );
1052
1053 QVariant minVal;
1054 QVariant maxVal;
1055 mLayer->minimumAndMaximumValue( attrNum, minVal, maxVal );
1056
1057 double minimum = minVal.toDouble();
1058 double maximum = maxVal.toDouble();
1059 mSymmetryPointValidator->setBottom( minimum );
1060 mSymmetryPointValidator->setTop( maximum );
1061 mSymmetryPointValidator->setMaxDecimals( spinPrecision->value() );
1062
1063 if ( mClassificationMethod->id() == QgsClassificationEqualInterval::METHOD_ID || mClassificationMethod->id() == QgsClassificationStandardDeviation::METHOD_ID )
1064 {
1065 // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
1066 // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
1067 double currentValue = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1068 if ( currentValue < ( minimum + ( maximum - minimum ) / 100. ) || currentValue > ( maximum - ( maximum - minimum ) / 100. ) )
1069 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( minimum + ( maximum - minimum ) / 2., 'f', mClassificationMethod->labelPrecision() + 2 ) );
1070 }
1071
1072 if ( mGroupBoxSymmetric->isChecked() )
1073 {
1074 double symmetryPoint = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1075 bool astride = cbxAstride->isChecked();
1076 mClassificationMethod->setSymmetricMode( true, symmetryPoint, astride );
1077 }
1078
1079 QVariantMap parameterValues;
1080 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
1081 parameterValues.insert( ppww->parameterDefinition()->name(), ppww->parameterValue() );
1082 mClassificationMethod->setParameterValues( parameterValues );
1083
1084 // set method to renderer
1085 mRenderer->setClassificationMethod( mClassificationMethod->clone().release() );
1086
1087 // create and set new renderer
1088 mRenderer->setClassAttribute( attrName );
1089
1090 // If complexity >= oN^2, warn for big dataset (more than 50k records)
1091 // and give the user the chance to cancel
1092 if ( mRenderer->classificationMethod()->codeComplexity() > 1 && mLayer->featureCount() > 50000 )
1093 {
1094 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 ) )
1095 {
1096 return;
1097 }
1098 }
1099
1100 if ( methodComboBox->currentData() == ColorMode )
1101 {
1102 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1103 if ( !ramp )
1104 {
1105 QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
1106 return;
1107 }
1108 mRenderer->setSourceColorRamp( ramp.release() );
1109 }
1110 else
1111 {
1112 mRenderer->setSourceColorRamp( nullptr );
1113 }
1114
1115 QString error;
1116 mRenderer->updateClasses( mLayer, nclasses, error );
1117
1118 if ( !error.isEmpty() )
1119 QMessageBox::critical( this, tr( "Apply Classification" ), error );
1120
1121 if ( methodComboBox->currentData() == SizeMode )
1122 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1123
1124 mRenderer->calculateLabelPrecision();
1125 // PrettyBreaks and StdDev calculation don't generate exact
1126 // number of classes - leave user interface unchanged for these
1127 updateUiFromRenderer( false );
1128}
1129
1131{
1132 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1133 if ( !ramp )
1134 return;
1135
1136 mRenderer->updateColorRamp( ramp.release() );
1137 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1139}
1140
1142{
1143 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1144 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1146}
1147
1148#if 0
1149int QgsRendererPropertiesDialog::currentRangeRow()
1150{
1151 QModelIndex idx = viewGraduated->selectionModel()->currentIndex();
1152 if ( !idx.isValid() )
1153 return -1;
1154 return idx.row();
1155}
1156#endif
1157
1159{
1160 QList<int> rows;
1161 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1162
1163 const auto constSelectedRows = selectedRows;
1164 for ( const QModelIndex &r : constSelectedRows )
1165 {
1166 if ( r.isValid() )
1167 {
1168 rows.append( r.row() );
1169 }
1170 }
1171 return rows;
1172}
1173
1175{
1177 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1178 QModelIndexList::const_iterator sIt = selectedRows.constBegin();
1179
1180 for ( ; sIt != selectedRows.constEnd(); ++sIt )
1181 {
1182 selectedRanges.append( mModel->rendererRange( *sIt ) );
1183 }
1184 return selectedRanges;
1185}
1186
1188{
1189 if ( idx.isValid() && idx.column() == 0 )
1190 changeRangeSymbol( idx.row() );
1191 if ( idx.isValid() && idx.column() == 1 )
1192 changeRange( idx.row() );
1193}
1194
1196{
1197 if ( !idx.isValid() )
1198 mRowSelected = -1;
1199 else
1200 mRowSelected = idx.row();
1201}
1202
1206
1208{
1209 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1210 std::unique_ptr<QgsSymbol> newSymbol( range.symbol()->clone() );
1212 if ( panel && panel->dockMode() )
1213 {
1215 widget->setContext( mContext );
1216 widget->setPanelTitle( range.label() );
1217 connect( widget, &QgsPanelWidget::widgetChanged, this, [=] { updateSymbolsFromWidget( widget ); } );
1218 openPanel( widget );
1219 }
1220 else
1221 {
1222 QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1223 dlg.setContext( mContext );
1224 if ( !dlg.exec() || !newSymbol )
1225 {
1226 return;
1227 }
1228
1229 mGraduatedSymbol = std::move( newSymbol );
1230 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
1232 }
1233}
1234
1236{
1237 QgsLUDialog dialog( this );
1238
1239 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1240 // Add arbitrary 2 to number of decimal places to retain a bit extra.
1241 // Ensures users can see if legend is not completely honest!
1242 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1243 if ( decimalPlaces < 0 )
1244 decimalPlaces = 0;
1245 dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
1246 dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
1247
1248 if ( dialog.exec() == QDialog::Accepted )
1249 {
1250 mRenderer->updateRangeUpperValue( rangeIdx, dialog.upperValueDouble() );
1251 mRenderer->updateRangeLowerValue( rangeIdx, dialog.lowerValueDouble() );
1252
1253 //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1254 if ( cbxLinkBoundaries->isChecked() )
1255 {
1256 if ( rangeIdx > 0 )
1257 {
1258 mRenderer->updateRangeUpperValue( rangeIdx - 1, dialog.lowerValueDouble() );
1259 }
1260
1261 if ( rangeIdx < mRenderer->ranges().size() - 1 )
1262 {
1263 mRenderer->updateRangeLowerValue( rangeIdx + 1, dialog.upperValueDouble() );
1264 }
1265 }
1266 }
1267 mHistogramWidget->refresh();
1268 emit widgetChanged();
1269}
1270
1272{
1273 mModel->addClass( mGraduatedSymbol.get() );
1274 mHistogramWidget->refresh();
1275 emit widgetChanged();
1276}
1277
1279{
1280 QList<int> classIndexes = selectedClasses();
1281 mModel->deleteRows( classIndexes );
1282 mHistogramWidget->refresh();
1283 emit widgetChanged();
1284}
1285
1287{
1288 mModel->removeAllRows();
1289 mHistogramWidget->refresh();
1290 emit widgetChanged();
1291}
1292
1294{
1295 const QgsRangeList &ranges = mRenderer->ranges();
1296 bool ordered = true;
1297 for ( int i = 1; i < ranges.size(); ++i )
1298 {
1299 if ( ranges[i] < ranges[i - 1] )
1300 {
1301 ordered = false;
1302 break;
1303 }
1304 }
1305 return ordered;
1306}
1307
1309{
1310 //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1311 //This is done by updating all lower ranges to the upper value of the range above
1312 if ( linked )
1313 {
1314 if ( !rowsOrdered() )
1315 {
1316 int result = QMessageBox::warning(
1317 this,
1318 tr( "Link Class Boundaries" ),
1319 tr( "Rows will be reordered before linking boundaries. Continue?" ),
1320 QMessageBox::Ok | QMessageBox::Cancel
1321 );
1322 if ( result != QMessageBox::Ok )
1323 {
1324 cbxLinkBoundaries->setChecked( false );
1325 return;
1326 }
1327 mRenderer->sortByValue();
1328 }
1329
1330 // Ok to proceed
1331 for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1332 {
1333 mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1334 }
1336 }
1337}
1338
1340{
1341 if ( item->column() == 2 )
1342 {
1343 QString label = item->text();
1344 int idx = item->row();
1345 mRenderer->updateRangeLabel( idx, label );
1346 }
1347}
1348
1350{
1351 mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
1352 mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
1353 mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
1354 mRenderer->updateRangeLabels();
1355 mModel->updateLabels();
1356}
1357
1358
1360{
1361 QList<QgsSymbol *> selectedSymbols;
1362
1363 QItemSelectionModel *m = viewGraduated->selectionModel();
1364 QModelIndexList selectedIndexes = m->selectedRows( 1 );
1365 if ( !selectedIndexes.isEmpty() )
1366 {
1367 const QgsRangeList &ranges = mRenderer->ranges();
1368 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1369 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1370 {
1371 QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1372 if ( list.size() < 3 )
1373 {
1374 continue;
1375 }
1376 // Not strictly necessary because the range should have been sanitized already
1377 // after user input, but being permissive never hurts
1378 bool ok = false;
1379 double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1380 if ( !ok )
1381 lowerBound = 0.0;
1382 double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1383 if ( !ok )
1384 upperBound = 0.0;
1385 QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1386 if ( s )
1387 {
1388 selectedSymbols.append( s );
1389 }
1390 }
1391 }
1392 return selectedSymbols;
1393}
1394
1395QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1396{
1397 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1398 if ( decimalPlaces < 0 )
1399 decimalPlaces = 0;
1400 double precision = 1.0 / std::pow( 10, decimalPlaces );
1401
1402 for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1403 {
1404 if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1405 {
1406 return it->symbol();
1407 }
1408 }
1409 return nullptr;
1410}
1411
1413{
1414 if ( mModel )
1415 {
1416 mModel->updateSymbology();
1417 }
1418 mHistogramWidget->refresh();
1419 emit widgetChanged();
1420}
1421
1426
1428{
1429 viewGraduated->selectionModel()->clear();
1430 if ( !rowsOrdered() )
1431 {
1432 cbxLinkBoundaries->setChecked( false );
1433 }
1434 emit widgetChanged();
1435}
1436
1441
1443{
1444 if ( !event )
1445 {
1446 return;
1447 }
1448
1449 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1450 {
1451 mCopyBuffer.clear();
1452 mCopyBuffer = selectedRanges();
1453 }
1454 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1455 {
1456 QgsRangeList::const_iterator rIt = mCopyBuffer.constBegin();
1457 for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1458 {
1459 mModel->addClass( *rIt );
1460 }
1461 emit widgetChanged();
1462 }
1463}
1464
1465void QgsGraduatedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1466{
1467 const QgsRangeList ranges = selectedRanges();
1468 if ( !ranges.isEmpty() )
1469 {
1470 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( ranges.at( 0 ).symbol()->clone() );
1471 }
1472 else if ( mRenderer->sourceSymbol() )
1473 {
1474 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1475 }
1476 btnChangeGraduatedSymbol->setDialogTitle( ranges.size() == 1 ? ranges.at( 0 ).label() : tr( "Symbol Settings" ) );
1477}
1478
1479void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1480{
1481 QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1482 QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1483 if ( panel )
1484 {
1485 connect( panel, &QgsPanelWidget::widgetChanged, this, [=] {
1486 mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1487 emit widgetChanged();
1488 } );
1489 openPanel( panel ); // takes ownership of the panel
1490 }
1491}
1492
1493void QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol()
1494{
1495 mGraduatedSymbol.reset( btnChangeGraduatedSymbol->symbol()->clone() );
1497}
1498
1500{
1501 std::unique_ptr<QgsSymbol> tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1502 if ( !tempSymbol )
1503 return;
1504
1505 const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1506 for ( const QModelIndex &index : selectedRows )
1507 {
1508 if ( !index.isValid() )
1509 continue;
1510
1511 const int row = index.row();
1512 if ( !mRenderer || mRenderer->ranges().size() <= row )
1513 continue;
1514
1515 if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
1516 continue;
1517
1518 std::unique_ptr<QgsSymbol> newCatSymbol( tempSymbol->clone() );
1519 if ( selectedRows.count() > 1 )
1520 {
1521 //if updating multiple ranges, retain the existing category colors
1522 newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
1523 }
1524
1525 mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
1526 }
1527 emit widgetChanged();
1528}
@ Size
Alter size of symbols.
@ Color
Alter color of symbols.
@ Millimeters
Millimeters.
@ Points
Points (e.g., for font sizes)
@ MapUnits
Map units.
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ Hybrid
Hybrid symbol.
A widget wrapper for Processing parameter value widgets.
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.
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.
std::unique_ptr< 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.
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.
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.
bool labelTrimTrailingZeroes() const
Returns if the trailing 0 are trimmed in the label.
@ IgnoresClassCount
The classification method does not compute classes based on a class count.
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.
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.
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.
Abstract base class for all 2D vector feature renderers.
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
The QgsFieldExpressionWidget class creates 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.
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
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)
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
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.
A vector feature renderer which uses numeric attributes to classify features into different ranges.
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:155
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.
double lowerValueDouble() const
Returns the lower value.
void setLowerValue(const QString &val)
void setUpperValue(const QString &val)
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 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.
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
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
Stores properties relating to a screen.
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, const QgsScreenProperties &screen=QgsScreenProperties())
Returns an icon preview for a color ramp.
A dialog 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 ...
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.
static QgsSymbolSelectorWidget * createWidgetWithSymbolOwnership(std::unique_ptr< QgsSymbol > symbol, QgsStyle *style, QgsVectorLayer *vl, QWidget *parent=nullptr)
Creates a QgsSymbolSelectorWidget which takes ownership of a symbol and maintains the ownership for t...
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:231
void setColor(const QColor &color) const
Sets the color for the symbol.
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:353
static QgsSymbol * defaultSymbol(Qgis::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
void changed()
Emitted when the selected unit is changed, or the definition of the map unit scale is changed.
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.
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:73
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6219
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6123
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
QList< QgsRendererRange > QgsRangeList
int precision