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