QGIS API Documentation  3.25.0-Master (10b47c2603)
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"
22 #include "qgsstatisticalsummary.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 
43 QgsHistogramWidget::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 
87 static 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 {
139  drawHistogram();
140 }
141 
143 {
144  if ( layer == mVectorLayer )
145  return;
146 
147  mVectorLayer = layer;
148  clearHistogram();
149 }
150 
151 void 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 
163 void 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 
302 QwtPlotHistogram *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 
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.
QgsVectorLayer * layer()
Returns the layer currently associated with the widget.
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.
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:62
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.
@ StDev
Standard deviation of values.
double mean() const
Returns calculated mean of values.
double stDev() const
Returns population standard deviation.
void setStatistics(QgsStatisticalSummary::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