QGIS API Documentation  2.14.0-Essen
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() ? -1 : 0;
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
static bool _rangesByLower(const QgsRendererRangeV2 &a, const QgsRendererRangeV2 &b)
double mean() const
Returns calculated mean of values.
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
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.
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.
bool isEmpty() const
bool isEmpty() const
void setCosmetic(bool cosmetic)
QBrush brush() const
Returns the brush used when drawing histogram bars.
QgsSymbolV2 * symbol() const
void setOverrideCursor(const QCursor &cursor)
void restoreOverrideCursor()
void setColor(const QColor &color)
iterator end()
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.
const QFont & font() const
void refresh()
Redraws the histogram.
int optimalNumberBins() const
Returns the optimal number of bins for the source values, calculated using the Freedman-Diaconis rule...
const T & at(int i) const
QVariant value(const QString &key, const QVariant &defaultValue) const
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 setWidth(int width)
void calculate(const QList< double > &values)
Calculates summary statistics for a list of values.
bool toBool() const
QPen pen() const
Returns the pen used when drawing histogram bars.
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.
iterator begin()
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.
QColor color() const
#define tr(sourceText)