QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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  Q_FOREACH ( const QgsRendererRange &range, 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  Q_FOREACH ( const QgsRendererRange &range, 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 
QList< QgsRendererRange > QgsRangeList
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
double mean() const
Returns calculated mean of values.
void refreshValues()
Refreshes the values for the histogram by fetching them from the layer.
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.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
QgsSymbol * symbol() const
void setGraduatedRanges(const QgsRangeList &ranges)
Sets the graduated ranges associated with the histogram.
double stDev() const
Returns population standard deviation.
void setSourceFieldExp(const QString &fieldOrExp)
Sets the source field or expression to use for values in the histogram.
QBrush brush() const
Returns the brush used when drawing histogram bars.
void setValues(const QList< double > &values)
Assigns numeric source values for the histogram.
QgsHistogramWidget(QWidget *parent=nullptr, QgsVectorLayer *layer=nullptr, const QString &fieldOrExp=QString())
QgsHistogramWidget constructor.
void refresh()
Redraws the histogram.
int optimalNumberBins() const
Returns the optimal number of bins for the source values, calculated using the Freedman-Diaconis rule...
QList< int > counts(int bins) const
Returns the calculated list of the counts for the histogram bins.
virtual void drawHistogram()
Updates and redraws the histogram.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void setStatistics(QgsStatisticalSummary::Statistics stats)
Sets flags which specify which statistics will be calculated.
void calculate(const QList< double > &values)
Calculates summary statistics for a list of values.
QPen pen() const
Returns the pen used when drawing histogram bars.
Standard deviation of values.
QColor color() const
Returns the symbol&#39;s color.
Definition: qgssymbol.cpp:459
Represents a vector layer which manages a vector based data sets.
QList< QwtPlotMarker * > mRangeMarkers
QList< double > binEdges(int bins) const
Returns a list of edges for the histogram for a specified number of bins.
QgsVectorLayer * layer()
Returns the layer currently associated with the widget.
void setLayer(QgsVectorLayer *layer)
Sets the vector layer associated with the histogram.