QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgshistogramwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgshistogramwidget.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/***************************************************************************
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
18#include "qgshistogramwidget.h"
19#include "qgsapplication.h"
20#include "qgsvectorlayer.h"
21#include "qgsvectorlayerutils.h"
23#include "qgssettings.h"
24
25#include <QObject>
26#include <QMouseEvent>
27
28// QWT Charting widget
29#include <qwt_global.h>
30#include <qwt_plot_canvas.h>
31#include <qwt_plot.h>
32#include <qwt_plot_curve.h>
33#include <qwt_plot_grid.h>
34#include <qwt_plot_marker.h>
35#include <qwt_plot_picker.h>
36#include <qwt_picker_machine.h>
37#include <qwt_plot_layout.h>
38#include <qwt_plot_renderer.h>
39#include <qwt_plot_histogram.h>
40#include <qwt_text.h>
41
42
43QgsHistogramWidget::QgsHistogramWidget( QWidget *parent, QgsVectorLayer *layer, const QString &fieldOrExp )
44 : QWidget( parent )
45 , mVectorLayer( layer )
46 , mSourceFieldExp( fieldOrExp )
47 , mXAxisTitle( QObject::tr( "Value" ) )
48 , mYAxisTitle( QObject::tr( "Count" ) )
49{
50 setupUi( this );
51
52 mPlot = mpPlot;
53
54 // hide the ugly canvas frame
55 QFrame *plotCanvasFrame = dynamic_cast<QFrame *>( mpPlot->canvas() );
56 if ( plotCanvasFrame )
57 plotCanvasFrame->setFrameStyle( QFrame::NoFrame );
58
59 QgsSettings settings;
60 mMeanCheckBox->setChecked( settings.value( QStringLiteral( "HistogramWidget/showMean" ), false ).toBool() );
61 mStdevCheckBox->setChecked( settings.value( QStringLiteral( "HistogramWidget/showStdev" ), false ).toBool() );
62
63 connect( mBinsSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsHistogramWidget::refresh );
64 connect( mMeanCheckBox, &QAbstractButton::toggled, this, &QgsHistogramWidget::refresh );
65 connect( mStdevCheckBox, &QAbstractButton::toggled, this, &QgsHistogramWidget::refresh );
66 connect( mLoadValuesButton, &QAbstractButton::clicked, this, &QgsHistogramWidget::refreshValues );
67
68 mGridPen = QPen( QColor( 0, 0, 0, 40 ) );
69 mMeanPen = QPen( QColor( 10, 10, 10, 220 ) );
70 mMeanPen.setStyle( Qt::DashLine );
71 mStdevPen = QPen( QColor( 30, 30, 30, 200 ) );
72 mStdevPen.setStyle( Qt::DashLine );
73
74 if ( layer && !mSourceFieldExp.isEmpty() )
75 {
76 refresh();
77 }
78}
79
81{
82 QgsSettings settings;
83 settings.setValue( QStringLiteral( "HistogramWidget/showMean" ), mMeanCheckBox->isChecked() );
84 settings.setValue( QStringLiteral( "HistogramWidget/showStdev" ), mStdevCheckBox->isChecked() );
85}
86
87static bool _rangesByLower( const QgsRendererRange &a, const QgsRendererRange &b )
88{
89 return a.lowerValue() < b.lowerValue();
90}
91
93{
94 mRanges = ranges;
95 std::sort( mRanges.begin(), mRanges.end(), _rangesByLower );
96}
97
99{
100 mValues.clear();
101
102 if ( !mVectorLayer || mSourceFieldExp.isEmpty() )
103 return;
104
105 QApplication::setOverrideCursor( Qt::WaitCursor );
106
107 bool ok;
108 mValues = QgsVectorLayerUtils::getDoubleValues( mVectorLayer, mSourceFieldExp, ok );
109
110 if ( ! ok )
111 {
112 QApplication::restoreOverrideCursor();
113 return;
114 }
115
116
117 std::sort( mValues.begin(), mValues.end() );
118 mHistogram.setValues( mValues );
119 mBinsSpinBox->blockSignals( true );
120 mBinsSpinBox->setValue( std::max( mHistogram.optimalNumberBins(), 30 ) );
121 mBinsSpinBox->blockSignals( false );
122
124 mStats.calculate( mValues );
125
126 mpPlot->setEnabled( true );
127 mMeanCheckBox->setEnabled( true );
128 mStdevCheckBox->setEnabled( true );
129 mBinsSpinBox->setEnabled( true );
130
131 QApplication::restoreOverrideCursor();
132
133 //also force a redraw
134 refresh();
135}
136
138{
140}
141
143{
144 if ( layer == mVectorLayer )
145 return;
146
147 mVectorLayer = layer;
148 clearHistogram();
149}
150
151void QgsHistogramWidget::clearHistogram()
152{
153 mValues.clear();
154 mHistogram.setValues( mValues );
155 refresh();
156
157 mpPlot->setEnabled( false );
158 mMeanCheckBox->setEnabled( false );
159 mStdevCheckBox->setEnabled( false );
160 mBinsSpinBox->setEnabled( false );
161}
162
163void QgsHistogramWidget::setSourceFieldExp( const QString &fieldOrExp )
164{
165 if ( fieldOrExp == mSourceFieldExp )
166 return;
167
168 mSourceFieldExp = fieldOrExp;
169 clearHistogram();
170}
171
173{
174 // clear plot
175 mpPlot->detachItems();
176
177 //ensure all children get removed
178 mpPlot->setAutoDelete( true );
179 // Set axis titles
180 if ( !mXAxisTitle.isEmpty() )
181 mpPlot->setAxisTitle( QwtPlot::xBottom, mXAxisTitle );
182 if ( !mYAxisTitle.isEmpty() )
183 mpPlot->setAxisTitle( QwtPlot::yLeft, mYAxisTitle );
184 mpPlot->setAxisFont( QwtPlot::xBottom, this->font() );
185 mpPlot->setAxisFont( QwtPlot::yLeft, this->font() );
186 QFont titleFont = this->font();
187 titleFont.setBold( true );
188 QwtText xAxisText = mpPlot->axisTitle( QwtPlot::xBottom );
189 xAxisText.setFont( titleFont );
190 mpPlot->setAxisTitle( QwtPlot::xBottom, xAxisText );
191 QwtText yAxisText = mpPlot->axisTitle( QwtPlot::yLeft );
192 yAxisText.setFont( titleFont );
193 mpPlot->setAxisTitle( QwtPlot::yLeft, yAxisText );
194 mpPlot->setAxisAutoScale( QwtPlot::yLeft );
195 mpPlot->setAxisAutoScale( QwtPlot::xBottom );
196
197 // add a grid
198 QwtPlotGrid *grid = new QwtPlotGrid();
199 grid->enableX( false );
200 grid->setPen( mGridPen );
201 grid->attach( mpPlot );
202
203 // make colors list
204 mHistoColors.clear();
205 for ( const QgsRendererRange &range : std::as_const( mRanges ) )
206 {
207 mHistoColors << ( range.symbol() ? range.symbol()->color() : Qt::black );
208 }
209
210 //draw histogram
211 QwtPlotHistogram *plotHistogram = nullptr;
212 plotHistogram = createPlotHistogram( !mRanges.isEmpty() ? mRanges.at( 0 ).label() : QString(),
213 !mRanges.isEmpty() ? QBrush( mHistoColors.at( 0 ) ) : mBrush,
214 !mRanges.isEmpty() ? Qt::NoPen : mPen );
215 QVector<QwtIntervalSample> dataHisto;
216
217 int bins = mBinsSpinBox->value();
218 QList<double> edges = mHistogram.binEdges( bins );
219 QList<int> counts = mHistogram.counts( bins );
220
221 int rangeIndex = 0;
222 int lastValue = 0;
223
224 for ( int bin = 0; bin < bins; ++bin )
225 {
226 int binValue = counts.at( bin );
227
228 //current bin crosses two graduated ranges, so we split between
229 //two histogram items
230 if ( rangeIndex < mRanges.count() - 1 && edges.at( bin ) > mRanges.at( rangeIndex ).upperValue() )
231 {
232 rangeIndex++;
233 plotHistogram->setSamples( dataHisto );
234 plotHistogram->attach( mpPlot );
235 plotHistogram = createPlotHistogram( mRanges.at( rangeIndex ).label(), mHistoColors.at( rangeIndex ) );
236 dataHisto.clear();
237 dataHisto << QwtIntervalSample( lastValue, mRanges.at( rangeIndex - 1 ).upperValue(), edges.at( bin ) );
238 }
239
240 double upperEdge = !mRanges.isEmpty() ? std::min( edges.at( bin + 1 ), mRanges.at( rangeIndex ).upperValue() )
241 : edges.at( bin + 1 );
242
243 dataHisto << QwtIntervalSample( binValue, edges.at( bin ), upperEdge );
244
245 lastValue = binValue;
246 }
247
248 plotHistogram->setSamples( dataHisto );
249 plotHistogram->attach( mpPlot );
250
251 mRangeMarkers.clear();
252 for ( const QgsRendererRange &range : std::as_const( mRanges ) )
253 {
254 QwtPlotMarker *rangeMarker = new QwtPlotMarker();
255 rangeMarker->attach( mpPlot );
256 rangeMarker->setLineStyle( QwtPlotMarker::VLine );
257 rangeMarker->setXValue( range.upperValue() );
258 rangeMarker->setLabel( QLocale().toString( range.upperValue() ) );
259 rangeMarker->setLabelOrientation( Qt::Vertical );
260 rangeMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
261 rangeMarker->show();
262 mRangeMarkers << rangeMarker;
263 }
264
265 if ( mMeanCheckBox->isChecked() )
266 {
267 QwtPlotMarker *meanMarker = new QwtPlotMarker();
268 meanMarker->attach( mpPlot );
269 meanMarker->setLineStyle( QwtPlotMarker::VLine );
270 meanMarker->setLinePen( mMeanPen );
271 meanMarker->setXValue( mStats.mean() );
272 meanMarker->setLabel( QString( QChar( 956 ) ) );
273 meanMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
274 meanMarker->show();
275 }
276
277 if ( mStdevCheckBox->isChecked() )
278 {
279 QwtPlotMarker *stdev1Marker = new QwtPlotMarker();
280 stdev1Marker->attach( mpPlot );
281 stdev1Marker->setLineStyle( QwtPlotMarker::VLine );
282 stdev1Marker->setLinePen( mStdevPen );
283 stdev1Marker->setXValue( mStats.mean() - mStats.stDev() );
284 stdev1Marker->setLabel( QString( QChar( 963 ) ) );
285 stdev1Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
286 stdev1Marker->show();
287
288 QwtPlotMarker *stdev2Marker = new QwtPlotMarker();
289 stdev2Marker->attach( mpPlot );
290 stdev2Marker->setLineStyle( QwtPlotMarker::VLine );
291 stdev2Marker->setLinePen( mStdevPen );
292 stdev2Marker->setXValue( mStats.mean() + mStats.stDev() );
293 stdev2Marker->setLabel( QString( QChar( 963 ) ) );
294 stdev2Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
295 stdev2Marker->show();
296 }
297
298 mpPlot->setEnabled( true );
299 mpPlot->replot();
300}
301
302QwtPlotHistogram *QgsHistogramWidget::createPlotHistogram( const QString &title, const QBrush &brush, const QPen &pen ) const
303{
304 QwtPlotHistogram *histogram = new QwtPlotHistogram( title );
305 histogram->setBrush( brush );
306 if ( pen != Qt::NoPen )
307 {
308 histogram->setPen( pen );
309 }
310 else if ( brush.color().lightness() > 200 )
311 {
312 QPen p;
313 p.setColor( brush.color().darker( 150 ) );
314 p.setWidth( 0 );
315 p.setCosmetic( true );
316 histogram->setPen( p );
317 }
318 else
319 {
320 histogram->setPen( QPen( Qt::NoPen ) );
321 }
322 return histogram;
323}
324
@ StDev
Standard deviation of values.
void refresh()
Redraws the histogram.
QList< QwtPlotMarker * > mRangeMarkers
QPen pen() const
Returns the pen used when drawing histogram bars.
QgsHistogramWidget(QWidget *parent=nullptr, QgsVectorLayer *layer=nullptr, const QString &fieldOrExp=QString())
QgsHistogramWidget constructor.
virtual void drawHistogram()
Updates and redraws the histogram.
void setLayer(QgsVectorLayer *layer)
Sets the vector layer associated with the histogram.
void setGraduatedRanges(const QgsRangeList &ranges)
Sets the graduated ranges associated with the histogram.
QBrush brush() const
Returns the brush used when drawing histogram bars.
QgsVectorLayer * layer()
Returns the layer currently associated with the widget.
void setSourceFieldExp(const QString &fieldOrExp)
Sets the source field or expression to use for values in the histogram.
void refreshValues()
Refreshes the values for the histogram by fetching them from the layer.
QList< int > counts(int bins) const
Returns the calculated list of the counts for the histogram bins.
int optimalNumberBins() const
Returns the optimal number of bins for the source values, calculated using the Freedman-Diaconis rule...
QList< double > binEdges(int bins) const
Returns a list of edges for the histogram for a specified number of bins.
void setValues(const QList< double > &values)
Assigns numeric source values for the histogram.
double lowerValue() const
Returns the lower bound of the range.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void calculate(const QList< double > &values)
Calculates summary statistics for a list of values.
double mean() const
Returns calculated mean of values.
double stDev() const
Returns population standard deviation.
void setStatistics(Qgis::Statistics stats)
Sets flags which specify which statistics will be calculated.
static QList< double > getDoubleValues(const QgsVectorLayer *layer, const QString &fieldOrExpression, bool &ok, bool selectedOnly=false, int *nullCount=nullptr, QgsFeedback *feedback=nullptr)
Fetches all double values from a specified field name or expression.
Represents a vector layer which manages a vector based data sets.
QList< QgsRendererRange > QgsRangeList