QGIS API Documentation 3.29.0-Master (8c80f25a4f)
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"
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
100void 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
116void 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
153void 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
172QgsGraduatedHistogramEventFilter::QgsGraduatedHistogramEventFilter( QwtPlot *plot )
173 : QObject( plot )
174 , mPlot( plot )
175{
176 mPlot->canvas()->installEventFilter( this );
177}
178
179bool 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
211double 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