QGIS API Documentation  2.12.0-Lyon
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 = 0;
219  plotHistogram = createPlotHistogram( mRanges.count() > 0 ? mRanges.at( 0 ).label() : QString(),
220  mRanges.count() > 0 ? QBrush( mHistoColors.at( 0 ) ) : mBrush,
221  mRanges.count() > 0 ? Qt::NoPen : mPen );
222 #else
223  HistogramItem *plotHistogramItem = 0;
224  plotHistogramItem = createHistoItem( mRanges.count() > 0 ? mRanges.at( 0 ).label() : QString(),
225  mRanges.count() > 0 ? QBrush( mHistoColors.at( 0 ) ) : mBrush,
226  mRanges.count() > 0 ? Qt::NoPen : mPen );
227 #endif
228 
229 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
230  QVector<QwtIntervalSample> dataHisto;
231 #else
232 
233  // we safely assume that QT>=4.0 (min version is 4.7), therefore QwtArray is a QVector, so don't set size here
234  QwtArray<QwtDoubleInterval> intervalsHisto;
235  QwtArray<double> valuesHisto;
236 
237 #endif
238 
239  int bins = mBinsSpinBox->value();
240  QList<double> edges = mHistogram.binEdges( bins );
241  QList<int> counts = mHistogram.counts( bins );
242 
243  int rangeIndex = 0;
244  int lastValue = 0;
245 
246  for ( int bin = 0; bin < bins; ++bin )
247  {
248  int binValue = counts.at( bin );
249 
250  //current bin crosses two graduated ranges, so we split between
251  //two histogram items
252  if ( rangeIndex < mRanges.count() - 1 && edges.at( bin ) > mRanges.at( rangeIndex ).upperValue() )
253  {
254  rangeIndex++;
255 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
256  plotHistogram->setSamples( dataHisto );
257  plotHistogram->attach( mpPlot );
258  plotHistogram = createPlotHistogram( mRanges.at( rangeIndex ).label(), mHistoColors.at( rangeIndex ) );
259  dataHisto.clear();
260  dataHisto << QwtIntervalSample( lastValue, mRanges.at( rangeIndex - 1 ).upperValue(), edges.at( bin ) );
261 #else
262  plotHistogramItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) );
263  plotHistogramItem->attach( mpPlot );
264  plotHistogramItem = createHistoItem( mRanges.at( rangeIndex ).label(), mHistoColors.at( rangeIndex ) );
265  intervalsHisto.clear();
266  valuesHisto.clear();
267  intervalsHisto.append( QwtDoubleInterval( mRanges.at( rangeIndex - 1 ).upperValue(), edges.at( bin ) ) );
268  valuesHisto.append( lastValue );
269 #endif
270  }
271 
272  double upperEdge = mRanges.count() > 0 ? qMin( edges.at( bin + 1 ), mRanges.at( rangeIndex ).upperValue() )
273  : edges.at( bin + 1 );
274 
275 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
276  dataHisto << QwtIntervalSample( binValue, edges.at( bin ), upperEdge );
277 #else
278  intervalsHisto.append( QwtDoubleInterval( edges.at( bin ), upperEdge ) );
279  valuesHisto.append( double( binValue ) );
280 #endif
281 
282  lastValue = binValue;
283  }
284 
285 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
286  plotHistogram->setSamples( dataHisto );
287  plotHistogram->attach( mpPlot );
288 #else
289  plotHistogramItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) );
290  plotHistogramItem->attach( mpPlot );
291 #endif
292 
294  Q_FOREACH ( const QgsRendererRangeV2& range, mRanges )
295  {
296  QwtPlotMarker* rangeMarker = new QwtPlotMarker();
297  rangeMarker->attach( mpPlot );
298  rangeMarker->setLineStyle( QwtPlotMarker::VLine );
299  rangeMarker->setXValue( range.upperValue() );
300  rangeMarker->setLabel( QString::number( range.upperValue() ) );
301  rangeMarker->setLabelOrientation( Qt::Vertical );
302  rangeMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
303  rangeMarker->show();
304  mRangeMarkers << rangeMarker;
305  }
306 
307  if ( mMeanCheckBox->isChecked() )
308  {
309  QwtPlotMarker* meanMarker = new QwtPlotMarker();
310  meanMarker->attach( mpPlot );
311  meanMarker->setLineStyle( QwtPlotMarker::VLine );
312  meanMarker->setLinePen( mMeanPen );
313  meanMarker->setXValue( mStats.mean() );
314  meanMarker->setLabel( QString( QChar( 956 ) ) );
315  meanMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
316  meanMarker->show();
317  }
318 
319  if ( mStdevCheckBox->isChecked() )
320  {
321  QwtPlotMarker* stdev1Marker = new QwtPlotMarker();
322  stdev1Marker->attach( mpPlot );
323  stdev1Marker->setLineStyle( QwtPlotMarker::VLine );
324  stdev1Marker->setLinePen( mStdevPen );
325  stdev1Marker->setXValue( mStats.mean() - mStats.stDev() );
326  stdev1Marker->setLabel( QString( QChar( 963 ) ) );
327  stdev1Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
328  stdev1Marker->show();
329 
330  QwtPlotMarker* stdev2Marker = new QwtPlotMarker();
331  stdev2Marker->attach( mpPlot );
332  stdev2Marker->setLineStyle( QwtPlotMarker::VLine );
333  stdev2Marker->setLinePen( mStdevPen );
334  stdev2Marker->setXValue( mStats.mean() + mStats.stDev() );
335  stdev2Marker->setLabel( QString( QChar( 963 ) ) );
336  stdev2Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop );
337  stdev2Marker->show();
338  }
339 
340  mpPlot->setEnabled( true );
341  mpPlot->replot();
342 }
343 
344 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
345 QwtPlotHistogram* QgsHistogramWidget::createPlotHistogram( const QString& title, const QBrush& brush, const QPen& pen ) const
346 {
347  QwtPlotHistogram* histogram = new QwtPlotHistogram( title );
348  histogram->setBrush( brush );
349  if ( pen != Qt::NoPen )
350  {
351  histogram->setPen( pen );
352  }
353  else if ( brush.color().lightness() > 200 )
354  {
355  QPen p;
356  p.setColor( brush.color().darker( 150 ) );
357  p.setWidth( 0 );
358  p.setCosmetic( true );
359  histogram->setPen( p );
360  }
361  else
362  {
363  histogram->setPen( QPen( Qt::NoPen ) );
364  }
365  return histogram;
366 }
367 #else
368 HistogramItem * QgsHistogramWidget::createHistoItem( const QString& title, const QBrush& brush, const QPen& pen ) const
369 {
370  HistogramItem* item = new HistogramItem( title );
371  item->setColor( brush.color() );
372  item->setFlat( true );
373  item->setSpacing( 0 );
374  if ( pen != Qt::NoPen )
375  {
376  item->setPen( pen );
377  }
378  else if ( brush.color().lightness() > 200 )
379  {
380  QPen p;
381  p.setColor( brush.color().darker( 150 ) );
382  p.setWidth( 0 );
383  p.setCosmetic( true );
384  item->setPen( p );
385  }
386  return item;
387 }
388 #endif
389 
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
void setStatistics(const Statistics &stats)
Sets flags which specify which statistics will be calculated.
void setFrameStyle(int style)
T value(int i) const
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
void setCosmetic(bool cosmetic)
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.
QList< double > getDoubleValues(const QString &fieldOrExpression, bool &ok, bool selectedOnly=false, int *nullCount=0)
Fetches all double values from a specified field name or expression.
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
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
QgsHistogramWidget(QWidget *parent=0, QgsVectorLayer *layer=0, const QString &fieldOrExp=QString())
QgsHistogramWidget constructor.
#define tr(sourceText)