Skip to content
Permalink
Browse files

[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!
  • Loading branch information
nyalldawson committed Feb 22, 2017
1 parent 0faf7c3 commit 5c42c7636bc6857a6ca443d9ede235c23cfddc60
@@ -22,7 +22,7 @@ class QgsHistogram
*/
void setValues( const QList<double>& values );

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

/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
* determined by the inter-quartile range of values and the number of values.
@@ -7,10 +7,18 @@ class QgsCurveEditorWidget : QWidget
public:

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

QgsCurveTransform curve() const;

void setCurve( const QgsCurveTransform& curve );
void setHistogramSource( const QgsVectorLayer* layer, const QString& expression );
double minHistogramValueRange() const;
double maxHistogramValueRange() const;

public slots:
void setMinHistogramValueRange( double minValueRange );
void setMaxHistogramValueRange( double maxValueRange );

signals:

@@ -47,7 +47,7 @@ void QgsHistogram::setValues( const QList<double> &values )
prepareValues();
}

bool QgsHistogram::setValues( QgsVectorLayer *layer, const QString &fieldOrExpression, QgsFeedback* feedback )
bool QgsHistogram::setValues( const QgsVectorLayer *layer, const QString &fieldOrExpression, QgsFeedback* feedback )
{
mValues.clear();
if ( !layer )
@@ -53,7 +53,7 @@ class CORE_EXPORT QgsHistogram
* @param feedback optional feedback object to allow cancelation of calculation
* @returns true if values were successfully set
*/
bool setValues( QgsVectorLayer* layer, const QString& fieldOrExpression, QgsFeedback* feedback = nullptr );
bool setValues( const QgsVectorLayer* layer, const QString& fieldOrExpression, QgsFeedback* feedback = nullptr );

/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
* determined by the inter-quartile range of values and the number of values.
@@ -20,6 +20,7 @@
#include <QPainter>
#include <QVBoxLayout>
#include <QMouseEvent>
#include <algorithm>

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

#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
#include <qwt_plot_renderer.h>
#include <qwt_plot_histogram.h>
#else
#include "../raster/qwt5_histogram_item.h"
#endif

QgsCurveEditorWidget::QgsCurveEditorWidget( QWidget* parent, const QgsCurveTransform& transform )
: QWidget( parent )
, mCurve( transform )
@@ -86,13 +94,70 @@ QgsCurveEditorWidget::QgsCurveEditorWidget( QWidget* parent, const QgsCurveTrans
updatePlot();
}

QgsCurveEditorWidget::~QgsCurveEditorWidget()
{
if ( mGatherer && mGatherer->isRunning() )
{
connect( mGatherer.get(), &QgsHistogramValuesGatherer::finished, mGatherer.get(), &QgsHistogramValuesGatherer::deleteLater );
mGatherer->stop();
( void )mGatherer.release();
}
}

void QgsCurveEditorWidget::setCurve( const QgsCurveTransform& curve )
{
mCurve = curve;
updatePlot();
emit changed();
}

void QgsCurveEditorWidget::setHistogramSource( const QgsVectorLayer* layer, const QString& expression )
{
if ( !mGatherer )
{
mGatherer.reset( new QgsHistogramValuesGatherer() );
connect( mGatherer.get(), &QgsHistogramValuesGatherer::calculatedHistogram, this, [=]
{
mHistogram.reset( new QgsHistogram( mGatherer->histogram() ) );
updateHistogram();
} );
}

bool changed = mGatherer->layer() != layer || mGatherer->expression() != expression;
if ( changed )
{
mGatherer->setExpression( expression );
mGatherer->setLayer( layer );
mGatherer->start();
if ( mGatherer->isRunning() )
{
//stop any currently running task
mGatherer->stop();
while ( mGatherer->isRunning() )
{
QCoreApplication::processEvents();
}
}
mGatherer->start();
}
else
{
updateHistogram();
}
}

void QgsCurveEditorWidget::setMinHistogramValueRange( double minValueRange )
{
mMinValueRange = minValueRange;
updateHistogram();
}

void QgsCurveEditorWidget::setMaxHistogramValueRange( double maxValueRange )
{
mMaxValueRange = maxValueRange;
updateHistogram();
}

void QgsCurveEditorWidget::keyPressEvent( QKeyEvent* event )
{
if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
@@ -205,6 +270,63 @@ void QgsCurveEditorWidget::addPlotMarker( double x, double y, bool isSelected )
mMarkers << marker;
}

void QgsCurveEditorWidget::updateHistogram()
{
if ( !mHistogram )
return;

//draw histogram
QBrush histoBrush( QColor( 0, 0, 0, 70 ) );

#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
delete mPlotHistogram;
mPlotHistogram = createPlotHistogram( histoBrush );
QVector<QwtIntervalSample> dataHisto;
#else
delete mPlotHistogramItem;
mPlotHistogramItem = createHistoItem( histoBrush );
QwtArray<QwtDoubleInterval> intervalsHisto;
QwtArray<double> valuesHisto;
#endif

int bins = 40;
QList<double> edges = mHistogram->binEdges( bins );
QList<int> counts = mHistogram->counts( bins );

// scale counts to 0->1
double max = *std::max_element( counts.constBegin(), counts.constEnd() );

// scale bin edges to fit in 0->1 range
if ( !qgsDoubleNear( mMinValueRange, mMaxValueRange ) )
{
std::transform( edges.begin(), edges.end(), edges.begin(),
[this]( double d ) -> double { return ( d - mMinValueRange ) / ( mMaxValueRange - mMinValueRange ); } );
}

for ( int bin = 0; bin < bins; ++bin )
{
double binValue = counts.at( bin ) / max;

double upperEdge = edges.at( bin + 1 );

#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
dataHisto << QwtIntervalSample( binValue, edges.at( bin ), upperEdge );
#else
intervalsHisto.append( QwtDoubleInterval( edges.at( bin ), upperEdge ) );
valuesHisto.append( double( binValue ) );
#endif
}

#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
mPlotHistogram->setSamples( dataHisto );
mPlotHistogram->attach( mPlot );
#else
mPlotHistogramItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) );
mPlotHistogramItem->attach( mPlot );
#endif
mPlot->replot();
}

void QgsCurveEditorWidget::updatePlot()
{
// remove existing markers
@@ -249,6 +371,52 @@ void QgsCurveEditorWidget::updatePlot()
}


#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
QwtPlotHistogram* QgsCurveEditorWidget::createPlotHistogram( const QBrush& brush, const QPen& pen ) const
{
QwtPlotHistogram* histogram = new QwtPlotHistogram( QString() );
histogram->setBrush( brush );
if ( pen != Qt::NoPen )
{
histogram->setPen( pen );
}
else if ( brush.color().lightness() > 200 )
{
QPen p;
p.setColor( brush.color().darker( 150 ) );
p.setWidth( 0 );
p.setCosmetic( true );
histogram->setPen( p );
}
else
{
histogram->setPen( QPen( Qt::NoPen ) );
}
return histogram;
}
#else
HistogramItem * QgsCurveEditorWidget::createHistoItem( const QBrush& brush, const QPen& pen ) const
{
HistogramItem* item = new HistogramItem( QString() );
item->setColor( brush.color() );
item->setFlat( true );
item->setSpacing( 0 );
if ( pen != Qt::NoPen )
{
item->setPen( pen );
}
else if ( brush.color().lightness() > 200 )
{
QPen p;
p.setColor( brush.color().darker( 150 ) );
p.setWidth( 0 );
p.setCosmetic( true );
item->setPen( p );
}
return item;
}
#endif

/// @cond PRIVATE

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


///@endcond

0 comments on commit 5c42c76

Please sign in to comment.
You can’t perform that action at this time.