QGIS API Documentation 3.43.0-Master (c67cf405802)
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}
837
838void QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged( int )
839{
840 const MethodMode newMethod = static_cast<MethodMode>( methodComboBox->currentData().toInt() );
841 toggleMethodWidgets( newMethod );
842 switch ( newMethod )
843 {
844 case ColorMode:
845 {
846 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Color );
847 QgsColorRamp *ramp = btnColorRamp->colorRamp();
848
849 if ( !ramp )
850 {
851 QMessageBox::critical( this, tr( "Select Method" ), tr( "No color ramp defined." ) );
852 return;
853 }
854 mRenderer->setSourceColorRamp( ramp );
856 break;
857 }
858
859 case SizeMode:
860 {
861 lblColorRamp->setVisible( false );
862 btnColorRamp->setVisible( false );
863 lblSize->setVisible( true );
864 minSizeSpinBox->setVisible( true );
865 lblSize->setVisible( true );
866 maxSizeSpinBox->setVisible( true );
867 mSizeUnitWidget->setVisible( true );
868
869 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Size );
870 reapplySizes();
871 break;
872 }
873 }
874}
875
876void QgsGraduatedSymbolRendererWidget::updateMethodParameters()
877{
878 clearParameterWidgets();
879
880 const QString methodId = cboGraduatedMode->currentData().toString();
881 mClassificationMethod = QgsApplication::classificationMethodRegistry()->method( methodId );
882 Q_ASSERT( mClassificationMethod.get() );
883
884 // need more context?
886
887 for ( const QgsProcessingParameterDefinition *def : mClassificationMethod->parameterDefinitions() )
888 {
890 mParametersLayout->addRow( ppww->createWrappedLabel(), ppww->createWrappedWidget( context ) );
891
892 QVariant value = mClassificationMethod->parameterValues().value( def->name(), def->defaultValueForGui() );
893 ppww->setParameterValue( value, context );
894
896
897 mParameterWidgetWrappers.push_back( std::unique_ptr<QgsAbstractProcessingParameterWidgetWrapper>( ppww ) );
898 }
899
900 spinGraduatedClasses->setEnabled( !( mClassificationMethod->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) );
901}
902
903void QgsGraduatedSymbolRendererWidget::toggleMethodWidgets( MethodMode mode )
904{
905 switch ( mode )
906 {
907 case ColorMode:
908 {
909 lblColorRamp->setVisible( true );
910 btnColorRamp->setVisible( true );
911 lblSize->setVisible( false );
912 minSizeSpinBox->setVisible( false );
913 lblSizeTo->setVisible( false );
914 maxSizeSpinBox->setVisible( false );
915 mSizeUnitWidget->setVisible( false );
916 break;
917 }
918
919 case SizeMode:
920 {
921 lblColorRamp->setVisible( false );
922 btnColorRamp->setVisible( false );
923 lblSize->setVisible( true );
924 minSizeSpinBox->setVisible( true );
925 lblSizeTo->setVisible( true );
926 maxSizeSpinBox->setVisible( true );
927 mSizeUnitWidget->setVisible( true );
928 break;
929 }
930 }
931}
932
933void QgsGraduatedSymbolRendererWidget::clearParameterWidgets()
934{
935 while ( mParametersLayout->rowCount() )
936 {
937 QFormLayout::TakeRowResult row = mParametersLayout->takeRow( 0 );
938 for ( QLayoutItem *item : { row.labelItem, row.fieldItem } )
939 if ( item )
940 {
941 QWidget *widget = item->widget();
942 delete item;
943 if ( widget )
944 delete widget;
945 }
946 }
947 mParameterWidgetWrappers.clear();
948}
949
951{
952 if ( !mModel )
953 return;
954
955 mModel->updateSymbology();
956
958 spinGraduatedClasses->setValue( mRenderer->ranges().count() );
960
961 emit widgetChanged();
962}
963
965{
966 for ( const QgsLegendSymbolItem &legendSymbol : levels )
967 {
968 QgsSymbol *sym = legendSymbol.symbol();
969 for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
970 {
971 mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
972 }
973 }
974 mRenderer->setUsingSymbolLevels( enabled );
975 mModel->updateSymbology();
976 emit widgetChanged();
977}
978
979void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget )
980{
981 mGraduatedSymbol.reset( widget->symbol()->clone() );
982
984}
985
987{
988 mSizeUnitWidget->blockSignals( true );
989 mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
990 mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
991 mSizeUnitWidget->blockSignals( false );
992
993 QItemSelectionModel *m = viewGraduated->selectionModel();
994 QModelIndexList selectedIndexes = m->selectedRows( 1 );
995 if ( !selectedIndexes.isEmpty() )
996 {
997 const auto constSelectedIndexes = selectedIndexes;
998 for ( const QModelIndex &idx : constSelectedIndexes )
999 {
1000 if ( idx.isValid() )
1001 {
1002 int rangeIdx = idx.row();
1003 QgsSymbol *newRangeSymbol = mGraduatedSymbol->clone();
1004 if ( selectedIndexes.count() > 1 )
1005 {
1006 //if updating multiple ranges, retain the existing range colors
1007 newRangeSymbol->setColor( mRenderer->ranges().at( rangeIdx ).symbol()->color() );
1008 }
1009 mRenderer->updateRangeSymbol( rangeIdx, newRangeSymbol );
1010 }
1011 }
1012 }
1013 else
1014 {
1015 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1016 }
1017
1019 emit widgetChanged();
1020}
1021
1022void QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished()
1023{
1024 const QString text = cboSymmetryPoint->lineEdit()->text();
1025 int index = cboSymmetryPoint->findText( text );
1026 if ( index != -1 )
1027 {
1028 cboSymmetryPoint->setCurrentIndex( index );
1029 }
1030 else
1031 {
1032 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), text );
1034 }
1035}
1036
1037
1039{
1040 mUpdateTimer.start( 500 );
1041}
1042
1043void QgsGraduatedSymbolRendererWidget::classifyGraduatedImpl()
1044{
1045 if ( mBlockUpdates || !mClassificationMethod )
1046 return;
1047
1048 QgsTemporaryCursorOverride override( Qt::WaitCursor );
1049 QString attrName = mExpressionWidget->currentField();
1050 int nclasses = spinGraduatedClasses->value();
1051
1052 int attrNum = mLayer->fields().lookupField( attrName );
1053
1054 QVariant minVal;
1055 QVariant maxVal;
1056 mLayer->minimumAndMaximumValue( attrNum, minVal, maxVal );
1057
1058 double minimum = minVal.toDouble();
1059 double maximum = maxVal.toDouble();
1060 mSymmetryPointValidator->setBottom( minimum );
1061 mSymmetryPointValidator->setTop( maximum );
1062 mSymmetryPointValidator->setMaxDecimals( spinPrecision->value() );
1063
1064 if ( mClassificationMethod->id() == QgsClassificationEqualInterval::METHOD_ID || mClassificationMethod->id() == QgsClassificationStandardDeviation::METHOD_ID )
1065 {
1066 // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
1067 // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
1068 double currentValue = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1069 if ( currentValue < ( minimum + ( maximum - minimum ) / 100. ) || currentValue > ( maximum - ( maximum - minimum ) / 100. ) )
1070 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( minimum + ( maximum - minimum ) / 2., 'f', mClassificationMethod->labelPrecision() + 2 ) );
1071 }
1072
1073 if ( mGroupBoxSymmetric->isChecked() )
1074 {
1075 double symmetryPoint = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1076 bool astride = cbxAstride->isChecked();
1077 mClassificationMethod->setSymmetricMode( true, symmetryPoint, astride );
1078 }
1079
1080 QVariantMap parameterValues;
1081 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
1082 parameterValues.insert( ppww->parameterDefinition()->name(), ppww->parameterValue() );
1083 mClassificationMethod->setParameterValues( parameterValues );
1084
1085 // set method to renderer
1086 mRenderer->setClassificationMethod( mClassificationMethod->clone().release() );
1087
1088 // create and set new renderer
1089 mRenderer->setClassAttribute( attrName );
1090
1091 // If complexity >= oN^2, warn for big dataset (more than 50k records)
1092 // and give the user the chance to cancel
1093 if ( mRenderer->classificationMethod()->codeComplexity() > 1 && mLayer->featureCount() > 50000 )
1094 {
1095 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 ) )
1096 {
1097 return;
1098 }
1099 }
1100
1101 if ( methodComboBox->currentData() == ColorMode )
1102 {
1103 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1104 if ( !ramp )
1105 {
1106 QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
1107 return;
1108 }
1109 mRenderer->setSourceColorRamp( ramp.release() );
1110 }
1111 else
1112 {
1113 mRenderer->setSourceColorRamp( nullptr );
1114 }
1115
1116 QString error;
1117 mRenderer->updateClasses( mLayer, nclasses, error );
1118
1119 if ( !error.isEmpty() )
1120 QMessageBox::critical( this, tr( "Apply Classification" ), error );
1121
1122 if ( methodComboBox->currentData() == SizeMode )
1123 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1124
1125 mRenderer->calculateLabelPrecision();
1126 // PrettyBreaks and StdDev calculation don't generate exact
1127 // number of classes - leave user interface unchanged for these
1128 updateUiFromRenderer( false );
1129}
1130
1132{
1133 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1134 if ( !ramp )
1135 return;
1136
1137 mRenderer->updateColorRamp( ramp.release() );
1138 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1140}
1141
1143{
1144 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1145 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1147}
1148
1149#if 0
1150int QgsRendererPropertiesDialog::currentRangeRow()
1151{
1152 QModelIndex idx = viewGraduated->selectionModel()->currentIndex();
1153 if ( !idx.isValid() )
1154 return -1;
1155 return idx.row();
1156}
1157#endif
1158
1160{
1161 QList<int> rows;
1162 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1163
1164 const auto constSelectedRows = selectedRows;
1165 for ( const QModelIndex &r : constSelectedRows )
1166 {
1167 if ( r.isValid() )
1168 {
1169 rows.append( r.row() );
1170 }
1171 }
1172 return rows;
1173}
1174
1176{
1178 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1179 QModelIndexList::const_iterator sIt = selectedRows.constBegin();
1180
1181 for ( ; sIt != selectedRows.constEnd(); ++sIt )
1182 {
1183 selectedRanges.append( mModel->rendererRange( *sIt ) );
1184 }
1185 return selectedRanges;
1186}
1187
1189{
1190 if ( idx.isValid() && idx.column() == 0 )
1191 changeRangeSymbol( idx.row() );
1192 if ( idx.isValid() && idx.column() == 1 )
1193 changeRange( idx.row() );
1194}
1195
1197{
1198 if ( !idx.isValid() )
1199 mRowSelected = -1;
1200 else
1201 mRowSelected = idx.row();
1202}
1203
1207
1209{
1210 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1211 std::unique_ptr<QgsSymbol> newSymbol( range.symbol()->clone() );
1213 if ( panel && panel->dockMode() )
1214 {
1216 widget->setContext( mContext );
1217 widget->setPanelTitle( range.label() );
1218 connect( widget, &QgsPanelWidget::widgetChanged, this, [=] { updateSymbolsFromWidget( widget ); } );
1219 openPanel( widget );
1220 }
1221 else
1222 {
1223 QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1224 dlg.setContext( mContext );
1225 if ( !dlg.exec() || !newSymbol )
1226 {
1227 return;
1228 }
1229
1230 mGraduatedSymbol = std::move( newSymbol );
1231 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
1233 }
1234}
1235
1237{
1238 QgsLUDialog dialog( this );
1239
1240 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1241 // Add arbitrary 2 to number of decimal places to retain a bit extra.
1242 // Ensures users can see if legend is not completely honest!
1243 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1244 if ( decimalPlaces < 0 )
1245 decimalPlaces = 0;
1246 dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
1247 dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
1248
1249 if ( dialog.exec() == QDialog::Accepted )
1250 {
1251 mRenderer->updateRangeUpperValue( rangeIdx, dialog.upperValueDouble() );
1252 mRenderer->updateRangeLowerValue( rangeIdx, dialog.lowerValueDouble() );
1253
1254 //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1255 if ( cbxLinkBoundaries->isChecked() )
1256 {
1257 if ( rangeIdx > 0 )
1258 {
1259 mRenderer->updateRangeUpperValue( rangeIdx - 1, dialog.lowerValueDouble() );
1260 }
1261
1262 if ( rangeIdx < mRenderer->ranges().size() - 1 )
1263 {
1264 mRenderer->updateRangeLowerValue( rangeIdx + 1, dialog.upperValueDouble() );
1265 }
1266 }
1267 }
1268 mHistogramWidget->refresh();
1269 emit widgetChanged();
1270}
1271
1273{
1274 mModel->addClass( mGraduatedSymbol.get() );
1275 mHistogramWidget->refresh();
1276 emit widgetChanged();
1277}
1278
1280{
1281 QList<int> classIndexes = selectedClasses();
1282 mModel->deleteRows( classIndexes );
1283 mHistogramWidget->refresh();
1284 emit widgetChanged();
1285}
1286
1288{
1289 mModel->removeAllRows();
1290 mHistogramWidget->refresh();
1291 emit widgetChanged();
1292}
1293
1295{
1296 const QgsRangeList &ranges = mRenderer->ranges();
1297 bool ordered = true;
1298 for ( int i = 1; i < ranges.size(); ++i )
1299 {
1300 if ( ranges[i] < ranges[i - 1] )
1301 {
1302 ordered = false;
1303 break;
1304 }
1305 }
1306 return ordered;
1307}
1308
1310{
1311 //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1312 //This is done by updating all lower ranges to the upper value of the range above
1313 if ( linked )
1314 {
1315 if ( !rowsOrdered() )
1316 {
1317 int result = QMessageBox::warning(
1318 this,
1319 tr( "Link Class Boundaries" ),
1320 tr( "Rows will be reordered before linking boundaries. Continue?" ),
1321 QMessageBox::Ok | QMessageBox::Cancel
1322 );
1323 if ( result != QMessageBox::Ok )
1324 {
1325 cbxLinkBoundaries->setChecked( false );
1326 return;
1327 }
1328 mRenderer->sortByValue();
1329 }
1330
1331 // Ok to proceed
1332 for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1333 {
1334 mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1335 }
1337 }
1338}
1339
1341{
1342 if ( item->column() == 2 )
1343 {
1344 QString label = item->text();
1345 int idx = item->row();
1346 mRenderer->updateRangeLabel( idx, label );
1347 }
1348}
1349
1351{
1352 mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
1353 mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
1354 mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
1355 mRenderer->updateRangeLabels();
1356 mModel->updateLabels();
1357}
1358
1359
1361{
1362 QList<QgsSymbol *> selectedSymbols;
1363
1364 QItemSelectionModel *m = viewGraduated->selectionModel();
1365 QModelIndexList selectedIndexes = m->selectedRows( 1 );
1366 if ( !selectedIndexes.isEmpty() )
1367 {
1368 const QgsRangeList &ranges = mRenderer->ranges();
1369 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1370 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1371 {
1372 QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1373 if ( list.size() < 3 )
1374 {
1375 continue;
1376 }
1377 // Not strictly necessary because the range should have been sanitized already
1378 // after user input, but being permissive never hurts
1379 bool ok = false;
1380 double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1381 if ( !ok )
1382 lowerBound = 0.0;
1383 double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1384 if ( !ok )
1385 upperBound = 0.0;
1386 QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1387 if ( s )
1388 {
1389 selectedSymbols.append( s );
1390 }
1391 }
1392 }
1393 return selectedSymbols;
1394}
1395
1396QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1397{
1398 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1399 if ( decimalPlaces < 0 )
1400 decimalPlaces = 0;
1401 double precision = 1.0 / std::pow( 10, decimalPlaces );
1402
1403 for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1404 {
1405 if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1406 {
1407 return it->symbol();
1408 }
1409 }
1410 return nullptr;
1411}
1412
1414{
1415 if ( mModel )
1416 {
1417 mModel->updateSymbology();
1418 }
1419 mHistogramWidget->refresh();
1420 emit widgetChanged();
1421}
1422
1427
1429{
1430 viewGraduated->selectionModel()->clear();
1431 if ( !rowsOrdered() )
1432 {
1433 cbxLinkBoundaries->setChecked( false );
1434 }
1435 emit widgetChanged();
1436}
1437
1442
1444{
1445 if ( !event )
1446 {
1447 return;
1448 }
1449
1450 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1451 {
1452 mCopyBuffer.clear();
1453 mCopyBuffer = selectedRanges();
1454 }
1455 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1456 {
1457 QgsRangeList::iterator rIt = mCopyBuffer.begin();
1458 for ( ; rIt != mCopyBuffer.end(); ++rIt )
1459 {
1460 rIt->mUuid = QUuid::createUuid().toString();
1461 mModel->addClass( *rIt );
1462 }
1463 emit widgetChanged();
1464 }
1465}
1466
1467void QgsGraduatedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1468{
1469 const QgsRangeList ranges = selectedRanges();
1470 if ( !ranges.isEmpty() )
1471 {
1472 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( ranges.at( 0 ).symbol()->clone() );
1473 }
1474 else if ( mRenderer->sourceSymbol() )
1475 {
1476 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1477 }
1478 btnChangeGraduatedSymbol->setDialogTitle( ranges.size() == 1 ? ranges.at( 0 ).label() : tr( "Symbol Settings" ) );
1479}
1480
1481void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1482{
1483 QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1484 QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1485 if ( panel )
1486 {
1487 connect( panel, &QgsPanelWidget::widgetChanged, this, [=] {
1488 mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1489 emit widgetChanged();
1490 } );
1491 openPanel( panel ); // takes ownership of the panel
1492 }
1493}
1494
1495void QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol()
1496{
1497 mGraduatedSymbol.reset( btnChangeGraduatedSymbol->symbol()->clone() );
1499}
1500
1502{
1503 std::unique_ptr<QgsSymbol> tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1504 if ( !tempSymbol )
1505 return;
1506
1507 const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1508 for ( const QModelIndex &index : selectedRows )
1509 {
1510 if ( !index.isValid() )
1511 continue;
1512
1513 const int row = index.row();
1514 if ( !mRenderer || mRenderer->ranges().size() <= row )
1515 continue;
1516
1517 if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
1518 continue;
1519
1520 std::unique_ptr<QgsSymbol> newCatSymbol( tempSymbol->clone() );
1521 if ( selectedRows.count() > 1 )
1522 {
1523 //if updating multiple ranges, retain the existing category colors
1524 newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
1525 }
1526
1527 mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
1528 }
1529 emit widgetChanged();
1530}
@ 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, 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,...
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:6286
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6190
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
QList< QgsRendererRange > QgsRangeList
int precision