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