QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 "qgsstatisticalsummary.h"
22 
23 #include <QSettings>
24 #include <QObject>
25 #include <QMouseEvent>
26 
27 // QWT Charting widget
28 #include <qwt_global.h>
29 #include <qwt_plot_canvas.h>
30 #include <qwt_plot.h>
31 #include <qwt_plot_curve.h>
32 #include <qwt_plot_grid.h>
33 #include <qwt_plot_marker.h>
34 #include <qwt_plot_picker.h>
35 #include <qwt_picker_machine.h>
36 #include <qwt_plot_layout.h>
37 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
38 #include <qwt_plot_renderer.h>
39 #include <qwt_plot_histogram.h>
40 #else
41 #include "../raster/qwt5_histogram_item.h"
42 #endif
43 
44 
46  : QWidget( parent )
47  , mVectorLayer( layer )
48  , mSourceFieldExp( fieldOrExp )
49  , mXAxisTitle( QObject::tr( "Value" ) )
50  , mYAxisTitle( QObject::tr( "Count" ) )
51 {
52  setupUi( this );
53 
54  mPlot = mpPlot;
55 
56  // hide the ugly canvas frame
57 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
58  QFrame* plotCanvasFrame = dynamic_cast<QFrame*>( mpPlot->canvas() );
59  if ( plotCanvasFrame )
60  plotCanvasFrame->setFrameStyle( QFrame::NoFrame );
61 #else
62  mpPlot->canvas()->setFrameStyle( QFrame::NoFrame );
63 #endif
64 
65  QSettings settings;
66  mMeanCheckBox->setChecked( settings.value( "/HistogramWidget/showMean", false ).toBool() );
67  mStdevCheckBox->setChecked( settings.value( "/HistogramWidget/showStdev", false ).toBool() );
68 
69  connect( mBinsSpinBox, SIGNAL( valueChanged( int ) ), this, SLOT( refresh() ) );
70  connect( mMeanCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( refresh() ) );
71  connect( mStdevCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( refresh() ) );
72  connect( mLoadValuesButton, SIGNAL( clicked() ), this, SLOT( refreshValues() ) );
73 
74  mGridPen = QPen( QColor( 0, 0, 0, 40 ) );
75  mMeanPen = QPen( QColor( 10, 10, 10, 220 ) );
76  mMeanPen.setStyle( Qt::DashLine );
77  mStdevPen = QPen( QColor( 30, 30, 30, 200 ) );
78  mStdevPen.setStyle( Qt::DashLine );
79 
80  if ( layer && !mSourceFieldExp.isEmpty() )
81  {
82  refresh();
83  }
84 }
85 
87 {
88  QSettings settings;
89  settings.setValue( "/HistogramWidget/showMean", mMeanCheckBox->isChecked() );
90  settings.setValue( "/HistogramWidget/showStdev", mStdevCheckBox->isChecked() );
91 }
92 
93 static bool _rangesByLower( const QgsRendererRangeV2& a, const QgsRendererRangeV2& b )
94 {
95  return a.lowerValue() < b.lowerValue();
96 }
97 
99 {
100  mRanges = ranges;
101  qSort( mRanges.begin(), mRanges.end(), _rangesByLower );
102 }
103 
105 {
106  mValues.clear();
107 
108  if ( !mVectorLayer || mSourceFieldExp.isEmpty() )
109  return;
110 
111  QApplication::setOverrideCursor( Qt::WaitCursor );
112 
113  bool ok;
114  mValues = mVectorLayer->getDoubleValues( mSourceFieldExp, ok );
115 
116  if ( ! ok )
117  {
119  return;
120  }
121 
122 
123  qSort( mValues.begin(), mValues.end() );
124  mHistogram.setValues( mValues );
125  mBinsSpinBox->blockSignals( true );
126  mBinsSpinBox->setValue( qMax( mHistogram.optimalNumberBins(), 30 ) );
127  mBinsSpinBox->blockSignals( false );
128 
130  mStats.calculate( mValues );
131 
132  mpPlot->setEnabled( true );
133  mMeanCheckBox->setEnabled( true );
134  mStdevCheckBox->setEnabled( true );
135  mBinsSpinBox->setEnabled( true );
136 
138 
139  //also force a redraw
140  refresh();
141 }
142 
144 {
145  drawHistogram();
146 }
147 
149 {
150  if ( layer == mVectorLayer )
151  return;
152 
153  mVectorLayer = layer;
154  clearHistogram();
155 }
156 
157 void QgsHistogramWidget::clearHistogram()
158 {
159  mValues.clear();
160  mHistogram.setValues( mValues );
161  refresh();
162 
163  mpPlot->setEnabled( false );
164  mMeanCheckBox->setEnabled( false );
165  mStdevCheckBox->setEnabled( false );
166  mBinsSpinBox->setEnabled( false );
167 }
168 
170 {
171  if ( fieldOrExp == mSourceFieldExp )
172  return;
173 
174  mSourceFieldExp = fieldOrExp;
175  clearHistogram();
176 }
177 
179 {
180  // clear plot
181  mpPlot->detachItems();
182 
183  //ensure all children get removed
184  mpPlot->setAutoDelete( true );
185  // Set axis titles
186  if ( !mXAxisTitle.isEmpty() )
187  mpPlot->setAxisTitle( QwtPlot::xBottom, mXAxisTitle );
188  if ( !mYAxisTitle.isEmpty() )
189  mpPlot->setAxisTitle( QwtPlot::yLeft, mYAxisTitle );
190  mpPlot->setAxisFont( QwtPlot::xBottom, this->font() );
191  mpPlot->setAxisFont( QwtPlot::yLeft, this->font() );
192  QFont titleFont = this->font();
193  titleFont.setBold( true );
194  QwtText xAxisText = mpPlot->axisTitle( QwtPlot::xBottom );
195  xAxisText.setFont( titleFont );
196  mpPlot->setAxisTitle( QwtPlot::xBottom, xAxisText );
197  QwtText yAxisText = mpPlot->axisTitle( QwtPlot::yLeft );
198  yAxisText.setFont( titleFont );
199  mpPlot->setAxisTitle( QwtPlot::yLeft, yAxisText );
200  mpPlot->setAxisAutoScale( QwtPlot::yLeft );
201  mpPlot->setAxisAutoScale( QwtPlot::xBottom );
202 
203  // add a grid
204  QwtPlotGrid * grid = new QwtPlotGrid();
205  grid->enableX( false );
206  grid->setPen( mGridPen );
207  grid->attach( mpPlot );
208 
209  // make colors list
210  mHistoColors.clear();
211  Q_FOREACH ( const QgsRendererRangeV2& range, mRanges )
212  {
213  mHistoColors << ( range.symbol() ? range.symbol()->color() : Qt::black );
214  }
215 
216  //draw histogram
217 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
218  QwtPlotHistogram *plotHistogram = nullptr;
219  plotHistogram = createPlotHistogram( !mRanges.isEmpty() ? mRanges.at( 0 ).label() : QString(),
220  !mRanges.isEmpty() ? QBrush( mHistoColors.at( 0 ) ) : mBrush,
221  !mRanges.isEmpty() ? Qt::NoPen : mPen );
222  QVector<QwtIntervalSample> dataHisto;
223 #else
224  HistogramItem *plotHistogramItem = nullptr;
225  plotHistogramItem = createHistoItem( !mRanges.isEmpty() ? mRanges.at( 0 ).label() : QString(),
226  !mRanges.isEmpty() ? QBrush( mHistoColors.at( 0 ) ) : mBrush,
227  !mRanges.isEmpty() ? Qt::NoPen : mPen );
228  // we safely assume that QT>=4.0 (min version is 4.7), therefore QwtArray is a QVector, so don't set size here
229  QwtArray<QwtDoubleInterval> intervalsHisto;
230  QwtArray<double> valuesHisto;
231 #endif
232 
233  int bins = mBinsSpinBox->value();
234  QList<double> edges = mHistogram.binEdges( bins );
235  QList<int> counts = mHistogram.counts( bins );
236 
237  int rangeIndex = 0;
238  int lastValue = 0;
239 
240  for ( int bin = 0; bin < bins; ++bin )
241  {
242  int binValue = counts.at( bin );
243 
244  //current bin crosses two graduated ranges, so we split between
245  //two histogram items
246  if ( rangeIndex < mRanges.count() - 1 && edges.at( bin ) > mRanges.at( rangeIndex ).upperValue() )
247  {
248  rangeIndex++;
249 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
250  plotHistogram->setSamples( dataHisto );
251  plotHistogram->attach( mpPlot );
252  plotHistogram = createPlotHistogram( mRanges.at( rangeIndex ).label(), mHistoColors.at( rangeIndex ) );
253  dataHisto.clear();
254  dataHisto << QwtIntervalSample( lastValue, mRanges.at( rangeIndex - 1 ).upperValue(), edges.at( bin ) );
255 #else
256  plotHistogramItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) );
257  plotHistogramItem->attach( mpPlot );
258  plotHistogramItem = createHistoItem( mRanges.at( rangeIndex ).label(), mHistoColors.at( rangeIndex ) );
259  intervalsHisto.clear();
260  valuesHisto.clear();
261  intervalsHisto.append( QwtDoubleInterval( mRanges.at( rangeIndex - 1 ).upperValue(), edges.at( bin ) ) );
262  valuesHisto.append( lastValue );
263 #endif
264  }
265 
266  double upperEdge = !mRanges.isEmpty() ? qMin( edges.at( bin + 1 ), mRanges.at( rangeIndex ).upperValue() )
267  : edges.at( bin + 1 );
268 
269 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
270  dataHisto << QwtIntervalSample( binValue, edges.at( bin ), upperEdge );
271 #else
272  intervalsHisto.append( QwtDoubleInterval( edges.at( bin ), upperEdge ) );
273  valuesHisto.append( double( binValue ) );
274 #endif
275 
276  lastValue = binValue;
277  }
278 
279 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
280  plotHistogram->setSamples( dataHisto );
281  plotHistogram->attach( mpPlot );
282 #else
283  plotHistogramItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) );
284  plotHistogramItem->attach( mpPlot );
285 #endif
286 
288  Q_FOREACH ( const QgsRendererRangeV2& range, mRanges )
289  {
290  QwtPlotMarker* rangeMarker = new QwtPlotMarker();
291  rangeMarker->attach( mpPlot );
292  rangeMarker->setLineStyle( QwtPlotMarker::VLine );
293  rangeMarker->setXValue( range.upperValue() );
294  rangeMarker->setLabel( QString::number( range.upperValue() ) );
295  rangeMarker->setLabelOrientation( Qt::Vertical );
296  rangeMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
297  rangeMarker->show();
298  mRangeMarkers << rangeMarker;
299  }
300 
301  if ( mMeanCheckBox->isChecked() )
302  {
303  QwtPlotMarker* meanMarker = new QwtPlotMarker();
304  meanMarker->attach( mpPlot );
305  meanMarker->setLineStyle( QwtPlotMarker::VLine );
306  meanMarker->setLinePen( mMeanPen );
307  meanMarker->setXValue( mStats.mean() );
308  meanMarker->setLabel( QString( QChar( 956 ) ) );
309  meanMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
310  meanMarker->show();
311  }
312 
313  if ( mStdevCheckBox->isChecked() )
314  {
315  QwtPlotMarker* stdev1Marker = new QwtPlotMarker();
316  stdev1Marker->attach( mpPlot );
317  stdev1Marker->setLineStyle( QwtPlotMarker::VLine );
318  stdev1Marker->setLinePen( mStdevPen );
319  stdev1Marker->setXValue( mStats.mean() - mStats.stDev() );
320  stdev1Marker->setLabel( QString( QChar( 963 ) ) );
321  stdev1Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
322  stdev1Marker->show();
323 
324  QwtPlotMarker* stdev2Marker = new QwtPlotMarker();
325  stdev2Marker->attach( mpPlot );
326  stdev2Marker->setLineStyle( QwtPlotMarker::VLine );
327  stdev2Marker->setLinePen( mStdevPen );
328  stdev2Marker->setXValue( mStats.mean() + mStats.stDev() );
329  stdev2Marker->setLabel( QString( QChar( 963 ) ) );
330  stdev2Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
331  stdev2Marker->show();
332  }
333 
334  mpPlot->setEnabled( true );
335  mpPlot->replot();
336 }
337 
338 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
339 QwtPlotHistogram* QgsHistogramWidget::createPlotHistogram( const QString& title, const QBrush& brush, const QPen& pen ) const
340 {
341  QwtPlotHistogram* histogram = new QwtPlotHistogram( title );
342  histogram->setBrush( brush );
343  if ( pen != Qt::NoPen )
344  {
345  histogram->setPen( pen );
346  }
347  else if ( brush.color().lightness() > 200 )
348  {
349  QPen p;
350  p.setColor( brush.color().darker( 150 ) );
351  p.setWidth( 0 );
352  p.setCosmetic( true );
353  histogram->setPen( p );
354  }
355  else
356  {
357  histogram->setPen( QPen( Qt::NoPen ) );
358  }
359  return histogram;
360 }
361 #else
362 HistogramItem * QgsHistogramWidget::createHistoItem( const QString& title, const QBrush& brush, const QPen& pen ) const
363 {
364  HistogramItem* item = new HistogramItem( title );
365  item->setColor( brush.color() );
366  item->setFlat( true );
367  item->setSpacing( 0 );
368  if ( pen != Qt::NoPen )
369  {
370  item->setPen( pen );
371  }
372  else if ( brush.color().lightness() > 200 )
373  {
374  QPen p;
375  p.setColor( brush.color().darker( 150 ) );
376  p.setWidth( 0 );
377  p.setCosmetic( true );
378  item->setPen( p );
379  }
380  return item;
381 }
382 #endif
383 
void clear()
void setStyle(Qt::PenStyle style)
void setupUi(QWidget *widget)
QColor darker(int factor) const
QList< double > binEdges(int bins) const
Returns a list of edges for the histogram for a specified number of bins.
static bool _rangesByLower(const QgsRendererRangeV2 &a, const QgsRendererRangeV2 &b)
void refreshValues()
Refreshes the values for the histogram by fetching them from the layer.
const T & at(int i) const
QList< double > getDoubleValues(const QString &fieldOrExpression, bool &ok, bool selectedOnly=false, int *nullCount=nullptr)
Fetches all double values from a specified field name or expression.
void setStatistics(const Statistics &stats)
Sets flags which specify which statistics will be calculated.
void setFrameStyle(int style)
QList< QwtPlotMarker *> mRangeMarkers
int lightness() const
double stDev() const
Returns population standard deviation.
const QColor & color() const
void setBold(bool enable)
void setValue(const QString &key, const QVariant &value)
void clear()
QString number(int n, int base)
int count(const T &value) const
void setGraduatedRanges(const QgsRangeList &ranges)
Sets the graduated ranges associated with the histogram.
void setSourceFieldExp(const QString &fieldOrExp)
Sets the source field or expression to use for values in the histogram.
bool isEmpty() const
bool isEmpty() const
void setCosmetic(bool cosmetic)
void setOverrideCursor(const QCursor &cursor)
void restoreOverrideCursor()
QBrush brush() const
Returns the brush used when drawing histogram bars.
void setColor(const QColor &color)
double mean() const
Returns calculated mean of values.
iterator end()
void setValues(const QList< double > &values)
Assigns numeric source values for the histogram.
QList< int > counts(int bins) const
Returns the calculated list of the counts for the histogram bins.
QgsHistogramWidget(QWidget *parent=nullptr, QgsVectorLayer *layer=nullptr, const QString &fieldOrExp=QString())
QgsHistogramWidget constructor.
const QFont & font() const
void refresh()
Redraws the histogram.
const T & at(int i) const
QVariant value(const QString &key, const QVariant &defaultValue) const
virtual void drawHistogram()
Updates and redraws the histogram.
void setWidth(int width)
QColor color() const
QPen pen() const
Returns the pen used when drawing histogram bars.
void calculate(const QList< double > &values)
Calculates summary statistics for a list of values.
bool toBool() const
Standard deviation of values.
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Represents a vector layer which manages a vector based data sets.
int optimalNumberBins() const
Returns the optimal number of bins for the source values, calculated using the Freedman-Diaconis rule...
iterator begin()
QgsVectorLayer * layer()
Returns the layer currently associated with the widget.
void setLayer(QgsVectorLayer *layer)
Sets the vector layer associated with the histogram.