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