QGIS API Documentation 3.41.0-Master (cea29feecf2)
Loading...
Searching...
No Matches
qgscurveeditorwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscurveeditorwidget.cpp
3 ------------------------
4 begin : February 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16
18#include "moc_qgscurveeditorwidget.cpp"
19#include "qgsvectorlayer.h"
20
21#include <QPainter>
22#include <QVBoxLayout>
23#include <QMouseEvent>
24#include <algorithm>
25
26// QWT Charting widget
27#include <qwt_global.h>
28#include <qwt_plot_canvas.h>
29#include <qwt_plot.h>
30#include <qwt_plot_curve.h>
31#include <qwt_plot_grid.h>
32#include <qwt_plot_marker.h>
33#include <qwt_plot_picker.h>
34#include <qwt_picker_machine.h>
35#include <qwt_plot_layout.h>
36#include <qwt_symbol.h>
37#include <qwt_legend.h>
38#include <qwt_scale_div.h>
39#include <qwt_scale_map.h>
40
41#include <qwt_plot_renderer.h>
42#include <qwt_plot_histogram.h>
43
45 : QWidget( parent )
46 , mCurve( transform )
47{
48 mPlot = new QwtPlot();
49 mPlot->setMinimumSize( QSize( 0, 100 ) );
50 mPlot->setAxisScale( QwtPlot::yLeft, 0, 1 );
51 mPlot->setAxisScale( QwtPlot::yRight, 0, 1 );
52 mPlot->setAxisScale( QwtPlot::xBottom, 0, 1 );
53 mPlot->setAxisScale( QwtPlot::xTop, 0, 1 );
54
55 QVBoxLayout *vlayout = new QVBoxLayout();
56 vlayout->addWidget( mPlot );
57 setLayout( vlayout );
58
59 // hide the ugly canvas frame
60 mPlot->setFrameStyle( QFrame::NoFrame );
61 QFrame *plotCanvasFrame = dynamic_cast<QFrame *>( mPlot->canvas() );
62 if ( plotCanvasFrame )
63 plotCanvasFrame->setFrameStyle( QFrame::NoFrame );
64
65 mPlot->enableAxis( QwtPlot::yLeft, false );
66 mPlot->enableAxis( QwtPlot::xBottom, false );
67
68 // add a grid
69 QwtPlotGrid *grid = new QwtPlotGrid();
70 const QwtScaleDiv gridDiv( 0.0, 1.0, QList<double>(), QList<double>(), QList<double>() << 0.2 << 0.4 << 0.6 << 0.8 );
71 grid->setXDiv( gridDiv );
72 grid->setYDiv( gridDiv );
73 grid->setPen( QPen( QColor( 0, 0, 0, 50 ) ) );
74 grid->attach( mPlot );
75
76 mPlotCurve = new QwtPlotCurve();
77 mPlotCurve->setTitle( QStringLiteral( "Curve" ) );
78 mPlotCurve->setPen( QPen( QColor( 30, 30, 30 ), 0.0 ) ),
79 mPlotCurve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
80 mPlotCurve->attach( mPlot );
81
82 mPlotFilter = new QgsCurveEditorPlotEventFilter( mPlot );
83 connect( mPlotFilter, &QgsCurveEditorPlotEventFilter::mousePress, this, &QgsCurveEditorWidget::plotMousePress );
84 connect( mPlotFilter, &QgsCurveEditorPlotEventFilter::mouseRelease, this, &QgsCurveEditorWidget::plotMouseRelease );
85 connect( mPlotFilter, &QgsCurveEditorPlotEventFilter::mouseMove, this, &QgsCurveEditorWidget::plotMouseMove );
86
87 mPlotCurve->setVisible( true );
88 updatePlot();
89}
90
92{
93 if ( mGatherer && mGatherer->isRunning() )
94 {
95 connect( mGatherer.get(), &QgsHistogramValuesGatherer::finished, mGatherer.get(), &QgsHistogramValuesGatherer::deleteLater );
96 mGatherer->stop();
97 ( void ) mGatherer.release();
98 }
99}
100
102{
103 mCurve = curve;
104 updatePlot();
105 emit changed();
106}
107
108void QgsCurveEditorWidget::setHistogramSource( const QgsVectorLayer *layer, const QString &expression )
109{
110 if ( !mGatherer )
111 {
112 mGatherer.reset( new QgsHistogramValuesGatherer() );
113 connect( mGatherer.get(), &QgsHistogramValuesGatherer::calculatedHistogram, this, [=] {
114 mHistogram.reset( new QgsHistogram( mGatherer->histogram() ) );
115 updateHistogram();
116 } );
117 }
118
119 const bool changed = mGatherer->layer() != layer || mGatherer->expression() != expression;
120 if ( changed )
121 {
122 mGatherer->setExpression( expression );
123 mGatherer->setLayer( layer );
124 mGatherer->start();
125 if ( mGatherer->isRunning() )
126 {
127 //stop any currently running task
128 mGatherer->stop();
129 while ( mGatherer->isRunning() )
130 {
131 QCoreApplication::processEvents();
132 }
133 }
134 mGatherer->start();
135 }
136 else
137 {
138 updateHistogram();
139 }
140}
141
143{
144 mMinValueRange = minValueRange;
145 updateHistogram();
146}
147
149{
150 mMaxValueRange = maxValueRange;
151 updateHistogram();
152}
153
155{
156 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
157 {
158 QList<QgsPointXY> cp = mCurve.controlPoints();
159 if ( mCurrentPlotMarkerIndex > 0 && mCurrentPlotMarkerIndex < cp.count() - 1 )
160 {
161 cp.removeAt( mCurrentPlotMarkerIndex );
162 mCurve.setControlPoints( cp );
163 updatePlot();
164 emit changed();
165 }
166 }
167}
168
169void QgsCurveEditorWidget::plotMousePress( QPointF point )
170{
171 mCurrentPlotMarkerIndex = findNearestControlPoint( point );
172 if ( mCurrentPlotMarkerIndex < 0 )
173 {
174 // add a new point
175 mCurve.addControlPoint( point.x(), point.y() );
176 mCurrentPlotMarkerIndex = findNearestControlPoint( point );
177 emit changed();
178 }
179 updatePlot();
180}
181
182
183int QgsCurveEditorWidget::findNearestControlPoint( QPointF point ) const
184{
185 double minDist = 3.0 / mPlot->width();
186 int currentPlotMarkerIndex = -1;
187
188 const QList<QgsPointXY> controlPoints = mCurve.controlPoints();
189
190 for ( int i = 0; i < controlPoints.count(); ++i )
191 {
192 const QgsPointXY currentPoint = controlPoints.at( i );
193 double currentDist;
194 currentDist = std::pow( point.x() - currentPoint.x(), 2.0 ) + std::pow( point.y() - currentPoint.y(), 2.0 );
195 if ( currentDist < minDist )
196 {
197 minDist = currentDist;
198 currentPlotMarkerIndex = i;
199 }
200 }
201 return currentPlotMarkerIndex;
202}
203
204
205void QgsCurveEditorWidget::plotMouseRelease( QPointF )
206{
207}
208
209void QgsCurveEditorWidget::plotMouseMove( QPointF point )
210{
211 if ( mCurrentPlotMarkerIndex < 0 )
212 return;
213
214 QList<QgsPointXY> cp = mCurve.controlPoints();
215 bool removePoint = false;
216 if ( mCurrentPlotMarkerIndex == 0 )
217 {
218 point.setX( std::min( point.x(), cp.at( 1 ).x() - 0.01 ) );
219 }
220 else
221 {
222 removePoint = point.x() <= cp.at( mCurrentPlotMarkerIndex - 1 ).x();
223 }
224 if ( mCurrentPlotMarkerIndex == cp.count() - 1 )
225 {
226 point.setX( std::max( point.x(), cp.at( mCurrentPlotMarkerIndex - 1 ).x() + 0.01 ) );
227 removePoint = false;
228 }
229 else
230 {
231 removePoint = removePoint || point.x() >= cp.at( mCurrentPlotMarkerIndex + 1 ).x();
232 }
233
234 if ( removePoint )
235 {
236 cp.removeAt( mCurrentPlotMarkerIndex );
237 mCurrentPlotMarkerIndex = -1;
238 }
239 else
240 {
241 cp[mCurrentPlotMarkerIndex] = QgsPointXY( point.x(), point.y() );
242 }
243 mCurve.setControlPoints( cp );
244 updatePlot();
245 emit changed();
246}
247
248void QgsCurveEditorWidget::addPlotMarker( double x, double y, bool isSelected )
249{
250 const QColor borderColor( 0, 0, 0 );
251
252 const QColor brushColor = isSelected ? borderColor : QColor( 255, 255, 255, 0 );
253
254 QwtPlotMarker *marker = new QwtPlotMarker();
255 marker->setSymbol( new QwtSymbol( QwtSymbol::Ellipse, QBrush( brushColor ), QPen( borderColor, isSelected ? 2 : 1 ), QSize( 8, 8 ) ) );
256 marker->setValue( x, y );
257 marker->attach( mPlot );
258 marker->setRenderHint( QwtPlotItem::RenderAntialiased, true );
259 mMarkers << marker;
260}
261
262void QgsCurveEditorWidget::updateHistogram()
263{
264 if ( !mHistogram )
265 return;
266
267 //draw histogram
268 const QBrush histoBrush( QColor( 0, 0, 0, 70 ) );
269
270 delete mPlotHistogram;
271 mPlotHistogram = createPlotHistogram( histoBrush );
272 QVector<QwtIntervalSample> dataHisto;
273
274 const int bins = 40;
275 QList<double> edges = mHistogram->binEdges( bins );
276 const QList<int> counts = mHistogram->counts( bins );
277
278 // scale counts to 0->1
279 const double max = *std::max_element( counts.constBegin(), counts.constEnd() );
280
281 // scale bin edges to fit in 0->1 range
282 if ( !qgsDoubleNear( mMinValueRange, mMaxValueRange ) )
283 {
284 std::transform( edges.begin(), edges.end(), edges.begin(), [this]( double d ) -> double { return ( d - mMinValueRange ) / ( mMaxValueRange - mMinValueRange ); } );
285 }
286
287 for ( int bin = 0; bin < bins; ++bin )
288 {
289 const double binValue = counts.at( bin ) / max;
290
291 const double upperEdge = edges.at( bin + 1 );
292
293 dataHisto << QwtIntervalSample( binValue, edges.at( bin ), upperEdge );
294 }
295
296 mPlotHistogram->setSamples( dataHisto );
297 mPlotHistogram->attach( mPlot );
298 mPlot->replot();
299}
300
301void QgsCurveEditorWidget::updatePlot()
302{
303 // remove existing markers
304 const auto constMMarkers = mMarkers;
305 for ( QwtPlotMarker *marker : constMMarkers )
306 {
307 marker->detach();
308 delete marker;
309 }
310 mMarkers.clear();
311
312 QPolygonF curvePoints;
313 QVector<double> x;
314
315 int i = 0;
316 const auto constControlPoints = mCurve.controlPoints();
317 for ( const QgsPointXY &point : constControlPoints )
318 {
319 x << point.x();
320 addPlotMarker( point.x(), point.y(), mCurrentPlotMarkerIndex == i );
321 i++;
322 }
323
324 //add extra intermediate points
325
326 for ( double p = 0; p <= 1.0; p += 0.01 )
327 {
328 x << p;
329 }
330 std::sort( x.begin(), x.end() );
331 const QVector<double> y = mCurve.y( x );
332
333 for ( int j = 0; j < x.count(); ++j )
334 {
335 curvePoints << QPointF( x.at( j ), y.at( j ) );
336 }
337
338 mPlotCurve->setSamples( curvePoints );
339 mPlot->replot();
340}
341
342QwtPlotHistogram *QgsCurveEditorWidget::createPlotHistogram( const QBrush &brush, const QPen &pen ) const
343{
344 QwtPlotHistogram *histogram = new QwtPlotHistogram( QString() );
345 histogram->setBrush( brush );
346 if ( pen != Qt::NoPen )
347 {
348 histogram->setPen( pen );
349 }
350 else if ( brush.color().lightness() > 200 )
351 {
352 QPen p;
353 p.setColor( brush.color().darker( 150 ) );
354 p.setWidth( 0 );
355 p.setCosmetic( true );
356 histogram->setPen( p );
357 }
358 else
359 {
360 histogram->setPen( QPen( Qt::NoPen ) );
361 }
362 return histogram;
363}
364
366
367QgsCurveEditorPlotEventFilter::QgsCurveEditorPlotEventFilter( QwtPlot *plot )
368 : QObject( plot )
369 , mPlot( plot )
370{
371 mPlot->canvas()->installEventFilter( this );
372}
373
374bool QgsCurveEditorPlotEventFilter::eventFilter( QObject *object, QEvent *event )
375{
376 if ( !mPlot->isEnabled() )
377 return QObject::eventFilter( object, event );
378
379 switch ( event->type() )
380 {
381 case QEvent::MouseButtonPress:
382 {
383 const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
384 if ( mouseEvent->button() == Qt::LeftButton )
385 {
386 emit mousePress( mapPoint( mouseEvent->pos() ) );
387 }
388 break;
389 }
390 case QEvent::MouseMove:
391 {
392 const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
393 if ( mouseEvent->buttons() & Qt::LeftButton )
394 {
395 // only emit when button pressed
396 emit mouseMove( mapPoint( mouseEvent->pos() ) );
397 }
398 break;
399 }
400 case QEvent::MouseButtonRelease:
401 {
402 const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
403 if ( mouseEvent->button() == Qt::LeftButton )
404 {
405 emit mouseRelease( mapPoint( mouseEvent->pos() ) );
406 }
407 break;
408 }
409 default:
410 break;
411 }
412
413 return QObject::eventFilter( object, event );
414}
415
416QPointF QgsCurveEditorPlotEventFilter::mapPoint( QPointF point ) const
417{
418 if ( !mPlot )
419 return QPointF();
420
421 return QPointF( mPlot->canvasMap( QwtPlot::xBottom ).invTransform( point.x() ), mPlot->canvasMap( QwtPlot::yLeft ).invTransform( point.y() ) );
422}
423
424
QgsCurveTransform curve() const
Returns a curve representing the current curve from the widget.
void setMaxHistogramValueRange(double maxValueRange)
Sets the maximum expected value for the range of values shown in the histogram.
void changed()
Emitted when the widget curve changes.
void setMinHistogramValueRange(double minValueRange)
Sets the minimum expected value for the range of values shown in the histogram.
void keyPressEvent(QKeyEvent *event) override
void setCurve(const QgsCurveTransform &curve)
Sets the curve to show in the widget.
void setHistogramSource(const QgsVectorLayer *layer, const QString &expression)
Sets a layer and expression source for values to show in a histogram behind the curve.
QgsCurveEditorWidget(QWidget *parent=nullptr, const QgsCurveTransform &curve=QgsCurveTransform())
Constructor for QgsCurveEditorWidget.
Handles scaling of input values to output values by using a curve created from smoothly joining a num...
void setControlPoints(const QList< QgsPointXY > &points)
Sets the list of control points for the transform.
void addControlPoint(double x, double y)
Adds a control point to the transform.
double y(double x) const
Returns the mapped y value corresponding to the specified x value.
QList< QgsPointXY > controlPoints() const
Returns a list of the control points for the transform.
A class to represent a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Represents a vector layer which manages a vector based data sets.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6024