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