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