QGIS API Documentation  3.17.0-Master (8af46bc54f)
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 }
QList< QgsRendererRange > QgsRangeList
QgsGraduatedHistogramWidget(QWidget *parent=nullptr)
QgsGraduatedHistogramWidget constructor.
bool updateRangeUpperValue(int rangeIndex, double value)
bool rangesOverlap() const
Tests whether classes assigned to the renderer have ranges which overlap.
void addBreak(double breakValue, bool updateSymbols=true)
Add a breakpoint by splitting existing classes so that the specified value becomes a break between tw...
void rangesModified(bool rangesAdded)
Emitted when the user modifies the graduated ranges using the histogram widget.
bool rangesHaveGaps() const
Tests whether classes assigned to the renderer have gaps between the ranges.
void setGraduatedRanges(const QgsRangeList &ranges)
Sets the graduated ranges associated with the histogram.
bool updateRangeLowerValue(int rangeIndex, double value)
void setRenderer(QgsGraduatedSymbolRenderer *renderer)
Sets the QgsGraduatedSymbolRenderer renderer associated with the histogram.
void refresh()
Redraws the histogram.
void drawHistogram() override
Updates and redraws the histogram.
virtual void drawHistogram()
Updates and redraws the histogram.
Graphical histogram for displaying distributions of field values.
void setXAxisTitle(const QString &title)
Sets the title for the histogram&#39;s x-axis.
QList< QwtPlotMarker *> mRangeMarkers
const QgsRangeList & ranges() const