QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 
41 
42 QgsHistogramWidget::QgsHistogramWidget( QWidget *parent, QgsVectorLayer *layer, const QString &fieldOrExp )
43  : QWidget( parent )
44  , mVectorLayer( layer )
45  , mSourceFieldExp( fieldOrExp )
46  , mXAxisTitle( QObject::tr( "Value" ) )
47  , mYAxisTitle( QObject::tr( "Count" ) )
48 {
49  setupUi( this );
50 
51  mPlot = mpPlot;
52 
53  // hide the ugly canvas frame
54  QFrame *plotCanvasFrame = dynamic_cast<QFrame *>( mpPlot->canvas() );
55  if ( plotCanvasFrame )
56  plotCanvasFrame->setFrameStyle( QFrame::NoFrame );
57 
58  QgsSettings settings;
59  mMeanCheckBox->setChecked( settings.value( QStringLiteral( "HistogramWidget/showMean" ), false ).toBool() );
60  mStdevCheckBox->setChecked( settings.value( QStringLiteral( "HistogramWidget/showStdev" ), false ).toBool() );
61 
62  connect( mBinsSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsHistogramWidget::refresh );
63  connect( mMeanCheckBox, &QAbstractButton::toggled, this, &QgsHistogramWidget::refresh );
64  connect( mStdevCheckBox, &QAbstractButton::toggled, this, &QgsHistogramWidget::refresh );
65  connect( mLoadValuesButton, &QAbstractButton::clicked, this, &QgsHistogramWidget::refreshValues );
66 
67  mGridPen = QPen( QColor( 0, 0, 0, 40 ) );
68  mMeanPen = QPen( QColor( 10, 10, 10, 220 ) );
69  mMeanPen.setStyle( Qt::DashLine );
70  mStdevPen = QPen( QColor( 30, 30, 30, 200 ) );
71  mStdevPen.setStyle( Qt::DashLine );
72 
73  if ( layer && !mSourceFieldExp.isEmpty() )
74  {
75  refresh();
76  }
77 }
78 
80 {
81  QgsSettings settings;
82  settings.setValue( QStringLiteral( "HistogramWidget/showMean" ), mMeanCheckBox->isChecked() );
83  settings.setValue( QStringLiteral( "HistogramWidget/showStdev" ), mStdevCheckBox->isChecked() );
84 }
85 
86 static bool _rangesByLower( const QgsRendererRange &a, const QgsRendererRange &b )
87 {
88  return a.lowerValue() < b.lowerValue();
89 }
90 
92 {
93  mRanges = ranges;
94  std::sort( mRanges.begin(), mRanges.end(), _rangesByLower );
95 }
96 
98 {
99  mValues.clear();
100 
101  if ( !mVectorLayer || mSourceFieldExp.isEmpty() )
102  return;
103 
104  QApplication::setOverrideCursor( Qt::WaitCursor );
105 
106  bool ok;
107  mValues = QgsVectorLayerUtils::getDoubleValues( mVectorLayer, mSourceFieldExp, ok );
108 
109  if ( ! ok )
110  {
111  QApplication::restoreOverrideCursor();
112  return;
113  }
114 
115 
116  std::sort( mValues.begin(), mValues.end() );
117  mHistogram.setValues( mValues );
118  mBinsSpinBox->blockSignals( true );
119  mBinsSpinBox->setValue( std::max( mHistogram.optimalNumberBins(), 30 ) );
120  mBinsSpinBox->blockSignals( false );
121 
123  mStats.calculate( mValues );
124 
125  mpPlot->setEnabled( true );
126  mMeanCheckBox->setEnabled( true );
127  mStdevCheckBox->setEnabled( true );
128  mBinsSpinBox->setEnabled( true );
129 
130  QApplication::restoreOverrideCursor();
131 
132  //also force a redraw
133  refresh();
134 }
135 
137 {
138  drawHistogram();
139 }
140 
142 {
143  if ( layer == mVectorLayer )
144  return;
145 
146  mVectorLayer = layer;
147  clearHistogram();
148 }
149 
150 void QgsHistogramWidget::clearHistogram()
151 {
152  mValues.clear();
153  mHistogram.setValues( mValues );
154  refresh();
155 
156  mpPlot->setEnabled( false );
157  mMeanCheckBox->setEnabled( false );
158  mStdevCheckBox->setEnabled( false );
159  mBinsSpinBox->setEnabled( false );
160 }
161 
162 void QgsHistogramWidget::setSourceFieldExp( const QString &fieldOrExp )
163 {
164  if ( fieldOrExp == mSourceFieldExp )
165  return;
166 
167  mSourceFieldExp = fieldOrExp;
168  clearHistogram();
169 }
170 
172 {
173  // clear plot
174  mpPlot->detachItems();
175 
176  //ensure all children get removed
177  mpPlot->setAutoDelete( true );
178  // Set axis titles
179  if ( !mXAxisTitle.isEmpty() )
180  mpPlot->setAxisTitle( QwtPlot::xBottom, mXAxisTitle );
181  if ( !mYAxisTitle.isEmpty() )
182  mpPlot->setAxisTitle( QwtPlot::yLeft, mYAxisTitle );
183  mpPlot->setAxisFont( QwtPlot::xBottom, this->font() );
184  mpPlot->setAxisFont( QwtPlot::yLeft, this->font() );
185  QFont titleFont = this->font();
186  titleFont.setBold( true );
187  QwtText xAxisText = mpPlot->axisTitle( QwtPlot::xBottom );
188  xAxisText.setFont( titleFont );
189  mpPlot->setAxisTitle( QwtPlot::xBottom, xAxisText );
190  QwtText yAxisText = mpPlot->axisTitle( QwtPlot::yLeft );
191  yAxisText.setFont( titleFont );
192  mpPlot->setAxisTitle( QwtPlot::yLeft, yAxisText );
193  mpPlot->setAxisAutoScale( QwtPlot::yLeft );
194  mpPlot->setAxisAutoScale( QwtPlot::xBottom );
195 
196  // add a grid
197  QwtPlotGrid *grid = new QwtPlotGrid();
198  grid->enableX( false );
199  grid->setPen( mGridPen );
200  grid->attach( mpPlot );
201 
202  // make colors list
203  mHistoColors.clear();
204  for ( const QgsRendererRange &range : std::as_const( mRanges ) )
205  {
206  mHistoColors << ( range.symbol() ? range.symbol()->color() : Qt::black );
207  }
208 
209  //draw histogram
210  QwtPlotHistogram *plotHistogram = nullptr;
211  plotHistogram = createPlotHistogram( !mRanges.isEmpty() ? mRanges.at( 0 ).label() : QString(),
212  !mRanges.isEmpty() ? QBrush( mHistoColors.at( 0 ) ) : mBrush,
213  !mRanges.isEmpty() ? Qt::NoPen : mPen );
214  QVector<QwtIntervalSample> dataHisto;
215 
216  int bins = mBinsSpinBox->value();
217  QList<double> edges = mHistogram.binEdges( bins );
218  QList<int> counts = mHistogram.counts( bins );
219 
220  int rangeIndex = 0;
221  int lastValue = 0;
222 
223  for ( int bin = 0; bin < bins; ++bin )
224  {
225  int binValue = counts.at( bin );
226 
227  //current bin crosses two graduated ranges, so we split between
228  //two histogram items
229  if ( rangeIndex < mRanges.count() - 1 && edges.at( bin ) > mRanges.at( rangeIndex ).upperValue() )
230  {
231  rangeIndex++;
232  plotHistogram->setSamples( dataHisto );
233  plotHistogram->attach( mpPlot );
234  plotHistogram = createPlotHistogram( mRanges.at( rangeIndex ).label(), mHistoColors.at( rangeIndex ) );
235  dataHisto.clear();
236  dataHisto << QwtIntervalSample( lastValue, mRanges.at( rangeIndex - 1 ).upperValue(), edges.at( bin ) );
237  }
238 
239  double upperEdge = !mRanges.isEmpty() ? std::min( edges.at( bin + 1 ), mRanges.at( rangeIndex ).upperValue() )
240  : edges.at( bin + 1 );
241 
242  dataHisto << QwtIntervalSample( binValue, edges.at( bin ), upperEdge );
243 
244  lastValue = binValue;
245  }
246 
247  plotHistogram->setSamples( dataHisto );
248  plotHistogram->attach( mpPlot );
249 
250  mRangeMarkers.clear();
251  for ( const QgsRendererRange &range : std::as_const( mRanges ) )
252  {
253  QwtPlotMarker *rangeMarker = new QwtPlotMarker();
254  rangeMarker->attach( mpPlot );
255  rangeMarker->setLineStyle( QwtPlotMarker::VLine );
256  rangeMarker->setXValue( range.upperValue() );
257  rangeMarker->setLabel( QString::number( range.upperValue() ) );
258  rangeMarker->setLabelOrientation( Qt::Vertical );
259  rangeMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
260  rangeMarker->show();
261  mRangeMarkers << rangeMarker;
262  }
263 
264  if ( mMeanCheckBox->isChecked() )
265  {
266  QwtPlotMarker *meanMarker = new QwtPlotMarker();
267  meanMarker->attach( mpPlot );
268  meanMarker->setLineStyle( QwtPlotMarker::VLine );
269  meanMarker->setLinePen( mMeanPen );
270  meanMarker->setXValue( mStats.mean() );
271  meanMarker->setLabel( QString( QChar( 956 ) ) );
272  meanMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
273  meanMarker->show();
274  }
275 
276  if ( mStdevCheckBox->isChecked() )
277  {
278  QwtPlotMarker *stdev1Marker = new QwtPlotMarker();
279  stdev1Marker->attach( mpPlot );
280  stdev1Marker->setLineStyle( QwtPlotMarker::VLine );
281  stdev1Marker->setLinePen( mStdevPen );
282  stdev1Marker->setXValue( mStats.mean() - mStats.stDev() );
283  stdev1Marker->setLabel( QString( QChar( 963 ) ) );
284  stdev1Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
285  stdev1Marker->show();
286 
287  QwtPlotMarker *stdev2Marker = new QwtPlotMarker();
288  stdev2Marker->attach( mpPlot );
289  stdev2Marker->setLineStyle( QwtPlotMarker::VLine );
290  stdev2Marker->setLinePen( mStdevPen );
291  stdev2Marker->setXValue( mStats.mean() + mStats.stDev() );
292  stdev2Marker->setLabel( QString( QChar( 963 ) ) );
293  stdev2Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
294  stdev2Marker->show();
295  }
296 
297  mpPlot->setEnabled( true );
298  mpPlot->replot();
299 }
300 
301 QwtPlotHistogram *QgsHistogramWidget::createPlotHistogram( const QString &title, const QBrush &brush, const QPen &pen ) const
302 {
303  QwtPlotHistogram *histogram = new QwtPlotHistogram( title );
304  histogram->setBrush( brush );
305  if ( pen != Qt::NoPen )
306  {
307  histogram->setPen( pen );
308  }
309  else if ( brush.color().lightness() > 200 )
310  {
311  QPen p;
312  p.setColor( brush.color().darker( 150 ) );
313  p.setWidth( 0 );
314  p.setCosmetic( true );
315  histogram->setPen( p );
316  }
317  else
318  {
319  histogram->setPen( QPen( Qt::NoPen ) );
320  }
321  return histogram;
322 }
323 
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
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