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