Skip to content

Commit

Permalink
[FEATURE] Show a histogram for values behind curve editor
Browse files Browse the repository at this point in the history
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 5c42c76
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 3 deletions.
2 changes: 1 addition & 1 deletion python/core/qgshistogram.sip
Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions python/gui/qgscurveeditorwidget.sip
Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion src/core/qgshistogram.cpp
Expand Up @@ -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 )
Expand Down
2 changes: 1 addition & 1 deletion src/core/qgshistogram.h
Expand Up @@ -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.
Expand Down
169 changes: 169 additions & 0 deletions src/gui/qgscurveeditorwidget.cpp
Expand Up @@ -20,6 +20,7 @@
#include <QPainter>
#include <QVBoxLayout>
#include <QMouseEvent>
#include <algorithm>

// QWT Charting widget
#include <qwt_global.h>
Expand All @@ -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 )
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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.