QGIS API Documentation  2.14.0-Essen
qgsgraduatedhistogramwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgraduatedhistogramwidget.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 
21 #include "qgsapplication.h"
22 #include "qgsvectorlayer.h"
23 #include "qgsstatisticalsummary.h"
24 
25 #include <QSettings>
26 #include <QObject>
27 #include <QMouseEvent>
28 
29 // QWT Charting widget
30 #include <qwt_global.h>
31 #include <qwt_plot_canvas.h>
32 #include <qwt_plot.h>
33 #include <qwt_plot_curve.h>
34 #include <qwt_plot_grid.h>
35 #include <qwt_plot_marker.h>
36 #include <qwt_plot_picker.h>
37 #include <qwt_picker_machine.h>
38 #include <qwt_plot_layout.h>
39 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
40 #include <qwt_plot_renderer.h>
41 #include <qwt_plot_histogram.h>
42 #else
43 #include "../raster/qwt5_histogram_item.h"
44 #endif
45 
46 
48  : QgsHistogramWidget( parent )
49  , mRenderer( nullptr )
50  , mHistoPicker( nullptr )
51  , mPressedValue( 0 )
52 {
53  //clear x axis title to make more room for graph
55 
56  mFilter = new QgsGraduatedHistogramEventFilter( mPlot );
57 
58  connect( mFilter, SIGNAL( mousePress( double ) ), this, SLOT( mousePress( double ) ) );
59  connect( mFilter, SIGNAL( mouseRelease( double ) ), this, SLOT( mouseRelease( double ) ) );
60 
61  mHistoPicker = new QwtPlotPicker( mPlot->canvas() );
62  mHistoPicker->setTrackerMode( QwtPicker::ActiveOnly );
63  mHistoPicker->setRubberBand( QwtPicker::VLineRubberBand );
64 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
65  mHistoPicker->setStateMachine( new QwtPickerDragPointMachine );
66 #else
67  mHistoPicker->setSelectionFlags( QwtPicker::PointSelection | QwtPicker::DragSelection );
68 #endif
69 }
70 
71 
73 {
74 }
75 
77 {
78  mRenderer = renderer;
79 }
80 
82 {
83  if ( !mRenderer )
84  return;
85 
86  bool pickerEnabled = false;
87  if ( mRenderer->rangesOverlap() )
88  {
89  setToolTip( tr( "Ranges are overlapping and can't be edited by the histogram" ) );
91  }
92  else if ( mRenderer->rangesHaveGaps() )
93  {
94  setToolTip( tr( "Ranges have gaps and can't be edited by the histogram" ) );
96  }
97  else if ( mRenderer->ranges().isEmpty() )
98  {
99  setToolTip( QString() );
101  }
102  else
103  {
104  setToolTip( QString() );
105  setGraduatedRanges( mRenderer->ranges() );
106  pickerEnabled = true;
107  }
109 
110  // histo picker
111  mHistoPicker->setEnabled( pickerEnabled );
112  mFilter->blockSignals( !pickerEnabled );
113 }
114 
115 void QgsGraduatedHistogramWidget::mousePress( double value )
116 {
117  mPressedValue = value;
118 
119  int closestRangeIndex = 0;
120  int minPixelDistance = 9999;
121  findClosestRange( mPressedValue, closestRangeIndex, minPixelDistance );
122 
123  if ( minPixelDistance <= 6 )
124  {
125  //moving a break, so hide the break line
126  mRangeMarkers.at( closestRangeIndex )->hide();
127  mPlot->replot();
128  }
129 }
130 
131 void QgsGraduatedHistogramWidget::mouseRelease( double value )
132 {
133  int closestRangeIndex = 0;
134  int minPixelDistance = 9999;
135  findClosestRange( mPressedValue, closestRangeIndex, minPixelDistance );
136 
137  if ( minPixelDistance <= 6 )
138  {
139  //do a sanity check - don't allow users to drag a break line
140  //into the middle of a different range. Doing so causes overlap
141  //of the ranges
142 
143  //new value needs to be within range covered by closestRangeIndex or
144  //closestRangeIndex + 1
145  if ( value <= mRenderer->ranges().at( closestRangeIndex ).lowerValue() ||
146  value >= mRenderer->ranges().at( closestRangeIndex + 1 ).upperValue() )
147  {
148  refresh();
149  return;
150  }
151 
152  mRenderer->updateRangeUpperValue( closestRangeIndex, value );
153  mRenderer->updateRangeLowerValue( closestRangeIndex + 1, value );
154  emit rangesModified( false );
155  }
156  else
157  {
158  //if distance from markers is too big, add a break
159  mRenderer->addBreak( value );
160  emit rangesModified( true );
161  }
162 
163  refresh();
164 }
165 
166 void QgsGraduatedHistogramWidget::findClosestRange( double value, int &closestRangeIndex, int& pixelDistance ) const
167 {
168  const QgsRangeList& ranges = mRenderer->ranges();
169 
170  double minDistance = DBL_MAX;
171  int pressedPixel = mPlot->canvasMap( QwtPlot::xBottom ).transform( value );
172  for ( int i = 0; i < ranges.count() - 1; ++i )
173  {
174  if ( qAbs( mPressedValue - ranges.at( i ).upperValue() ) < minDistance )
175  {
176  closestRangeIndex = i;
177  minDistance = qAbs( mPressedValue - ranges.at( i ).upperValue() );
178  pixelDistance = qAbs( pressedPixel - mPlot->canvasMap( QwtPlot::xBottom ).transform( ranges.at( i ).upperValue() ) );
179  }
180  }
181 }
182 
184 
185 QgsGraduatedHistogramEventFilter::QgsGraduatedHistogramEventFilter( QwtPlot *plot )
186  : QObject( plot )
187  , mPlot( plot )
188 {
189  mPlot->canvas()->installEventFilter( this );
190 }
191 
192 bool QgsGraduatedHistogramEventFilter::eventFilter( QObject *object, QEvent *event )
193 {
194  if ( !mPlot->isEnabled() )
195  return QObject::eventFilter( object, event );
196 
197  switch ( event->type() )
198  {
199  case QEvent::MouseButtonPress:
200  {
201  const QMouseEvent* mouseEvent = static_cast<QMouseEvent* >( event );
202  if ( mouseEvent->button() == Qt::LeftButton )
203  {
204  emit mousePress( posToValue( mouseEvent->pos() ) );
205  }
206  break;
207  }
208  case QEvent::MouseButtonRelease:
209  {
210  const QMouseEvent* mouseEvent = static_cast<QMouseEvent* >( event );
211  if ( mouseEvent->button() == Qt::LeftButton )
212  {
213  emit mouseRelease( posToValue( mouseEvent->pos() ) );
214  }
215  break;
216  }
217  default:
218  break;
219  }
220 
221  return QObject::eventFilter( object, event );
222 }
223 
224 double QgsGraduatedHistogramEventFilter::posToValue( QPointF point ) const
225 {
226  if ( !mPlot )
227  return -99999999;
228 
229  return mPlot->canvasMap( QwtPlot::xBottom ).invTransform( point.x() );
230 }
QList< QgsRendererRangeV2 > QgsRangeList
Type type() const
void addBreak(double breakValue, bool updateSymbols=true)
Add a breakpoint by splitting existing classes so that the specified value becomes a break between tw...
QgsGraduatedHistogramWidget(QWidget *parent=nullptr)
QgsGraduatedHistogramWidget constructor.
const T & at(int i) const
void rangesModified(bool rangesAdded)
Emitted when the user modifies the graduated ranges using the histogram widget.
QString tr(const char *sourceText, const char *disambiguation, int n)
QList< QwtPlotMarker * > mRangeMarkers
bool rangesHaveGaps() const
Tests whether classes assigned to the renderer have gaps between the ranges.
int count(const T &value) const
qreal x() const
void setGraduatedRanges(const QgsRangeList &ranges)
Sets the graduated ranges associated with the histogram.
Qt::MouseButton button() const
bool isEmpty() const
bool updateRangeLowerValue(int rangeIndex, double value)
virtual bool eventFilter(QObject *watched, QEvent *event)
void refresh()
Redraws the histogram.
virtual void drawHistogram() override
Updates and redraws the histogram.
bool rangesOverlap() const
Tests whether classes assigned to the renderer have ranges which overlap.
QObject(QObject *parent)
virtual void drawHistogram()
Updates and redraws the histogram.
const QgsRangeList & ranges() const
bool updateRangeUpperValue(int rangeIndex, double value)
Graphical histogram for displaying distributions of field values.
const QPoint & pos() const
void setToolTip(const QString &)
void setXAxisTitle(const QString &title)
Sets the title for the histogram&#39;s x-axis.
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual bool event(QEvent *event)
void setRenderer(QgsGraduatedSymbolRendererV2 *renderer)
Sets the QgsGraduatedSymbolRendererV2 renderer associated with the histogram.