QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 #include <qwt_plot_renderer.h>
40 #include <qwt_plot_histogram.h>
41 
42 
44  : QgsHistogramWidget( parent )
45 {
46  //clear x axis title to make more room for graph
47  setXAxisTitle( QString() );
48 
49  mFilter = new QgsGraduatedHistogramEventFilter( mPlot );
50 
51  connect( mFilter, &QgsGraduatedHistogramEventFilter::mousePress, this, &QgsGraduatedHistogramWidget::mousePress );
52  connect( mFilter, &QgsGraduatedHistogramEventFilter::mouseRelease, this, &QgsGraduatedHistogramWidget::mouseRelease );
53 
54  mHistoPicker = new QwtPlotPicker( mPlot->canvas() );
55  mHistoPicker->setTrackerMode( QwtPicker::ActiveOnly );
56  mHistoPicker->setRubberBand( QwtPicker::VLineRubberBand );
57  mHistoPicker->setStateMachine( new QwtPickerDragPointMachine );
58 }
59 
61 {
62  mRenderer = renderer;
63 }
64 
66 {
67  if ( !mRenderer )
68  return;
69 
70  bool pickerEnabled = false;
71  if ( mRenderer->rangesOverlap() )
72  {
73  setToolTip( tr( "Ranges are overlapping and can't be edited by the histogram" ) );
75  }
76  else if ( mRenderer->rangesHaveGaps() )
77  {
78  setToolTip( tr( "Ranges have gaps and can't be edited by the histogram" ) );
80  }
81  else if ( mRenderer->ranges().isEmpty() )
82  {
83  setToolTip( QString() );
85  }
86  else
87  {
88  setToolTip( QString() );
89  setGraduatedRanges( mRenderer->ranges() );
90  pickerEnabled = true;
91  }
93 
94  // histo picker
95  mHistoPicker->setEnabled( pickerEnabled );
96  mFilter->blockSignals( !pickerEnabled );
97 }
98 
99 void QgsGraduatedHistogramWidget::mousePress( double value )
100 {
101  mPressedValue = value;
102 
103  int closestRangeIndex = 0;
104  int minPixelDistance = 9999;
105  findClosestRange( mPressedValue, closestRangeIndex, minPixelDistance );
106 
107  if ( minPixelDistance <= 6 )
108  {
109  //moving a break, so hide the break line
110  mRangeMarkers.at( closestRangeIndex )->hide();
111  mPlot->replot();
112  }
113 }
114 
115 void QgsGraduatedHistogramWidget::mouseRelease( double value )
116 {
117  int closestRangeIndex = 0;
118  int minPixelDistance = 9999;
119  findClosestRange( mPressedValue, closestRangeIndex, minPixelDistance );
120 
121  if ( minPixelDistance <= 6 )
122  {
123  //do a sanity check - don't allow users to drag a break line
124  //into the middle of a different range. Doing so causes overlap
125  //of the ranges
126 
127  //new value needs to be within range covered by closestRangeIndex or
128  //closestRangeIndex + 1
129  if ( value <= mRenderer->ranges().at( closestRangeIndex ).lowerValue() ||
130  value >= mRenderer->ranges().at( closestRangeIndex + 1 ).upperValue() )
131  {
132  refresh();
133  return;
134  }
135 
136  mRenderer->updateRangeUpperValue( closestRangeIndex, value );
137  mRenderer->updateRangeLowerValue( closestRangeIndex + 1, value );
138  emit rangesModified( false );
139  }
140  else
141  {
142  //if distance from markers is too big, add a break
143  mRenderer->addBreak( value );
144  // to fix the deprecated call to reset() in QgsGraduatedSymbolRendererWidget::refreshRanges,
145  // this class should work on the model in the widget rather than adding break via the renderer.
146  emit rangesModified( true );
147  }
148 
149  refresh();
150 }
151 
152 void QgsGraduatedHistogramWidget::findClosestRange( double value, int &closestRangeIndex, int &pixelDistance ) const
153 {
154  const QgsRangeList &ranges = mRenderer->ranges();
155 
156  double minDistance = std::numeric_limits<double>::max();
157  int pressedPixel = mPlot->canvasMap( QwtPlot::xBottom ).transform( value );
158  for ( int i = 0; i < ranges.count() - 1; ++i )
159  {
160  if ( std::fabs( mPressedValue - ranges.at( i ).upperValue() ) < minDistance )
161  {
162  closestRangeIndex = i;
163  minDistance = std::fabs( mPressedValue - ranges.at( i ).upperValue() );
164  pixelDistance = std::fabs( pressedPixel - mPlot->canvasMap( QwtPlot::xBottom ).transform( ranges.at( i ).upperValue() ) );
165  }
166  }
167 }
168 
170 
171 QgsGraduatedHistogramEventFilter::QgsGraduatedHistogramEventFilter( QwtPlot *plot )
172  : QObject( plot )
173  , mPlot( plot )
174 {
175  mPlot->canvas()->installEventFilter( this );
176 }
177 
178 bool QgsGraduatedHistogramEventFilter::eventFilter( QObject *object, QEvent *event )
179 {
180  if ( !mPlot->isEnabled() )
181  return QObject::eventFilter( object, event );
182 
183  switch ( event->type() )
184  {
185  case QEvent::MouseButtonPress:
186  {
187  const QMouseEvent *mouseEvent = static_cast<QMouseEvent * >( event );
188  if ( mouseEvent->button() == Qt::LeftButton )
189  {
190  emit mousePress( posToValue( mouseEvent->pos() ) );
191  }
192  break;
193  }
194  case QEvent::MouseButtonRelease:
195  {
196  const QMouseEvent *mouseEvent = static_cast<QMouseEvent * >( event );
197  if ( mouseEvent->button() == Qt::LeftButton )
198  {
199  emit mouseRelease( posToValue( mouseEvent->pos() ) );
200  }
201  break;
202  }
203  default:
204  break;
205  }
206 
207  return QObject::eventFilter( object, event );
208 }
209 
210 double QgsGraduatedHistogramEventFilter::posToValue( QPointF point ) const
211 {
212  if ( !mPlot )
213  return -99999999;
214 
215  return mPlot->canvasMap( QwtPlot::xBottom ).invTransform( point.x() );
216 }
void rangesModified(bool rangesAdded)
Emitted when the user modifies the graduated ranges using the histogram widget.
QgsGraduatedHistogramWidget(QWidget *parent=nullptr)
QgsGraduatedHistogramWidget constructor.
void setRenderer(QgsGraduatedSymbolRenderer *renderer)
Sets the QgsGraduatedSymbolRenderer renderer associated with the histogram.
void drawHistogram() override
Updates and redraws the histogram.
void addBreak(double breakValue, bool updateSymbols=true)
Add a breakpoint by splitting existing classes so that the specified value becomes a break between tw...
bool rangesOverlap() const
Tests whether classes assigned to the renderer have ranges which overlap.
bool updateRangeUpperValue(int rangeIndex, double value)
bool updateRangeLowerValue(int rangeIndex, double value)
const QgsRangeList & ranges() const
bool rangesHaveGaps() const
Tests whether classes assigned to the renderer have gaps between the ranges.
Graphical histogram for displaying distributions of field values.
void refresh()
Redraws the histogram.
QList< QwtPlotMarker * > mRangeMarkers
void setXAxisTitle(const QString &title)
Sets the title for the histogram's x-axis.
virtual void drawHistogram()
Updates and redraws the histogram.
void setGraduatedRanges(const QgsRangeList &ranges)
Sets the graduated ranges associated with the histogram.
QList< QgsRendererRange > QgsRangeList