Skip to content

Commit 5c42c76

Browse files
committed
[FEATURE] Show a histogram for values behind curve editor
in property assistant Makes it easier to set suitable curves. Populated in the background for a nice reponsive widget!
1 parent 0faf7c3 commit 5c42c76

File tree

7 files changed

+360
-3
lines changed

7 files changed

+360
-3
lines changed

python/core/qgshistogram.sip

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class QgsHistogram
2222
*/
2323
void setValues( const QList<double>& values );
2424

25-
bool setValues( QgsVectorLayer* layer, const QString& fieldOrExpression, QgsFeedback* feedback = 0 );
25+
bool setValues( const QgsVectorLayer* layer, const QString& fieldOrExpression, QgsFeedback* feedback = 0 );
2626

2727
/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
2828
* determined by the inter-quartile range of values and the number of values.

python/gui/qgscurveeditorwidget.sip

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ class QgsCurveEditorWidget : QWidget
77
public:
88

99
QgsCurveEditorWidget( QWidget* parent /TransferThis/ = 0, const QgsCurveTransform& curve = QgsCurveTransform() );
10+
~QgsCurveEditorWidget();
1011

1112
QgsCurveTransform curve() const;
1213

1314
void setCurve( const QgsCurveTransform& curve );
15+
void setHistogramSource( const QgsVectorLayer* layer, const QString& expression );
16+
double minHistogramValueRange() const;
17+
double maxHistogramValueRange() const;
18+
19+
public slots:
20+
void setMinHistogramValueRange( double minValueRange );
21+
void setMaxHistogramValueRange( double maxValueRange );
1422

1523
signals:
1624

src/core/qgshistogram.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ void QgsHistogram::setValues( const QList<double> &values )
4747
prepareValues();
4848
}
4949

50-
bool QgsHistogram::setValues( QgsVectorLayer *layer, const QString &fieldOrExpression, QgsFeedback* feedback )
50+
bool QgsHistogram::setValues( const QgsVectorLayer *layer, const QString &fieldOrExpression, QgsFeedback* feedback )
5151
{
5252
mValues.clear();
5353
if ( !layer )

src/core/qgshistogram.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class CORE_EXPORT QgsHistogram
5353
* @param feedback optional feedback object to allow cancelation of calculation
5454
* @returns true if values were successfully set
5555
*/
56-
bool setValues( QgsVectorLayer* layer, const QString& fieldOrExpression, QgsFeedback* feedback = nullptr );
56+
bool setValues( const QgsVectorLayer* layer, const QString& fieldOrExpression, QgsFeedback* feedback = nullptr );
5757

5858
/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
5959
* determined by the inter-quartile range of values and the number of values.

src/gui/qgscurveeditorwidget.cpp

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <QPainter>
2121
#include <QVBoxLayout>
2222
#include <QMouseEvent>
23+
#include <algorithm>
2324

2425
// QWT Charting widget
2526
#include <qwt_global.h>
@@ -34,6 +35,13 @@
3435
#include <qwt_symbol.h>
3536
#include <qwt_legend.h>
3637

38+
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
39+
#include <qwt_plot_renderer.h>
40+
#include <qwt_plot_histogram.h>
41+
#else
42+
#include "../raster/qwt5_histogram_item.h"
43+
#endif
44+
3745
QgsCurveEditorWidget::QgsCurveEditorWidget( QWidget* parent, const QgsCurveTransform& transform )
3846
: QWidget( parent )
3947
, mCurve( transform )
@@ -86,13 +94,70 @@ QgsCurveEditorWidget::QgsCurveEditorWidget( QWidget* parent, const QgsCurveTrans
8694
updatePlot();
8795
}
8896

97+
QgsCurveEditorWidget::~QgsCurveEditorWidget()
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+
89107
void QgsCurveEditorWidget::setCurve( const QgsCurveTransform& curve )
90108
{
91109
mCurve = curve;
92110
updatePlot();
93111
emit changed();
94112
}
95113

114+
void QgsCurveEditorWidget::setHistogramSource( const QgsVectorLayer* layer, const QString& expression )
115+
{
116+
if ( !mGatherer )
117+
{
118+
mGatherer.reset( new QgsHistogramValuesGatherer() );
119+
connect( mGatherer.get(), &QgsHistogramValuesGatherer::calculatedHistogram, this, [=]
120+
{
121+
mHistogram.reset( new QgsHistogram( mGatherer->histogram() ) );
122+
updateHistogram();
123+
} );
124+
}
125+
126+
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+
149+
void QgsCurveEditorWidget::setMinHistogramValueRange( double minValueRange )
150+
{
151+
mMinValueRange = minValueRange;
152+
updateHistogram();
153+
}
154+
155+
void QgsCurveEditorWidget::setMaxHistogramValueRange( double maxValueRange )
156+
{
157+
mMaxValueRange = maxValueRange;
158+
updateHistogram();
159+
}
160+
96161
void QgsCurveEditorWidget::keyPressEvent( QKeyEvent* event )
97162
{
98163
if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
@@ -205,6 +270,63 @@ void QgsCurveEditorWidget::addPlotMarker( double x, double y, bool isSelected )
205270
mMarkers << marker;
206271
}
207272

273+
void QgsCurveEditorWidget::updateHistogram()
274+
{
275+
if ( !mHistogram )
276+
return;
277+
278+
//draw histogram
279+
QBrush histoBrush( QColor( 0, 0, 0, 70 ) );
280+
281+
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
282+
delete mPlotHistogram;
283+
mPlotHistogram = createPlotHistogram( histoBrush );
284+
QVector<QwtIntervalSample> dataHisto;
285+
#else
286+
delete mPlotHistogramItem;
287+
mPlotHistogramItem = createHistoItem( histoBrush );
288+
QwtArray<QwtDoubleInterval> intervalsHisto;
289+
QwtArray<double> valuesHisto;
290+
#endif
291+
292+
int bins = 40;
293+
QList<double> edges = mHistogram->binEdges( bins );
294+
QList<int> counts = mHistogram->counts( bins );
295+
296+
// scale counts to 0->1
297+
double max = *std::max_element( counts.constBegin(), counts.constEnd() );
298+
299+
// scale bin edges to fit in 0->1 range
300+
if ( !qgsDoubleNear( mMinValueRange, mMaxValueRange ) )
301+
{
302+
std::transform( edges.begin(), edges.end(), edges.begin(),
303+
[this]( double d ) -> double { return ( d - mMinValueRange ) / ( mMaxValueRange - mMinValueRange ); } );
304+
}
305+
306+
for ( int bin = 0; bin < bins; ++bin )
307+
{
308+
double binValue = counts.at( bin ) / max;
309+
310+
double upperEdge = edges.at( bin + 1 );
311+
312+
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
313+
dataHisto << QwtIntervalSample( binValue, edges.at( bin ), upperEdge );
314+
#else
315+
intervalsHisto.append( QwtDoubleInterval( edges.at( bin ), upperEdge ) );
316+
valuesHisto.append( double( binValue ) );
317+
#endif
318+
}
319+
320+
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
321+
mPlotHistogram->setSamples( dataHisto );
322+
mPlotHistogram->attach( mPlot );
323+
#else
324+
mPlotHistogramItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) );
325+
mPlotHistogramItem->attach( mPlot );
326+
#endif
327+
mPlot->replot();
328+
}
329+
208330
void QgsCurveEditorWidget::updatePlot()
209331
{
210332
// remove existing markers
@@ -249,6 +371,52 @@ void QgsCurveEditorWidget::updatePlot()
249371
}
250372

251373

374+
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
375+
QwtPlotHistogram* QgsCurveEditorWidget::createPlotHistogram( const QBrush& brush, const QPen& pen ) const
376+
{
377+
QwtPlotHistogram* histogram = new QwtPlotHistogram( QString() );
378+
histogram->setBrush( brush );
379+
if ( pen != Qt::NoPen )
380+
{
381+
histogram->setPen( pen );
382+
}
383+
else if ( brush.color().lightness() > 200 )
384+
{
385+
QPen p;
386+
p.setColor( brush.color().darker( 150 ) );
387+
p.setWidth( 0 );
388+
p.setCosmetic( true );
389+
histogram->setPen( p );
390+
}
391+
else
392+
{
393+
histogram->setPen( QPen( Qt::NoPen ) );
394+
}
395+
return histogram;
396+
}
397+
#else
398+
HistogramItem * QgsCurveEditorWidget::createHistoItem( const QBrush& brush, const QPen& pen ) const
399+
{
400+
HistogramItem* item = new HistogramItem( QString() );
401+
item->setColor( brush.color() );
402+
item->setFlat( true );
403+
item->setSpacing( 0 );
404+
if ( pen != Qt::NoPen )
405+
{
406+
item->setPen( pen );
407+
}
408+
else if ( brush.color().lightness() > 200 )
409+
{
410+
QPen p;
411+
p.setColor( brush.color().darker( 150 ) );
412+
p.setWidth( 0 );
413+
p.setCosmetic( true );
414+
item->setPen( p );
415+
}
416+
return item;
417+
}
418+
#endif
419+
252420
/// @cond PRIVATE
253421

254422
QgsCurveEditorPlotEventFilter::QgsCurveEditorPlotEventFilter( QwtPlot *plot )
@@ -309,4 +477,5 @@ QPointF QgsCurveEditorPlotEventFilter::mapPoint( QPointF point ) const
309477
mPlot->canvasMap( QwtPlot::yLeft ).invTransform( point.y() ) );
310478
}
311479

480+
312481
///@endcond

0 commit comments

Comments
 (0)