QGIS API Documentation  3.23.0-Master (dd0cd13a00)
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
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