QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
qgspointcloudclassifiedrendererwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudclassifiedrendererwidget.cpp
3 ---------------------
4 begin : November 2020
5 copyright : (C) 2020 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "moc_qgspointcloudclassifiedrendererwidget.cpp"
21#include "qgspointcloudlayer.h"
23#include "qgsdoublevalidator.h"
24#include "qgsstyle.h"
25#include "qgsguiutils.h"
27#include "qgscolordialog.h"
28#include "qgsapplication.h"
31
32#include <QMimeData>
33#include <QInputDialog>
34
36
37QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
38 : QAbstractItemModel( parent )
39 , mMimeFormat( QStringLiteral( "application/x-qgspointcloudclassifiedrenderermodel" ) )
40{
41}
42
43void QgsPointCloudClassifiedRendererModel::setRendererCategories( const QgsPointCloudCategoryList &categories )
44{
45 if ( !mCategories.empty() )
46 {
47 beginRemoveRows( QModelIndex(), 0, std::max<int>( mCategories.size() - 1, 0 ) );
48 mCategories.clear();
49 endRemoveRows();
50 }
51 if ( categories.size() > 0 )
52 {
53 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
54 mCategories = categories;
55 endInsertRows();
56 }
57}
58
59void QgsPointCloudClassifiedRendererModel::addCategory( const QgsPointCloudCategory &cat )
60{
61 const int idx = mCategories.size();
62 beginInsertRows( QModelIndex(), idx, idx );
63 mCategories.append( cat );
64 endInsertRows();
65
66 emit categoriesChanged();
67}
68
69QgsPointCloudCategory QgsPointCloudClassifiedRendererModel::category( const QModelIndex &index )
70{
71 const int row = index.row();
72 if ( row >= mCategories.size() )
73 {
74 return QgsPointCloudCategory();
75 }
76 return mCategories.at( row );
77}
78
79Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags( const QModelIndex &index ) const
80{
81 if ( !index.isValid() || mCategories.empty() )
82 {
83 return Qt::ItemIsDropEnabled;
84 }
85
86 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
87 if ( index.column() == 1 || index.column() == 2 || index.column() == 3 )
88 {
89 flags |= Qt::ItemIsEditable;
90 }
91 return flags;
92}
93
94Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions() const
95{
96 return Qt::MoveAction;
97}
98
99QVariant QgsPointCloudClassifiedRendererModel::data( const QModelIndex &index, int role ) const
100{
101 if ( !index.isValid() || mCategories.empty() )
102 return QVariant();
103
104 const QgsPointCloudCategory category = mCategories.value( index.row() );
105
106 switch ( role )
107 {
108 case Qt::CheckStateRole:
109 {
110 if ( index.column() == 0 )
111 {
112 return category.renderState() ? Qt::Checked : Qt::Unchecked;
113 }
114 break;
115 }
116
117 case Qt::DisplayRole:
118 case Qt::ToolTipRole:
119 {
120 switch ( index.column() )
121 {
122 case 1:
123 return category.pointSize() > 0 ? QString::number( category.pointSize() ) : QString();
124 case 2:
125 return QString::number( category.value() );
126 case 3:
127 return category.label();
128 case 4:
129 const float value = mPercentages.value( category.value(), -1 );
130 QString str;
131 if ( value < 0 )
132 str = tr( "N/A" );
133 else if ( value != 0 && std::round( value * 10 ) < 1 )
134 str = QStringLiteral( "< " ) + QLocale().toString( 0.1, 'f', 1 );
135 else
136 str = QLocale().toString( mPercentages.value( category.value() ), 'f', 1 );
137 return str;
138 }
139 break;
140 }
141
142 case Qt::DecorationRole:
143 {
144 if ( index.column() == 0 )
145 {
146 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
147 QPixmap pix( iconSize, iconSize );
148 pix.fill( category.color() );
149 return QIcon( pix );
150 }
151 break;
152 }
153
154 case Qt::TextAlignmentRole:
155 {
156 if ( index.column() == 0 )
157 return static_cast<Qt::Alignment::Int>( Qt::AlignHCenter );
158 if ( index.column() == 4 )
159 return static_cast<Qt::Alignment::Int>( Qt::AlignRight );
160 return static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
161 }
162
163 case Qt::EditRole:
164 {
165 switch ( index.column() )
166 {
167 case 1:
168 return category.pointSize() > 0 ? QString::number( category.pointSize() ) : QString();
169 case 2:
170 return QString::number( category.value() );
171 case 3:
172 return category.label();
173 }
174 break;
175 }
176 }
177
178 return QVariant();
179}
180
181bool QgsPointCloudClassifiedRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
182{
183 if ( !index.isValid() )
184 return false;
185
186 if ( index.column() == 0 && role == Qt::CheckStateRole )
187 {
188 mCategories[index.row()].setRenderState( value == Qt::Checked );
189 emit dataChanged( index, index );
190 emit categoriesChanged();
191 return true;
192 }
193
194 if ( role != Qt::EditRole )
195 return false;
196
197 switch ( index.column() )
198 {
199 case 1: // point size
200 {
201 const double size = value.toDouble();
202 mCategories[index.row()].setPointSize( size );
203 break;
204 }
205 case 2: // value
206 {
207 const int val = value.toInt();
208 mCategories[index.row()].setValue( val );
209 break;
210 }
211 case 3: // label
212 {
213 mCategories[index.row()].setLabel( value.toString() );
214 break;
215 }
216 default:
217 return false;
218 }
219
220 emit dataChanged( index, index );
221 emit categoriesChanged();
222 return true;
223}
224
225QVariant QgsPointCloudClassifiedRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
226{
227 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
228 {
229 QStringList lst;
230 lst << tr( "Color" ) << tr( "Size" ) << tr( "Value" ) << tr( "Legend" ) << tr( "Percentage" );
231 return lst.value( section );
232 }
233 return QVariant();
234}
235
236int QgsPointCloudClassifiedRendererModel::rowCount( const QModelIndex &parent ) const
237{
238 if ( parent.isValid() )
239 {
240 return 0;
241 }
242 return mCategories.size();
243}
244
245int QgsPointCloudClassifiedRendererModel::columnCount( const QModelIndex &index ) const
246{
247 Q_UNUSED( index )
248 return 5;
249}
250
251QModelIndex QgsPointCloudClassifiedRendererModel::index( int row, int column, const QModelIndex &parent ) const
252{
253 if ( hasIndex( row, column, parent ) )
254 {
255 return createIndex( row, column );
256 }
257 return QModelIndex();
258}
259
260QModelIndex QgsPointCloudClassifiedRendererModel::parent( const QModelIndex &index ) const
261{
262 Q_UNUSED( index )
263 return QModelIndex();
264}
265
266QStringList QgsPointCloudClassifiedRendererModel::mimeTypes() const
267{
268 QStringList types;
269 types << mMimeFormat;
270 return types;
271}
272
273QMimeData *QgsPointCloudClassifiedRendererModel::mimeData( const QModelIndexList &indexes ) const
274{
275 QMimeData *mimeData = new QMimeData();
276 QByteArray encodedData;
277
278 QDataStream stream( &encodedData, QIODevice::WriteOnly );
279
280 // Create list of rows
281 const auto constIndexes = indexes;
282 for ( const QModelIndex &index : constIndexes )
283 {
284 if ( !index.isValid() || index.column() != 0 )
285 continue;
286
287 stream << index.row();
288 }
289 mimeData->setData( mMimeFormat, encodedData );
290 return mimeData;
291}
292
293bool QgsPointCloudClassifiedRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
294{
295 Q_UNUSED( row )
296 Q_UNUSED( column )
297 if ( action != Qt::MoveAction )
298 return true;
299
300 if ( !data->hasFormat( mMimeFormat ) )
301 return false;
302
303 QByteArray encodedData = data->data( mMimeFormat );
304 QDataStream stream( &encodedData, QIODevice::ReadOnly );
305
306 QVector<int> rows;
307 while ( !stream.atEnd() )
308 {
309 int r;
310 stream >> r;
311 rows.append( r );
312 }
313
314 int to = parent.row();
315 // to is -1 if dragged outside items, i.e. below any item,
316 // then move to the last position
317 if ( to == -1 )
318 to = mCategories.size(); // out of rang ok, will be decreased
319 for ( int i = rows.size() - 1; i >= 0; i-- )
320 {
321 int t = to;
322 if ( rows[i] < t )
323 t--;
324
325 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
326 {
327 mCategories.move( rows[i], t );
328 }
329
330 // current moved under another, shift its index up
331 for ( int j = 0; j < i; j++ )
332 {
333 if ( to < rows[j] && rows[i] > rows[j] )
334 rows[j] += 1;
335 }
336 // removed under 'to' so the target shifted down
337 if ( rows[i] < to )
338 to--;
339 }
340 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
341 emit categoriesChanged();
342 return false;
343}
344
345void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
346{
347 std::sort( rows.begin(), rows.end() ); // list might be unsorted, depending on how the user selected the rows
348 for ( int i = rows.size() - 1; i >= 0; i-- )
349 {
350 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
351 mCategories.removeAt( rows[i] );
352 endRemoveRows();
353 }
354 emit categoriesChanged();
355}
356
357void QgsPointCloudClassifiedRendererModel::removeAllRows()
358{
359 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
360 mCategories.clear();
361 endRemoveRows();
362 emit categoriesChanged();
363}
364
365void QgsPointCloudClassifiedRendererModel::setCategoryColor( int row, const QColor &color )
366{
367 mCategories[row].setColor( color );
368 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
369 emit categoriesChanged();
370}
371
372void QgsPointCloudClassifiedRendererModel::setCategoryPointSize( int row, double size )
373{
374 mCategories[row].setPointSize( size );
375 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
376 emit categoriesChanged();
377}
378
379// ------------------------------ View style --------------------------------
380QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
381 : QgsProxyStyle( parent )
382{}
383
384void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
385{
386 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
387 {
388 QStyleOption opt( *option );
389 opt.rect.setLeft( 0 );
390 // draw always as line above, because we move item to that index
391 opt.rect.setHeight( 0 );
392 if ( widget )
393 opt.rect.setRight( widget->width() );
394 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
395 return;
396 }
397 QProxyStyle::drawPrimitive( element, option, painter, widget );
398}
399
400
401QgsPointCloudClassifiedRendererWidget::QgsPointCloudClassifiedRendererWidget( QgsPointCloudLayer *layer, QgsStyle *style )
402 : QgsPointCloudRendererWidget( layer, style )
403{
404 setupUi( this );
405
406 mAttributeComboBox->setAllowEmptyAttributeName( true );
408
409 mModel = new QgsPointCloudClassifiedRendererModel( this );
410
411 if ( layer )
412 {
413 mAttributeComboBox->setLayer( layer );
414
415 setFromRenderer( layer->renderer() );
416 }
417
418 viewCategories->setModel( mModel );
419 viewCategories->resizeColumnToContents( 0 );
420 viewCategories->resizeColumnToContents( 1 );
421 viewCategories->resizeColumnToContents( 2 );
422 viewCategories->resizeColumnToContents( 3 );
423
424 viewCategories->setStyle( new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
425
426 connect( mAttributeComboBox, &QgsPointCloudAttributeComboBox::attributeChanged, this, &QgsPointCloudClassifiedRendererWidget::attributeChanged );
427 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged, this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
428
429 connect( viewCategories, &QAbstractItemView::doubleClicked, this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
430 connect( btnAddCategories, &QAbstractButton::clicked, this, &QgsPointCloudClassifiedRendererWidget::addCategories );
431 connect( btnDeleteCategories, &QAbstractButton::clicked, this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
432 connect( btnDeleteAllCategories, &QAbstractButton::clicked, this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
433 connect( btnAddCategory, &QAbstractButton::clicked, this, &QgsPointCloudClassifiedRendererWidget::addCategory );
434
435 contextMenu = new QMenu( tr( "Options" ), this );
436 contextMenu->addAction( tr( "Change &Color…" ), this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
437 contextMenu->addAction( tr( "Change &Opacity…" ), this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
438 contextMenu->addAction( tr( "Change &Size…" ), this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
439
440 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
441 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
442 connect( viewCategories, &QTreeView::customContextMenuRequested, this, [=]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
443}
444
445QgsPointCloudRendererWidget *QgsPointCloudClassifiedRendererWidget::create( QgsPointCloudLayer *layer, QgsStyle *style, QgsPointCloudRenderer * )
446{
447 return new QgsPointCloudClassifiedRendererWidget( layer, style );
448}
449
450QgsPointCloudRenderer *QgsPointCloudClassifiedRendererWidget::renderer()
451{
452 if ( !mLayer )
453 {
454 return nullptr;
455 }
456
457 std::unique_ptr<QgsPointCloudClassifiedRenderer> renderer = std::make_unique<QgsPointCloudClassifiedRenderer>();
458 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
459 renderer->setCategories( mModel->categories() );
460
461 return renderer.release();
462}
463
464QgsPointCloudCategoryList QgsPointCloudClassifiedRendererWidget::categoriesList()
465{
466 return mModel->categories();
467}
468
469QString QgsPointCloudClassifiedRendererWidget::attribute()
470{
471 return mAttributeComboBox->currentAttribute();
472}
473
474void QgsPointCloudClassifiedRendererWidget::attributeChanged()
475{
476 if ( mBlockChangedSignal )
477 return;
478
479 mBlockChangedSignal = true;
480 mModel->removeAllRows();
481 mBlockChangedSignal = false;
482 addCategories();
483}
484
485void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
486{
487 if ( mBlockChangedSignal )
488 return;
489
490 updateCategoriesPercentages();
491 emit widgetChanged();
492}
493
494void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked( const QModelIndex &idx )
495{
496 if ( idx.isValid() && idx.column() == 0 )
497 changeCategoryColor();
498}
499
500void QgsPointCloudClassifiedRendererWidget::addCategories()
501{
502 if ( !mLayer || !mLayer->dataProvider() )
503 return;
504
505
506 const QString currentAttribute = mAttributeComboBox->currentAttribute();
507 const QgsPointCloudStatistics stats = mLayer->statistics();
508
509 const QgsPointCloudCategoryList currentCategories = mModel->categories();
510
511 const bool isClassificationAttribute = ( 0 == currentAttribute.compare( QStringLiteral( "Classification" ), Qt::CaseInsensitive ) );
512 const bool isBooleanAttribute = ( 0 == currentAttribute.compare( QStringLiteral( "Synthetic" ), Qt::CaseInsensitive ) || 0 == currentAttribute.compare( QStringLiteral( "KeyPoint" ), Qt::CaseInsensitive ) || 0 == currentAttribute.compare( QStringLiteral( "Withheld" ), Qt::CaseInsensitive ) || 0 == currentAttribute.compare( QStringLiteral( "Overlap" ), Qt::CaseInsensitive ) );
513
514 QList<int> providerCategories = stats.classesOf( currentAttribute );
515
516 // for 0/1 attributes we should always show both 0 and 1 categories, unless we have full stats
517 // so we can show categories that are actually available
518 if ( isBooleanAttribute && ( providerCategories.isEmpty() || stats.sampledPointsCount() < mLayer->pointCount() ) )
519 providerCategories = { 0, 1 };
520
521 const QgsPointCloudCategoryList defaultLayerCategories = isClassificationAttribute ? QgsPointCloudRendererRegistry::classificationAttributeCategories( mLayer ) : QgsPointCloudCategoryList();
522
523 mBlockChangedSignal = true;
524 for ( const int &providerCategory : std::as_const( providerCategories ) )
525 {
526 // does this category already exist?
527 bool found = false;
528 for ( const QgsPointCloudCategory &c : currentCategories )
529 {
530 if ( c.value() == providerCategory )
531 {
532 found = true;
533 break;
534 }
535 }
536
537 if ( found )
538 continue;
539
540 QgsPointCloudCategory category;
541 if ( isClassificationAttribute )
542 {
543 for ( const QgsPointCloudCategory &c : defaultLayerCategories )
544 {
545 if ( c.value() == providerCategory )
546 category = c;
547 }
548 }
549 else
550 {
551 category = QgsPointCloudCategory( providerCategory, QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor(), QString::number( providerCategory ) );
552 }
553 mModel->addCategory( category );
554 }
555 mBlockChangedSignal = false;
556 emitWidgetChanged();
557}
558
559void QgsPointCloudClassifiedRendererWidget::addCategory()
560{
561 if ( !mModel )
562 return;
563
564 const QgsPointCloudCategory cat( mModel->categories().size(), QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor(), QString(), true );
565 mModel->addCategory( cat );
566}
567
568void QgsPointCloudClassifiedRendererWidget::deleteCategories()
569{
570 const QList<int> categoryIndexes = selectedCategories();
571 mModel->deleteRows( categoryIndexes );
572}
573
574void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
575{
576 mModel->removeAllRows();
577}
578
579void QgsPointCloudClassifiedRendererWidget::setFromRenderer( const QgsPointCloudRenderer *r )
580{
581 mBlockChangedSignal = true;
582 if ( const QgsPointCloudClassifiedRenderer *classifiedRenderer = dynamic_cast<const QgsPointCloudClassifiedRenderer *>( r ) )
583 {
584 mModel->setRendererCategories( classifiedRenderer->categories() );
585 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
586 }
587 else
588 {
589 initialize();
590 }
591 mBlockChangedSignal = false;
592 emitWidgetChanged();
593}
594
595void QgsPointCloudClassifiedRendererWidget::setFromCategories( QgsPointCloudCategoryList categories, const QString &attribute )
596{
597 mBlockChangedSignal = true;
598 mModel->setRendererCategories( categories );
599 if ( !attribute.isEmpty() )
600 {
601 mAttributeComboBox->setAttribute( attribute );
602 }
603 else
604 {
605 initialize();
606 }
607 mBlockChangedSignal = false;
608 emitWidgetChanged();
609}
610
611void QgsPointCloudClassifiedRendererWidget::initialize()
612{
613 if ( mAttributeComboBox->findText( QStringLiteral( "Classification" ) ) > -1 )
614 {
615 mAttributeComboBox->setAttribute( QStringLiteral( "Classification" ) );
616 }
617 else
618 {
619 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
620 }
621 mModel->removeAllRows();
622 addCategories();
623}
624
625void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
626{
627 const QList<int> categoryList = selectedCategories();
628 if ( categoryList.isEmpty() )
629 {
630 return;
631 }
632
633 const QgsPointCloudCategory category = mModel->categories().value( categoryList.first() );
634
636 if ( panel && panel->dockMode() )
637 {
639 colorWidget->setPanelTitle( categoryList.count() == 1 ? category.label() : tr( "Select Color" ) );
640 colorWidget->setAllowOpacity( true );
641 colorWidget->setPreviousColor( category.color() );
642
643 connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [=]( const QColor &newColor ) {
644 for ( int row : categoryList )
645 {
646 mModel->setCategoryColor( row, newColor );
647 }
648 } );
649 panel->openPanel( colorWidget );
650 }
651 else
652 {
653 const QColor newColor = QgsColorDialog::getColor( category.color(), this, category.label(), true );
654 if ( newColor.isValid() )
655 {
656 for ( int row : categoryList )
657 {
658 mModel->setCategoryColor( row, newColor );
659 }
660 }
661 }
662}
663
664void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
665{
666 const QList<int> categoryList = selectedCategories();
667 if ( categoryList.isEmpty() )
668 {
669 return;
670 }
671
672 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
673
674 bool ok;
675 const double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
676 if ( ok )
677 {
678 for ( int row : categoryList )
679 {
680 const QgsPointCloudCategory category = mModel->categories().value( row );
681 QColor color = category.color();
682 color.setAlphaF( opacity / 100.0 );
683 mModel->setCategoryColor( row, color );
684 }
685 }
686}
687
688void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
689{
690 const QList<int> categoryList = selectedCategories();
691 if ( categoryList.isEmpty() )
692 {
693 return;
694 }
695
696 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
697
698 bool ok;
699 const double size = QInputDialog::getDouble( this, tr( "Point Size" ), tr( "Change point size (set to 0 to reset to default point size)" ), oldSize, 0.0, 42.0, 1, &ok );
700 if ( ok )
701 {
702 for ( int row : categoryList )
703 {
704 mModel->setCategoryPointSize( row, size );
705 }
706 }
707}
708
709QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
710{
711 QList<int> rows;
712 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
713 for ( const QModelIndex &r : selectedRows )
714 {
715 if ( r.isValid() )
716 {
717 rows.append( r.row() );
718 }
719 }
720 return rows;
721}
722
723int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
724{
725 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
726 if ( !idx.isValid() )
727 return -1;
728 return idx.row();
729}
730
731void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
732{
733 QMap<int, float> percentages;
734
735 const QgsPointCloudStatistics stats = mLayer->statistics();
736 const QMap<int, int> classes = stats.availableClasses( attribute() );
737 const int pointCount = stats.sampledPointsCount();
738 const QgsPointCloudCategoryList currentCategories = mModel->categories();
739
740 // when the stats are 100% accurate, we are sure that missing classes have a 0% of points
741 const bool statsExact = stats.sampledPointsCount() == mLayer->pointCount();
742 for ( const QgsPointCloudCategory &category : currentCategories )
743 {
744 if ( classes.contains( category.value() ) || statsExact )
745 percentages.insert( category.value(), ( double ) classes.value( category.value() ) / pointCount * 100 );
746 }
747 mModel->updateCategoriesPercentages( percentages );
748}
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application's color scheme registry, used for managing color schemes.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
QColor fetchRandomStyleColor() const
Returns a random color for use with a new symbol style (e.g.
A custom QGIS widget for selecting a color, including options for selecting colors via hue wheel,...
@ LayoutVertical
Use a narrower, vertically stacked layout.
void currentColorChanged(const QColor &color)
Emitted when the dialog's color changes.
void setPreviousColor(const QColor &color)
Sets the color to show in an optional "previous color" section.
void setAllowOpacity(bool allowOpacity)
Sets whether opacity modification (transparency) is permitted for the color dialog.
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 ...
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.
void attributeChanged(const QString &name)
Emitted when the currently selected attribute changes.
Represents an individual category (class) from a QgsPointCloudClassifiedRenderer.
int value() const
Returns the value corresponding to this category.
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QColor color() const
Returns the color which will be used to render this category.
double pointSize() const
Returns the point size for this category.
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
Renders point clouds by a classification attribute.
Represents a map layer supporting display of point clouds.
QgsPointCloudRenderer * renderer()
Returns the 2D renderer for the point cloud.
static QgsPointCloudCategoryList classificationAttributeCategories(const QgsPointCloudLayer *layer)
Returns a list of categories using the available Classification classes of a specified layer,...
Base class for point cloud 2D renderer settings widgets.
Abstract base class for 2d point cloud renderers.
Class used to store statistics of a point cloud dataset.
QMap< int, int > availableClasses(const QString &attribute) const
Returns a map containing the count of each class of the attribute attribute If no matching statistic ...
QList< int > classesOf(const QString &attribute) const
Returns a list of existing classes which are present for the specified attribute.
int sampledPointsCount() const
Returns the number of points used to calculate the statistics.
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
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,...
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QList< QgsPointCloudCategory > QgsPointCloudCategoryList