Skip to content

Commit e7b7549

Browse files
committed
[FEATURE] New statistical summary dock widget
Can display summary statistics (eg mean, standard deviation, ...) for a field or expression from a vector layer.
1 parent 1078daf commit e7b7549

10 files changed

+542
-1
lines changed

python/core/qgsstatisticalsummary.sip

+11
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ class QgsStatisticalSummary
6969
*/
7070
void calculate( const QList<double>& values );
7171

72+
/** Returns the value of a specified statistic
73+
* @param stat statistic to return
74+
* @returns calculated value of statistic
75+
*/
76+
double statistic( Statistic stat ) const;
77+
7278
/** Returns calculated count of values
7379
*/
7480
int count() const;
@@ -151,6 +157,11 @@ class QgsStatisticalSummary
151157
*/
152158
double interQuartileRange() const;
153159

160+
/** Returns the friendly display name for a statistic
161+
* @param statistic statistic to return name for
162+
*/
163+
static QString displayName( Statistic statistic );
164+
154165
};
155166

156167
QFlags<QgsStatisticalSummary::Statistic> operator|(QgsStatisticalSummary::Statistic f1, QFlags<QgsStatisticalSummary::Statistic> f2);

src/app/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ SET(QGIS_APP_SRCS
103103
qgsprojectproperties.cpp
104104
qgsrastercalcdialog.cpp
105105
qgsrasterlayerproperties.cpp
106+
qgsstatisticalsummarydockwidget.cpp
106107
qgstextannotationdialog.cpp
107108
qgsshortcutsmanager.cpp
108109
qgsguivectorlayertools.h
@@ -248,6 +249,7 @@ SET (QGIS_APP_MOC_HDRS
248249
qgsrasterlayerproperties.h
249250
qgssnappingdialog.h
250251
qgssponsors.h
252+
qgsstatisticalsummarydockwidget.h
251253
qgssvgannotationdialog.h
252254
qgstextannotationdialog.h
253255
qgstipgui.h

src/app/qgisapp.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@
195195
#include "qgssinglebandgrayrenderer.h"
196196
#include "qgssnappingdialog.h"
197197
#include "qgssponsors.h"
198+
#include "qgsstatisticalsummarydockwidget.h"
198199
#include "qgssvgannotationitem.h"
199200
#include "qgstextannotationitem.h"
200201
#include "qgstipgui.h"
@@ -598,6 +599,10 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, QWidget * parent,
598599
mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this );
599600
mAdvancedDigitizingDockWidget->setObjectName( "AdvancedDigitizingTools" );
600601

602+
// Statistical Summary dock
603+
mStatisticalSummaryDockWidget = new QgsStatisticalSummaryDockWidget( this );
604+
mStatisticalSummaryDockWidget->setObjectName( "StatistalSummaryDockWidget" );
605+
601606
mSnappingUtils = new QgsMapCanvasSnappingUtils( mMapCanvas, this );
602607
mMapCanvas->setSnappingUtils( mSnappingUtils );
603608
connect( QgsProject::instance(), SIGNAL( snapSettingsChanged() ), mSnappingUtils, SLOT( readConfigFromProject() ) );
@@ -642,6 +647,9 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, QWidget * parent,
642647
addDockWidget( Qt::LeftDockWidgetArea, mAdvancedDigitizingDockWidget );
643648
mAdvancedDigitizingDockWidget->hide();
644649

650+
addDockWidget( Qt::LeftDockWidgetArea, mStatisticalSummaryDockWidget );
651+
mStatisticalSummaryDockWidget->hide();
652+
645653
QMainWindow::addDockWidget( Qt::BottomDockWidgetArea, mUserInputDockWidget );
646654
mUserInputDockWidget->setFloating( true );
647655

@@ -1202,6 +1210,7 @@ void QgisApp::createActions()
12021210
connect( mActionSvgAnnotation, SIGNAL( triggered() ), this, SLOT( addSvgAnnotation() ) );
12031211
connect( mActionAnnotation, SIGNAL( triggered() ), this, SLOT( modifyAnnotation() ) );
12041212
connect( mActionLabeling, SIGNAL( triggered() ), this, SLOT( labeling() ) );
1213+
connect( mActionStatisticalSummary, SIGNAL( triggered( ) ), this, SLOT( showStatisticsDockWidget() ) );
12051214

12061215
// Layer Menu Items
12071216

@@ -10483,6 +10492,12 @@ void QgisApp::osmExportDialog()
1048310492
dlg.exec();
1048410493
}
1048510494

10495+
void QgisApp::showStatisticsDockWidget()
10496+
{
10497+
mStatisticalSummaryDockWidget->show();
10498+
mStatisticalSummaryDockWidget->raise();
10499+
}
10500+
1048610501

1048710502
#ifdef HAVE_TOUCH
1048810503
bool QgisApp::gestureEvent( QGestureEvent *event )

src/app/qgisapp.h

+6
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class QgsBrowserDockWidget;
7676
class QgsAdvancedDigitizingDockWidget;
7777
class QgsSnappingDialog;
7878
class QgsGPSInformationWidget;
79+
class QgsStatisticalSummaryDockWidget;
7980

8081
class QgsDecorationItem;
8182

@@ -1226,6 +1227,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
12261227
/** Make the user feel dizzy */
12271228
void dizzy();
12281229

1230+
/** Shows the statistical summary dock widget and brings it to the foreground
1231+
*/
1232+
void showStatisticsDockWidget();
1233+
12291234
signals:
12301235
/** emitted when a key is pressed and we want non widget sublasses to be able
12311236
to pick up on this (e.g. maplayer) */
@@ -1615,6 +1620,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
16151620
QgsBrowserDockWidget *mBrowserWidget2;
16161621

16171622
QgsAdvancedDigitizingDockWidget *mAdvancedDigitizingDockWidget;
1623+
QgsStatisticalSummaryDockWidget* mStatisticalSummaryDockWidget;
16181624

16191625
QgsSnappingDialog *mSnappingDialog;
16201626

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/***************************************************************************
2+
qgsstatisticalsummarydockwidget.cpp
3+
-----------------------------------
4+
begin : May 2015
5+
copyright : (C) 2015 by Nyall Dawson
6+
email : nyall dot dawson 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+
#include "qgsstatisticalsummarydockwidget.h"
16+
#include "qgsstatisticalsummary.h"
17+
#include <QTableWidget>
18+
#include <QAction>
19+
#include <QSettings>
20+
21+
QList< QgsStatisticalSummary::Statistic > QgsStatisticalSummaryDockWidget::mDisplayStats =
22+
QList< QgsStatisticalSummary::Statistic > () << QgsStatisticalSummary::Count
23+
<< QgsStatisticalSummary::Sum
24+
<< QgsStatisticalSummary::Mean
25+
<< QgsStatisticalSummary::Median
26+
<< QgsStatisticalSummary::StDev
27+
<< QgsStatisticalSummary::StDevSample
28+
<< QgsStatisticalSummary::Min
29+
<< QgsStatisticalSummary::Max
30+
<< QgsStatisticalSummary::Range
31+
<< QgsStatisticalSummary::Minority
32+
<< QgsStatisticalSummary::Majority
33+
<< QgsStatisticalSummary::Variety
34+
<< QgsStatisticalSummary::FirstQuartile
35+
<< QgsStatisticalSummary::ThirdQuartile
36+
<< QgsStatisticalSummary::InterQuartileRange;
37+
38+
#define MISSING_VALUES -1
39+
40+
QgsStatisticalSummaryDockWidget::QgsStatisticalSummaryDockWidget( QWidget *parent )
41+
: QDockWidget( parent )
42+
, mLayer( 0 )
43+
{
44+
setupUi( this );
45+
46+
mLayerComboBox->setFilters( QgsMapLayerProxyModel::VectorLayer );
47+
mFieldExpressionWidget->setFilters( QgsFieldProxyModel::Numeric );
48+
connect( mLayerComboBox, SIGNAL( layerChanged( QgsMapLayer* ) ), this, SLOT( layerChanged( QgsMapLayer* ) ) );
49+
connect( mFieldExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( refreshStatistics() ) );
50+
connect( mSelectedOnlyCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( refreshStatistics() ) );
51+
connect( mButtonRefresh, SIGNAL( clicked( bool ) ), this, SLOT( refreshStatistics() ) );
52+
53+
if ( mLayerComboBox->currentLayer() )
54+
{
55+
mFieldExpressionWidget->setLayer( mLayerComboBox->currentLayer() );
56+
}
57+
58+
QSettings settings;
59+
foreach ( QgsStatisticalSummary::Statistic stat, mDisplayStats )
60+
{
61+
QAction* action = new QAction( QgsStatisticalSummary::displayName( stat ), mOptionsToolButton );
62+
action->setCheckable( true );
63+
bool checked = settings.value( QString( "/StatisticalSummaryDock/checked_%1" ).arg( stat ), true ).toBool();
64+
action->setChecked( checked );
65+
action->setData( stat );
66+
mStatsActions.insert( stat, action );
67+
connect( action, SIGNAL( triggered( bool ) ), this, SLOT( statActionTriggered( bool ) ) );
68+
mOptionsToolButton->addAction( action );
69+
}
70+
71+
//count of null values statistic:
72+
QAction* nullCountAction = new QAction( tr( "Missing (null) values" ), mOptionsToolButton );
73+
nullCountAction->setCheckable( true );
74+
bool checked = settings.value( QString( "/StatisticalSummaryDock/checked_missing_values" ), true ).toBool();
75+
nullCountAction->setChecked( checked );
76+
nullCountAction->setData( MISSING_VALUES );
77+
mStatsActions.insert( MISSING_VALUES, nullCountAction );
78+
connect( nullCountAction, SIGNAL( triggered( bool ) ), this, SLOT( statActionTriggered( bool ) ) );
79+
mOptionsToolButton->addAction( nullCountAction );
80+
}
81+
82+
QgsStatisticalSummaryDockWidget::~QgsStatisticalSummaryDockWidget()
83+
{
84+
85+
}
86+
87+
void QgsStatisticalSummaryDockWidget::refreshStatistics()
88+
{
89+
if ( !mLayer || !mFieldExpressionWidget->isValidExpression() )
90+
{
91+
mStatisticsTable->setRowCount( 0 );
92+
return;
93+
}
94+
95+
QString sourceFieldExp = mFieldExpressionWidget->currentField();
96+
97+
bool ok;
98+
bool selectedOnly = mSelectedOnlyCheckBox->isChecked();
99+
int missingValues = 0;
100+
QList< double > values = mLayer->getDoubleValues( sourceFieldExp, ok, selectedOnly, &missingValues );
101+
102+
if ( ! ok )
103+
{
104+
return;
105+
}
106+
107+
QList< QgsStatisticalSummary::Statistic > statsToDisplay;
108+
foreach ( QgsStatisticalSummary::Statistic stat, mDisplayStats )
109+
{
110+
if ( mStatsActions.value( stat )->isChecked() )
111+
statsToDisplay << stat;
112+
}
113+
114+
int extraRows = 0;
115+
if ( mStatsActions.value( MISSING_VALUES )->isChecked() )
116+
extraRows++;
117+
118+
QgsStatisticalSummary stats;
119+
stats.setStatistics( QgsStatisticalSummary::All );
120+
stats.calculate( values );
121+
122+
mStatisticsTable->setRowCount( statsToDisplay.count() + extraRows );
123+
mStatisticsTable->setColumnCount( 2 );
124+
125+
int row = 0;
126+
foreach ( QgsStatisticalSummary::Statistic stat, statsToDisplay )
127+
{
128+
QTableWidgetItem* nameItem = new QTableWidgetItem( QgsStatisticalSummary::displayName( stat ) );
129+
nameItem->setToolTip( nameItem->text() );
130+
nameItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
131+
mStatisticsTable->setItem( row, 0, nameItem );
132+
133+
QTableWidgetItem* valueItem = new QTableWidgetItem();
134+
if ( stats.count() != 0 )
135+
{
136+
valueItem->setText( QString::number( stats.statistic( stat ) ) );
137+
}
138+
valueItem->setToolTip( valueItem->text() );
139+
valueItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
140+
mStatisticsTable->setItem( row, 1, valueItem );
141+
142+
row++;
143+
}
144+
145+
if ( mStatsActions.value( MISSING_VALUES )->isChecked() )
146+
{
147+
QTableWidgetItem* nameItem = new QTableWidgetItem( tr( "Missing (null) values" ) );
148+
nameItem->setToolTip( nameItem->text() );
149+
nameItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
150+
mStatisticsTable->setItem( row, 0, nameItem );
151+
152+
QTableWidgetItem* valueItem = new QTableWidgetItem();
153+
if ( stats.count() != 0 || missingValues != 0 )
154+
{
155+
valueItem->setText( QString::number( missingValues ) );
156+
}
157+
valueItem->setToolTip( valueItem->text() );
158+
valueItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
159+
mStatisticsTable->setItem( row, 1, valueItem );
160+
row++;
161+
}
162+
}
163+
164+
void QgsStatisticalSummaryDockWidget::layerChanged( QgsMapLayer *layer )
165+
{
166+
QgsVectorLayer* newLayer = dynamic_cast< QgsVectorLayer* >( layer );
167+
if ( mLayer && mLayer != newLayer )
168+
{
169+
disconnect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( refreshStatistics() ) );
170+
}
171+
172+
mLayer = newLayer;
173+
174+
if ( mLayer )
175+
{
176+
connect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( refreshStatistics() ) );
177+
}
178+
179+
mFieldExpressionWidget->setLayer( mLayer );
180+
181+
if ( mFieldExpressionWidget->currentField().isEmpty() )
182+
{
183+
mStatisticsTable->setRowCount( 0 );
184+
}
185+
else
186+
{
187+
refreshStatistics();
188+
}
189+
}
190+
191+
void QgsStatisticalSummaryDockWidget::statActionTriggered( bool checked )
192+
{
193+
refreshStatistics();
194+
QAction* action = dynamic_cast<QAction*>( sender() );
195+
int stat = action->data().toInt();
196+
197+
QSettings settings;
198+
if ( stat >= 0 )
199+
{
200+
settings.setValue( QString( "/StatisticalSummaryDock/checked_%1" ).arg( stat ), checked );
201+
}
202+
else if ( stat == MISSING_VALUES )
203+
{
204+
settings.setValue( QString( "/StatisticalSummaryDock/checked_missing_values" ).arg( stat ), checked );
205+
}
206+
}
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/***************************************************************************
2+
qgsstatisticalsummarydockwidget.h
3+
---------------------------------
4+
begin : May 2015
5+
copyright : (C) 2015 by Nyall Dawson
6+
email : nyall dot dawson 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+
#ifndef QGSSTATISTICALSUMMARYDOCKWIDGET_H
16+
#define QGSSTATISTICALSUMMARYDOCKWIDGET_H
17+
18+
#include <QDockWidget>
19+
#include <QMap>
20+
#include "ui_qgsstatisticalsummarybase.h"
21+
22+
#include "qgsstatisticalsummary.h"
23+
24+
class QgsBrowserModel;
25+
class QModelIndex;
26+
class QgsDockBrowserTreeView;
27+
class QgsLayerItem;
28+
class QgsDataItem;
29+
class QgsBrowserTreeFilterProxyModel;
30+
31+
/**A dock widget which displays a statistical summary of the values in a field or expression
32+
*/
33+
class APP_EXPORT QgsStatisticalSummaryDockWidget : public QDockWidget, private Ui::QgsStatisticalSummaryWidgetBase
34+
{
35+
Q_OBJECT
36+
37+
public:
38+
QgsStatisticalSummaryDockWidget( QWidget *parent = 0 );
39+
~QgsStatisticalSummaryDockWidget();
40+
41+
public slots:
42+
43+
/**Recalculates the displayed statistics
44+
*/
45+
void refreshStatistics();
46+
47+
private slots:
48+
49+
void layerChanged( QgsMapLayer* layer );
50+
void statActionTriggered( bool checked );
51+
52+
private:
53+
54+
QgsVectorLayer* mLayer;
55+
56+
QMap< int, QAction* > mStatsActions;
57+
static QList< QgsStatisticalSummary::Statistic > mDisplayStats;
58+
};
59+
60+
#endif // QGSSTATISTICALSUMMARYDOCKWIDGET_H

0 commit comments

Comments
 (0)