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