[FEATURE] New statistical summary dock widget

Can display summary statistics (eg mean, standard deviation, ...)
for a field or expression from a vector layer.
This commit is contained in:
Nyall Dawson 2015-05-18 05:55:14 +10:00
parent 1078daf712
commit e7b7549c29
10 changed files with 542 additions and 1 deletions

View File

@ -69,6 +69,12 @@ class QgsStatisticalSummary
*/
void calculate( const QList<double>& values );
/** Returns the value of a specified statistic
* @param stat statistic to return
* @returns calculated value of statistic
*/
double statistic( Statistic stat ) const;
/** Returns calculated count of values
*/
int count() const;
@ -151,6 +157,11 @@ class QgsStatisticalSummary
*/
double interQuartileRange() const;
/** Returns the friendly display name for a statistic
* @param statistic statistic to return name for
*/
static QString displayName( Statistic statistic );
};
QFlags<QgsStatisticalSummary::Statistic> operator|(QgsStatisticalSummary::Statistic f1, QFlags<QgsStatisticalSummary::Statistic> f2);

View File

@ -103,6 +103,7 @@ SET(QGIS_APP_SRCS
qgsprojectproperties.cpp
qgsrastercalcdialog.cpp
qgsrasterlayerproperties.cpp
qgsstatisticalsummarydockwidget.cpp
qgstextannotationdialog.cpp
qgsshortcutsmanager.cpp
qgsguivectorlayertools.h
@ -248,6 +249,7 @@ SET (QGIS_APP_MOC_HDRS
qgsrasterlayerproperties.h
qgssnappingdialog.h
qgssponsors.h
qgsstatisticalsummarydockwidget.h
qgssvgannotationdialog.h
qgstextannotationdialog.h
qgstipgui.h

View File

@ -195,6 +195,7 @@
#include "qgssinglebandgrayrenderer.h"
#include "qgssnappingdialog.h"
#include "qgssponsors.h"
#include "qgsstatisticalsummarydockwidget.h"
#include "qgssvgannotationitem.h"
#include "qgstextannotationitem.h"
#include "qgstipgui.h"
@ -598,6 +599,10 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, QWidget * parent,
mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this );
mAdvancedDigitizingDockWidget->setObjectName( "AdvancedDigitizingTools" );
// Statistical Summary dock
mStatisticalSummaryDockWidget = new QgsStatisticalSummaryDockWidget( this );
mStatisticalSummaryDockWidget->setObjectName( "StatistalSummaryDockWidget" );
mSnappingUtils = new QgsMapCanvasSnappingUtils( mMapCanvas, this );
mMapCanvas->setSnappingUtils( mSnappingUtils );
connect( QgsProject::instance(), SIGNAL( snapSettingsChanged() ), mSnappingUtils, SLOT( readConfigFromProject() ) );
@ -642,6 +647,9 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, QWidget * parent,
addDockWidget( Qt::LeftDockWidgetArea, mAdvancedDigitizingDockWidget );
mAdvancedDigitizingDockWidget->hide();
addDockWidget( Qt::LeftDockWidgetArea, mStatisticalSummaryDockWidget );
mStatisticalSummaryDockWidget->hide();
QMainWindow::addDockWidget( Qt::BottomDockWidgetArea, mUserInputDockWidget );
mUserInputDockWidget->setFloating( true );
@ -1202,6 +1210,7 @@ void QgisApp::createActions()
connect( mActionSvgAnnotation, SIGNAL( triggered() ), this, SLOT( addSvgAnnotation() ) );
connect( mActionAnnotation, SIGNAL( triggered() ), this, SLOT( modifyAnnotation() ) );
connect( mActionLabeling, SIGNAL( triggered() ), this, SLOT( labeling() ) );
connect( mActionStatisticalSummary, SIGNAL( triggered( ) ), this, SLOT( showStatisticsDockWidget() ) );
// Layer Menu Items
@ -10483,6 +10492,12 @@ void QgisApp::osmExportDialog()
dlg.exec();
}
void QgisApp::showStatisticsDockWidget()
{
mStatisticalSummaryDockWidget->show();
mStatisticalSummaryDockWidget->raise();
}
#ifdef HAVE_TOUCH
bool QgisApp::gestureEvent( QGestureEvent *event )

View File

@ -76,6 +76,7 @@ class QgsBrowserDockWidget;
class QgsAdvancedDigitizingDockWidget;
class QgsSnappingDialog;
class QgsGPSInformationWidget;
class QgsStatisticalSummaryDockWidget;
class QgsDecorationItem;
@ -1226,6 +1227,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
/** Make the user feel dizzy */
void dizzy();
/** Shows the statistical summary dock widget and brings it to the foreground
*/
void showStatisticsDockWidget();
signals:
/** emitted when a key is pressed and we want non widget sublasses to be able
to pick up on this (e.g. maplayer) */
@ -1615,6 +1620,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QgsBrowserDockWidget *mBrowserWidget2;
QgsAdvancedDigitizingDockWidget *mAdvancedDigitizingDockWidget;
QgsStatisticalSummaryDockWidget* mStatisticalSummaryDockWidget;
QgsSnappingDialog *mSnappingDialog;

View File

@ -0,0 +1,206 @@
/***************************************************************************
qgsstatisticalsummarydockwidget.cpp
-----------------------------------
begin : May 2015
copyright : (C) 2015 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsstatisticalsummarydockwidget.h"
#include "qgsstatisticalsummary.h"
#include <QTableWidget>
#include <QAction>
#include <QSettings>
QList< QgsStatisticalSummary::Statistic > QgsStatisticalSummaryDockWidget::mDisplayStats =
QList< QgsStatisticalSummary::Statistic > () << QgsStatisticalSummary::Count
<< QgsStatisticalSummary::Sum
<< QgsStatisticalSummary::Mean
<< QgsStatisticalSummary::Median
<< QgsStatisticalSummary::StDev
<< QgsStatisticalSummary::StDevSample
<< QgsStatisticalSummary::Min
<< QgsStatisticalSummary::Max
<< QgsStatisticalSummary::Range
<< QgsStatisticalSummary::Minority
<< QgsStatisticalSummary::Majority
<< QgsStatisticalSummary::Variety
<< QgsStatisticalSummary::FirstQuartile
<< QgsStatisticalSummary::ThirdQuartile
<< QgsStatisticalSummary::InterQuartileRange;
#define MISSING_VALUES -1
QgsStatisticalSummaryDockWidget::QgsStatisticalSummaryDockWidget( QWidget *parent )
: QDockWidget( parent )
, mLayer( 0 )
{
setupUi( this );
mLayerComboBox->setFilters( QgsMapLayerProxyModel::VectorLayer );
mFieldExpressionWidget->setFilters( QgsFieldProxyModel::Numeric );
connect( mLayerComboBox, SIGNAL( layerChanged( QgsMapLayer* ) ), this, SLOT( layerChanged( QgsMapLayer* ) ) );
connect( mFieldExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( refreshStatistics() ) );
connect( mSelectedOnlyCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( refreshStatistics() ) );
connect( mButtonRefresh, SIGNAL( clicked( bool ) ), this, SLOT( refreshStatistics() ) );
if ( mLayerComboBox->currentLayer() )
{
mFieldExpressionWidget->setLayer( mLayerComboBox->currentLayer() );
}
QSettings settings;
foreach ( QgsStatisticalSummary::Statistic stat, mDisplayStats )
{
QAction* action = new QAction( QgsStatisticalSummary::displayName( stat ), mOptionsToolButton );
action->setCheckable( true );
bool checked = settings.value( QString( "/StatisticalSummaryDock/checked_%1" ).arg( stat ), true ).toBool();
action->setChecked( checked );
action->setData( stat );
mStatsActions.insert( stat, action );
connect( action, SIGNAL( triggered( bool ) ), this, SLOT( statActionTriggered( bool ) ) );
mOptionsToolButton->addAction( action );
}
//count of null values statistic:
QAction* nullCountAction = new QAction( tr( "Missing (null) values" ), mOptionsToolButton );
nullCountAction->setCheckable( true );
bool checked = settings.value( QString( "/StatisticalSummaryDock/checked_missing_values" ), true ).toBool();
nullCountAction->setChecked( checked );
nullCountAction->setData( MISSING_VALUES );
mStatsActions.insert( MISSING_VALUES, nullCountAction );
connect( nullCountAction, SIGNAL( triggered( bool ) ), this, SLOT( statActionTriggered( bool ) ) );
mOptionsToolButton->addAction( nullCountAction );
}
QgsStatisticalSummaryDockWidget::~QgsStatisticalSummaryDockWidget()
{
}
void QgsStatisticalSummaryDockWidget::refreshStatistics()
{
if ( !mLayer || !mFieldExpressionWidget->isValidExpression() )
{
mStatisticsTable->setRowCount( 0 );
return;
}
QString sourceFieldExp = mFieldExpressionWidget->currentField();
bool ok;
bool selectedOnly = mSelectedOnlyCheckBox->isChecked();
int missingValues = 0;
QList< double > values = mLayer->getDoubleValues( sourceFieldExp, ok, selectedOnly, &missingValues );
if ( ! ok )
{
return;
}
QList< QgsStatisticalSummary::Statistic > statsToDisplay;
foreach ( QgsStatisticalSummary::Statistic stat, mDisplayStats )
{
if ( mStatsActions.value( stat )->isChecked() )
statsToDisplay << stat;
}
int extraRows = 0;
if ( mStatsActions.value( MISSING_VALUES )->isChecked() )
extraRows++;
QgsStatisticalSummary stats;
stats.setStatistics( QgsStatisticalSummary::All );
stats.calculate( values );
mStatisticsTable->setRowCount( statsToDisplay.count() + extraRows );
mStatisticsTable->setColumnCount( 2 );
int row = 0;
foreach ( QgsStatisticalSummary::Statistic stat, statsToDisplay )
{
QTableWidgetItem* nameItem = new QTableWidgetItem( QgsStatisticalSummary::displayName( stat ) );
nameItem->setToolTip( nameItem->text() );
nameItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
mStatisticsTable->setItem( row, 0, nameItem );
QTableWidgetItem* valueItem = new QTableWidgetItem();
if ( stats.count() != 0 )
{
valueItem->setText( QString::number( stats.statistic( stat ) ) );
}
valueItem->setToolTip( valueItem->text() );
valueItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
mStatisticsTable->setItem( row, 1, valueItem );
row++;
}
if ( mStatsActions.value( MISSING_VALUES )->isChecked() )
{
QTableWidgetItem* nameItem = new QTableWidgetItem( tr( "Missing (null) values" ) );
nameItem->setToolTip( nameItem->text() );
nameItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
mStatisticsTable->setItem( row, 0, nameItem );
QTableWidgetItem* valueItem = new QTableWidgetItem();
if ( stats.count() != 0 || missingValues != 0 )
{
valueItem->setText( QString::number( missingValues ) );
}
valueItem->setToolTip( valueItem->text() );
valueItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
mStatisticsTable->setItem( row, 1, valueItem );
row++;
}
}
void QgsStatisticalSummaryDockWidget::layerChanged( QgsMapLayer *layer )
{
QgsVectorLayer* newLayer = dynamic_cast< QgsVectorLayer* >( layer );
if ( mLayer && mLayer != newLayer )
{
disconnect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( refreshStatistics() ) );
}
mLayer = newLayer;
if ( mLayer )
{
connect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( refreshStatistics() ) );
}
mFieldExpressionWidget->setLayer( mLayer );
if ( mFieldExpressionWidget->currentField().isEmpty() )
{
mStatisticsTable->setRowCount( 0 );
}
else
{
refreshStatistics();
}
}
void QgsStatisticalSummaryDockWidget::statActionTriggered( bool checked )
{
refreshStatistics();
QAction* action = dynamic_cast<QAction*>( sender() );
int stat = action->data().toInt();
QSettings settings;
if ( stat >= 0 )
{
settings.setValue( QString( "/StatisticalSummaryDock/checked_%1" ).arg( stat ), checked );
}
else if ( stat == MISSING_VALUES )
{
settings.setValue( QString( "/StatisticalSummaryDock/checked_missing_values" ).arg( stat ), checked );
}
}

View File

@ -0,0 +1,60 @@
/***************************************************************************
qgsstatisticalsummarydockwidget.h
---------------------------------
begin : May 2015
copyright : (C) 2015 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSSTATISTICALSUMMARYDOCKWIDGET_H
#define QGSSTATISTICALSUMMARYDOCKWIDGET_H
#include <QDockWidget>
#include <QMap>
#include "ui_qgsstatisticalsummarybase.h"
#include "qgsstatisticalsummary.h"
class QgsBrowserModel;
class QModelIndex;
class QgsDockBrowserTreeView;
class QgsLayerItem;
class QgsDataItem;
class QgsBrowserTreeFilterProxyModel;
/**A dock widget which displays a statistical summary of the values in a field or expression
*/
class APP_EXPORT QgsStatisticalSummaryDockWidget : public QDockWidget, private Ui::QgsStatisticalSummaryWidgetBase
{
Q_OBJECT
public:
QgsStatisticalSummaryDockWidget( QWidget *parent = 0 );
~QgsStatisticalSummaryDockWidget();
public slots:
/**Recalculates the displayed statistics
*/
void refreshStatistics();
private slots:
void layerChanged( QgsMapLayer* layer );
void statActionTriggered( bool checked );
private:
QgsVectorLayer* mLayer;
QMap< int, QAction* > mStatsActions;
static QList< QgsStatisticalSummary::Statistic > mDisplayStats;
};
#endif // QGSSTATISTICALSUMMARYDOCKWIDGET_H

View File

@ -16,7 +16,7 @@
#include "qgsstatisticalsummary.h"
#include <limits>
#include <qmath.h>
#include <QString>
QgsStatisticalSummary::QgsStatisticalSummary( Statistics stats )
: mStatistics( stats )
@ -175,3 +175,83 @@ void QgsStatisticalSummary::calculate( const QList<double> &values )
}
double QgsStatisticalSummary::statistic( QgsStatisticalSummary::Statistic stat ) const
{
switch ( stat )
{
case Count:
return mCount;
case Sum:
return mSum;
case Mean:
return mMean;
case Median:
return mMedian;
case StDev:
return mStdev;
case StDevSample:
return mSampleStdev;
case Min:
return mMin;
case Max:
return mMax;
case Range:
return mMax - mMin;
case Minority:
return mMinority;
case Majority:
return mMajority;
case Variety:
return mValueCount.count();
case FirstQuartile:
return mFirstQuartile;
case ThirdQuartile:
return mThirdQuartile;
case InterQuartileRange:
return mThirdQuartile - mFirstQuartile;
case All:
return 0;
}
return 0;
}
QString QgsStatisticalSummary::displayName( QgsStatisticalSummary::Statistic statistic )
{
switch ( statistic )
{
case Count:
return QT_TR_NOOP( "Count" );
case Sum:
return QT_TR_NOOP( "Sum" );
case Mean:
return QT_TR_NOOP( "Mean" );
case Median:
return QT_TR_NOOP( "Median" );
case StDev:
return QT_TR_NOOP( "St dev (pop)" );
case StDevSample:
return QT_TR_NOOP( "St dev (sample)" );
case Min:
return QT_TR_NOOP( "Minimum" );
case Max:
return QT_TR_NOOP( "Maximum" );
case Range:
return QT_TR_NOOP( "Range" );
case Minority:
return QT_TR_NOOP( "Minority" );
case Majority:
return QT_TR_NOOP( "Majority" );
case Variety:
return QT_TR_NOOP( "Variety" );
case FirstQuartile:
return QT_TR_NOOP( "Q1" );
case ThirdQuartile:
return QT_TR_NOOP( "Q3" );
case InterQuartileRange:
return QT_TR_NOOP( "IQR" );
case All:
return QString();
}
return QString();
}

View File

@ -85,6 +85,12 @@ class CORE_EXPORT QgsStatisticalSummary
*/
void calculate( const QList<double>& values );
/** Returns the value of a specified statistic
* @param stat statistic to return
* @returns calculated value of statistic
*/
double statistic( Statistic stat ) const;
/** Returns calculated count of values
*/
int count() const { return mCount; }
@ -167,6 +173,11 @@ class CORE_EXPORT QgsStatisticalSummary
*/
double interQuartileRange() const { return mThirdQuartile - mFirstQuartile; }
/** Returns the friendly display name for a statistic
* @param statistic statistic to return name for
*/
static QString displayName( Statistic statistic );
private:
Statistics mStatistics;

View File

@ -105,6 +105,7 @@
<addaction name="menuSelect"/>
<addaction name="mActionIdentify"/>
<addaction name="menuMeasure"/>
<addaction name="mActionStatisticalSummary"/>
<addaction name="separator"/>
<addaction name="mActionZoomFullExtent"/>
<addaction name="mActionZoomToLayer"/>
@ -410,6 +411,7 @@
<addaction name="mActionSelectByExpression"/>
<addaction name="mActionOpenTable"/>
<addaction name="mActionOpenFieldCalc"/>
<addaction name="mActionStatisticalSummary"/>
<addaction name="mActionMapTips"/>
<addaction name="mActionNewBookmark"/>
<addaction name="mActionShowBookmarks"/>
@ -2283,6 +2285,21 @@ Acts on currently active editable layer</string>
<string>New temporary scratch layer</string>
</property>
</action>
<action name="mActionStatisticalSummary">
<property name="checkable">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionSum.png</normaloff>:/images/themes/default/mActionSum.png</iconset>
</property>
<property name="text">
<string>Statistical Summary</string>
</property>
<property name="toolTip">
<string>Show statistical summary</string>
</property>
</action>
</widget>
<resources>
<include location="../../images/images.qrc"/>

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsStatisticalSummaryWidgetBase</class>
<widget class="QDockWidget" name="QgsStatisticalSummaryWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>291</width>
<height>376</height>
</rect>
</property>
<property name="windowTitle">
<string>Statistics</string>
</property>
<widget class="QWidget" name="mContents">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QgsMapLayerComboBox" name="mLayerComboBox"/>
</item>
<item>
<widget class="QgsFieldExpressionWidget" name="mFieldExpressionWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QTableWidget" name="mStatisticsTable">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Statistic</string>
</property>
</column>
<column>
<property name="text">
<string>Value</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="mSelectedOnlyCheckBox">
<property name="text">
<string>Selected features only</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="mButtonRefresh">
<property name="toolTip">
<string>Recalculate Statistics</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionDraw.svg</normaloff>:/images/themes/default/mActionDraw.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="mOptionsToolButton">
<property name="text">
<string>...</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>QgsFieldExpressionWidget</class>
<extends>QWidget</extends>
<header>qgsfieldexpressionwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsMapLayerComboBox</class>
<extends>QComboBox</extends>
<header>qgsmaplayercombobox.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../images/images.qrc"/>
</resources>
<connections/>
</ui>