diff --git a/python/gui/gui.sip b/python/gui/gui.sip index 0402e93259f..377dffa2a70 100644 --- a/python/gui/gui.sip +++ b/python/gui/gui.sip @@ -56,6 +56,7 @@ %Include qgsfilterlineedit.sip %Include qgsformannotationitem.sip %Include qgsgenericprojectionselector.sip +%Include qgshistogramwidget.sip %Include qgshtmlannotationitem.sip %Include qgsidentifymenu.sip %Include qgslegendinterface.sip @@ -173,6 +174,7 @@ %Include symbology-ng/qgsdatadefinedsymboldialog.sip %Include symbology-ng/qgsstylev2exportimportdialog.sip %Include symbology-ng/qgssvgselectorwidget.sip +%Include symbology-ng/qgsgraduatedhistogramwidget.sip %Include effects/qgseffectdrawmodecombobox.sip %Include effects/qgspainteffectpropertieswidget.sip diff --git a/python/gui/qgshistogramwidget.sip b/python/gui/qgshistogramwidget.sip new file mode 100644 index 00000000000..68e89938152 --- /dev/null +++ b/python/gui/qgshistogramwidget.sip @@ -0,0 +1,107 @@ + +/** \ingroup gui + * \class QgsHistogramWidget + * \brief Graphical histogram for displaying distributions of field values. + * + * \note Added in version 2.9 + */ + +class QgsHistogramWidget : QWidget +{ +%TypeHeaderCode +#include +%End + + public: + + /** QgsHistogramWidget constructor. If layer and fieldOrExp are specified then the histogram + * will be initially populated with the corresponding values. + * @param parent parent widget + * @param layer source vector layer + * @param fieldOrExp field name or expression string + */ + QgsHistogramWidget( QWidget *parent /TransferThis/ = 0, QgsVectorLayer* layer = 0, const QString& fieldOrExp = QString() ); + + ~QgsHistogramWidget(); + + /** Returns the layer currently associated with the widget. + * @see setLayer + * @see sourceFieldExp + */ + QgsVectorLayer* layer(); + + /** Returns the source field name or expression used to calculate values displayed + * in the histogram. + * @see setSourceFieldExp + * @see layer + */ + QString sourceFieldExp() const; + + /** Sets the pen to use when drawing histogram bars. If set to Qt::NoPen then the + * pen will be automatically calculated. If ranges have been set using @link setGraduatedRanges @endlink + * then the pen and brush will have no effect. + * @param pen histogram pen + * @see pen + * @see setBrush + */ + void setPen( const QPen& pen ); + + /** Returns the pen used when drawing histogram bars. + * @see setPen + * @see brush + */ + QPen pen() const; + + /** Sets the brush used for drawing histogram bars. If ranges have been set using @link setGraduatedRanges @endlink + * then the pen and brush will have no effect. + * @param brush histogram brush + * @see brush + * @see setPen + */ + void setBrush( const QBrush& brush ); + + /** Returns the brush used when drawing histogram bars. + * @see setBrush + * @see pen + */ + QBrush brush() const; + + /** Sets the graduated ranges associated with the histogram. If set, the ranges will be used to colour the histogram + * bars and for showing vertical dividers at the histogram breaks. + * @param ranges graduated range list + */ + void setGraduatedRanges( const QgsRangeList& ranges ); + + /** Returns the graduated ranges associated with the histogram. If set, the ranges will be used to colour the histogram + * bars and for showing vertical dividers at the histogram breaks. + * @returns graduated range list + * @see setGraduatedRanges + */ + QgsRangeList graduatedRanges() const; + + public slots: + + /** Triggers a refresh of the histogram when the widget is next repainted. + */ + void refreshHistogram(); + + /** Sets the vector layer associated with the histogram. + * @param layer source vector layer + * @see setSourceFieldExp + */ + void setLayer( QgsVectorLayer* layer ); + + /** Sets the source field or expression to use for values in the histogram. + * @param fieldOrExp field name or expression string + * @see setLayer + */ + void setSourceFieldExp( const QString& fieldOrExp ); + + protected: + + /** Updates and redraws the histogram. + */ + virtual void drawHistogram(); + + virtual void paintEvent( QPaintEvent * event ); +}; diff --git a/python/gui/symbology-ng/qgsgraduatedhistogramwidget.sip b/python/gui/symbology-ng/qgsgraduatedhistogramwidget.sip new file mode 100644 index 00000000000..bf49745325c --- /dev/null +++ b/python/gui/symbology-ng/qgsgraduatedhistogramwidget.sip @@ -0,0 +1,42 @@ + +/** \ingroup gui + * \class QgsGraduatedHistogramWidget + * \brief Graphical histogram for displaying distribution of field values and + * editing range breaks for a QgsGraduatedSymbolRendererV2 renderer. + * + * \note Added in version 2.9 + */ + +class QgsGraduatedHistogramWidget : QWidget +{ +%TypeHeaderCode +#include +%End + + public: + + /** QgsGraduatedHistogramWidget constructor + * @param parent parent widget + */ + QgsGraduatedHistogramWidget( QWidget *parent /TransferThis/ = 0 ); + ~QgsGraduatedHistogramWidget(); + + /** Sets the QgsGraduatedSymbolRendererV2 renderer associated with the histogram. + * The histogram will fetch the ranges from the renderer before every refresh. + * @param renderer associated QgsGraduatedSymbolRendererV2 + */ + void setRenderer( QgsGraduatedSymbolRendererV2* renderer ); + + signals: + + /** Emitted when the user modifies the graduated ranges using the histogram widget. + * @param rangesAdded true if the user has added ranges, false if the user has just + * modified existing range breaks + */ + void rangesModified( bool rangesAdded ); + + protected: + + virtual void drawHistogram(); + +}; diff --git a/src/core/qgshistogram.cpp b/src/core/qgshistogram.cpp index 535d1cb1c29..4ede508a27e 100644 --- a/src/core/qgshistogram.cpp +++ b/src/core/qgshistogram.cpp @@ -103,7 +103,7 @@ QList QgsHistogram::counts( int bins ) const for ( int i = 0; i < bins; ++i ) { int count = 0; - while ( mValues.at( currentValueIndex ) < edges.at( i + 1 ) ) + while ( currentValueIndex < mValues.count() && mValues.at( currentValueIndex ) < edges.at( i + 1 ) ) { count++; currentValueIndex++; diff --git a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp index 98300d8100c..8774d016bc2 100644 --- a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp +++ b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp @@ -1357,6 +1357,37 @@ void QgsGraduatedSymbolRendererV2::addClass( double lower, double upper ) mRanges.append( QgsRendererRangeV2( lower, upper, newSymbol, label ) ); } +void QgsGraduatedSymbolRendererV2::addBreak( double breakValue, bool updateSymbols ) +{ + QMutableListIterator< QgsRendererRangeV2 > it( mRanges ); + while ( it.hasNext() ) + { + QgsRendererRangeV2 range = it.next(); + if ( range.lowerValue() < breakValue && range.upperValue() > breakValue ) + { + QgsRendererRangeV2 newRange = QgsRendererRangeV2(); + newRange.setLowerValue( breakValue ); + newRange.setUpperValue( range.upperValue() ); + newRange.setLabel( mLabelFormat.labelForRange( newRange ) ); + newRange.setSymbol( mSourceSymbol->clone() ); + + //update old range + bool isDefaultLabel = range.label() == mLabelFormat.labelForRange( range ); + range.setUpperValue( breakValue ); + if ( isDefaultLabel ) range.setLabel( mLabelFormat.labelForRange( range.lowerValue(), breakValue ) ); + it.setValue( range ); + + it.insert( newRange ); + break; + } + } + + if ( updateSymbols && mGraduatedMethod == GraduatedColor ) + { + updateColorRamp( mSourceColorRamp.data(), mInvertedColorRamp ); + } +} + void QgsGraduatedSymbolRendererV2::addClass( QgsRendererRangeV2 range ) { mRanges.append( range ); diff --git a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h index caa005d1c4e..a60e129c3dd 100644 --- a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h +++ b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h @@ -157,6 +157,16 @@ class CORE_EXPORT QgsGraduatedSymbolRendererV2 : public QgsFeatureRendererV2 void addClass( QgsRendererRangeV2 range ); //! @note available in python bindings as addClassLowerUpper void addClass( double lower, double upper ); + + /** Add a breakpoint by splitting existing classes so that the specified + * value becomes a break between two classes. + * @param breakValue position to insert break + * @param updateSymbols set to true to reapply ramp colors to the new + * symbol ranges + * @note added in QGIS 2.9 + */ + void addBreak( double breakValue, bool updateSymbols = true ); + void deleteClass( int idx ); void deleteAllClasses(); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index f23dc294c2b..0f6cd012dd5 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -8,6 +8,7 @@ raster/qgspalettedrendererwidget.cpp raster/qgssinglebandgrayrendererwidget.cpp raster/qgssinglebandpseudocolorrendererwidget.cpp raster/qgsrasterhistogramwidget.cpp +raster/qwt5_histogram_item.cpp symbology-ng/qgsbrushstylecombobox.cpp symbology-ng/qgscolorrampcombobox.cpp @@ -19,6 +20,7 @@ symbology-ng/qgsrendererv2widget.cpp symbology-ng/qgssinglesymbolrendererv2widget.cpp symbology-ng/qgscategorizedsymbolrendererv2widget.cpp symbology-ng/qgsgraduatedsymbolrendererv2widget.cpp +symbology-ng/qgsgraduatedhistogramwidget.cpp symbology-ng/qgsrulebasedrendererv2widget.cpp symbology-ng/qgsheatmaprendererwidget.cpp symbology-ng/qgsinvertedpolygonrendererwidget.cpp @@ -174,6 +176,7 @@ qgsfilterlineedit.cpp qgsformannotationitem.cpp qgsgenericprojectionselector.cpp qgshighlight.cpp +qgshistogramwidget.cpp qgsidentifymenu.cpp qgshtmlannotationitem.cpp qgslegendinterface.cpp @@ -287,6 +290,7 @@ SET(QGIS_GUI_MOC_HDRS qgsfilterlineedit.h qgsformannotationitem.h qgsgenericprojectionselector.h + qgshistogramwidget.h qgshtmlannotationitem.h qgsidentifymenu.h qgslegendinterface.h @@ -352,6 +356,7 @@ SET(QGIS_GUI_MOC_HDRS symbology-ng/qgsdatadefinedsymboldialog.h symbology-ng/qgsellipsesymbollayerv2widget.h symbology-ng/qgsgraduatedsymbolrendererv2widget.h + symbology-ng/qgsgraduatedhistogramwidget.h symbology-ng/qgsheatmaprendererwidget.h symbology-ng/qgsinvertedpolygonrendererwidget.h symbology-ng/qgslayerpropertieswidget.h diff --git a/src/gui/qgshistogramwidget.cpp b/src/gui/qgshistogramwidget.cpp new file mode 100644 index 00000000000..04e4a8ba0bb --- /dev/null +++ b/src/gui/qgshistogramwidget.cpp @@ -0,0 +1,353 @@ +/*************************************************************************** + qgshistogramwidget.cpp + ---------------------- + begin : May 2015 + copyright : (C) 2015 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgshistogramwidget.h" +#include "qgsapplication.h" +#include "qgsvectorlayer.h" +#include "qgsstatisticalsummary.h" + +#include +#include +#include + +// QWT Charting widget +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 +#include +#include +#else +#include "../raster/qwt5_histogram_item.h" +#endif + + +QgsHistogramWidget::QgsHistogramWidget( QWidget *parent, QgsVectorLayer* layer, const QString& fieldOrExp ) + : QWidget( parent ) + , mRedrawRequired( true ) + , mVectorLayer( layer ) + , mSourceFieldExp( fieldOrExp ) +{ + setupUi( this ); + + mPlot = mpPlot; + + // hide the ugly canvas frame +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 + QFrame* plotCanvasFrame = dynamic_cast( mpPlot->canvas() ); + if ( plotCanvasFrame ) + plotCanvasFrame->setFrameStyle( QFrame::NoFrame ); +#else + mpPlot->canvas()->setFrameStyle( QFrame::NoFrame ); +#endif + + QSettings settings; + mMeanCheckBox->setChecked( settings.value( "/HistogramWidget/showMean", false ).toBool() ); + mStdevCheckBox->setChecked( settings.value( "/HistogramWidget/showStdev", false ).toBool() ); + + connect( mBinsSpinBox, SIGNAL( valueChanged( int ) ), this, SLOT( refreshHistogram() ) ); + connect( mMeanCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( refreshHistogram() ) ); + connect( mStdevCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( refreshHistogram() ) ); + + mGridPen = QPen( QColor( 0, 0, 0, 40 ) ); + mMeanPen = QPen( QColor( 10, 10, 10, 220 ) ); + mMeanPen.setStyle( Qt::DashLine ); + mStdevPen = QPen( QColor( 30, 30, 30, 200 ) ); + mStdevPen.setStyle( Qt::DashLine ); + + if ( layer && !mSourceFieldExp.isEmpty() ) + { + refreshHistogram(); + } +} + +QgsHistogramWidget::~QgsHistogramWidget() +{ + QSettings settings; + settings.setValue( "/HistogramWidget/showMean", mMeanCheckBox->isChecked() ); + settings.setValue( "/HistogramWidget/showStdev", mStdevCheckBox->isChecked() ); +} + +void QgsHistogramWidget::setLayer( QgsVectorLayer *layer ) +{ + if ( layer == mVectorLayer ) + return; + + mVectorLayer = layer; + mValues.clear(); + mRedrawRequired = true; +} + +void QgsHistogramWidget::refreshHistogram() +{ + mRedrawRequired = true; +} + +void QgsHistogramWidget::setSourceFieldExp( const QString &fieldOrExp ) +{ + if ( fieldOrExp == mSourceFieldExp ) + return; + + mSourceFieldExp = fieldOrExp; + mValues.clear(); + mRedrawRequired = true; +} + +void QgsHistogramWidget::drawHistogram() +{ + if ( !mVectorLayer || mSourceFieldExp.isEmpty() ) + return; + + QApplication::setOverrideCursor( Qt::WaitCursor ); + + if ( mValues.empty() ) + { + bool ok; + mValues = mVectorLayer->getDoubleValues( mSourceFieldExp, ok ); + + if ( ! ok ) + { + QApplication::restoreOverrideCursor(); + return; + } + qSort( mValues.begin(), mValues.end() ); + mHistogram.setValues( mValues ); + mBinsSpinBox->blockSignals( true ); + mBinsSpinBox->setValue( qMax( mHistogram.optimalNumberBins(), 30 ) ); + mBinsSpinBox->blockSignals( false ); + + mStats.setStatistics( QgsStatisticalSummary::StDev ); + mStats.calculate( mValues ); + } + + // clear plot + mpPlot->detachItems(); + + //ensure all children get removed + mpPlot->setAutoDelete( true ); + // Set axis titles + mpPlot->setAxisTitle( QwtPlot::xBottom, QObject::tr( "Value" ) ); + mpPlot->setAxisTitle( QwtPlot::yLeft, QObject::tr( "Count" ) ); + mpPlot->setAxisAutoScale( QwtPlot::yLeft ); + mpPlot->setAxisAutoScale( QwtPlot::xBottom ); + + // add a grid + QwtPlotGrid * grid = new QwtPlotGrid(); + grid->enableX( false ); + grid->setPen( mGridPen ); + grid->attach( mpPlot ); + + // make colors list + mHistoColors.clear(); + foreach ( QgsRendererRangeV2 range, mRanges ) + { + mHistoColors << ( range.symbol() ? range.symbol()->color() : Qt::black ); + } + + //draw histogram +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 + QwtPlotHistogram * plotHistogram = 0; + plotHistogram = createPlotHistogram( mRanges.count() > 0 ? mRanges.at( 0 ).label() : QString(), + mRanges.count() > 0 ? QBrush( mHistoColors.at( 0 ) ) : mBrush, + mRanges.count() > 0 ? Qt::NoPen : mPen ); +#else + HistogramItem *plotHistogramItem = 0; + plotHistogramItem = createHistoItem( mRanges.count() > 0 ? mRanges.at( 0 ).label() : QString(), + mRanges.count() > 0 ? QBrush( mHistoColors.at( 0 ) ) : mBrush, + mRanges.count() > 0 ? Qt::NoPen : mPen ); +#endif + +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 + QVector dataHisto; +#else + + // we safely assume that QT>=4.0 (min version is 4.7), therefore QwtArray is a QVector, so don't set size here + QwtArray intervalsHisto; + QwtArray valuesHisto; + +#endif + + int bins = mBinsSpinBox->value(); + QList edges = mHistogram.binEdges( bins ); + QList counts = mHistogram.counts( bins ); + + int rangeIndex = 0; + int lastValue = 0; + + for ( int bin = 0; bin < bins; ++bin ) + { + int binValue = counts.at( bin ); + + //current bin crosses two graduated ranges, so we split between + //two histogram items + if ( rangeIndex < mRanges.count() - 1 && edges.at( bin ) > mRanges.at( rangeIndex ).upperValue() ) + { + rangeIndex++; +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 + plotHistogram->setSamples( dataHisto ); + plotHistogram->attach( mpPlot ); + plotHistogram = createPlotHistogram( mRanges.at( rangeIndex ).label(), mHistoColors.at( rangeIndex ) ); + dataHisto.clear(); + dataHisto << QwtIntervalSample( lastValue, mRanges.at( rangeIndex - 1 ).upperValue(), edges.at( bin ) ); +#else + plotHistogramItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) ); + plotHistogramItem->attach( mpPlot ); + plotHistogramItem = createHistoItem( mRanges.at( rangeIndex ).label(), mHistoColors.at( rangeIndex ) ); + intervalsHisto.clear(); + valuesHisto.clear(); + intervalsHisto.append( QwtDoubleInterval( mRanges.at( rangeIndex - 1 ).upperValue(), edges.at( bin ) ) ); + valuesHisto.append( lastValue ); +#endif + } + + double upperEdge = mRanges.count() > 0 ? qMin( edges.at( bin + 1 ), mRanges.at( rangeIndex ).upperValue() ) + : 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 + + lastValue = binValue; + } + +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 + plotHistogram->setSamples( dataHisto ); + plotHistogram->attach( mpPlot ); +#else + plotHistogramItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) ); + plotHistogramItem->attach( mpPlot ); +#endif + + mRangeMarkers.clear(); + foreach ( QgsRendererRangeV2 range, mRanges ) + { + QwtPlotMarker* rangeMarker = new QwtPlotMarker(); + rangeMarker->attach( mpPlot ); + rangeMarker->setLineStyle( QwtPlotMarker::VLine ); + rangeMarker->setXValue( range.upperValue() ); + rangeMarker->setLabel( QString::number( range.upperValue() ) ); + rangeMarker->setLabelOrientation( Qt::Vertical ); + rangeMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop ); + rangeMarker->show(); + mRangeMarkers << rangeMarker; + } + + if ( mMeanCheckBox->isChecked() ) + { + QwtPlotMarker* meanMarker = new QwtPlotMarker(); + meanMarker->attach( mpPlot ); + meanMarker->setLineStyle( QwtPlotMarker::VLine ); + meanMarker->setLinePen( mMeanPen ); + meanMarker->setXValue( mStats.mean() ); + meanMarker->setLabel( QString( QChar( 956 ) ) ); + meanMarker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop ); + meanMarker->show(); + } + + if ( mStdevCheckBox->isChecked() ) + { + QwtPlotMarker* stdev1Marker = new QwtPlotMarker(); + stdev1Marker->attach( mpPlot ); + stdev1Marker->setLineStyle( QwtPlotMarker::VLine ); + stdev1Marker->setLinePen( mStdevPen ); + stdev1Marker->setXValue( mStats.mean() - mStats.stDev() ); + stdev1Marker->setLabel( QString( QChar( 963 ) ) ); + stdev1Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop ); + stdev1Marker->show(); + + QwtPlotMarker* stdev2Marker = new QwtPlotMarker(); + stdev2Marker->attach( mpPlot ); + stdev2Marker->setLineStyle( QwtPlotMarker::VLine ); + stdev2Marker->setLinePen( mStdevPen ); + stdev2Marker->setXValue( mStats.mean() + mStats.stDev() ); + stdev2Marker->setLabel( QString( QChar( 963 ) ) ); + stdev2Marker->setLabelAlignment( Qt::AlignLeft | Qt::AlignTop ); + stdev2Marker->show(); + } + + mpPlot->setEnabled( true ); + mpPlot->replot(); + + QApplication::restoreOverrideCursor(); + + mRedrawRequired = false; +} + +void QgsHistogramWidget::paintEvent( QPaintEvent *event ) +{ + if ( mRedrawRequired ) + { + drawHistogram(); + } + QWidget::paintEvent( event ); +} + +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 +QwtPlotHistogram* QgsHistogramWidget::createPlotHistogram( const QString& title, const QBrush& brush, const QPen& pen ) const +{ + QwtPlotHistogram* histogram = new QwtPlotHistogram( title ); + 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 * QgsHistogramWidget::createHistoItem( const QString& title, const QBrush& brush, const QPen& pen ) const +{ + HistogramItem* item = new HistogramItem( title ); + 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 + diff --git a/src/gui/qgshistogramwidget.h b/src/gui/qgshistogramwidget.h new file mode 100644 index 00000000000..04b9f2b7cac --- /dev/null +++ b/src/gui/qgshistogramwidget.h @@ -0,0 +1,174 @@ +/*************************************************************************** + qgshistogramwidget.h + -------------------- + begin : May 2015 + copyright : (C) 2015 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSHISTOGRAMWIDGET_H +#define QGSHISTOGRAMWIDGET_H + +#include "ui_qgshistogramwidgetbase.h" + +#include "qgshistogram.h" +#include "qgsstatisticalsummary.h" +#include "qgsgraduatedsymbolrendererv2.h" +#include +#include + +class QgsVectorLayer; +class QgsGraduatedSymbolRendererV2; +class QwtPlotPicker; +class QwtPlotMarker; +class QwtPlot; +class HistogramItem; +class QwtPlotHistogram; + +// fix for qwt5/qwt6 QwtDoublePoint vs. QPointF +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 +typedef QPointF QwtDoublePoint; +#endif + + +/** \ingroup gui + * \class QgsHistogramWidget + * \brief Graphical histogram for displaying distributions of field values. + * + * \note Added in version 2.9 + */ + +class GUI_EXPORT QgsHistogramWidget : public QWidget, private Ui::QgsHistogramWidgetBase +{ + Q_OBJECT + + public: + + /** QgsHistogramWidget constructor. If layer and fieldOrExp are specified then the histogram + * will be initially populated with the corresponding values. + * @param parent parent widget + * @param layer source vector layer + * @param fieldOrExp field name or expression string + */ + QgsHistogramWidget( QWidget *parent = 0, QgsVectorLayer* layer = 0, const QString& fieldOrExp = QString() ); + + ~QgsHistogramWidget(); + + /** Returns the layer currently associated with the widget. + * @see setLayer + * @see sourceFieldExp + */ + QgsVectorLayer* layer() { return mVectorLayer; } + + /** Returns the source field name or expression used to calculate values displayed + * in the histogram. + * @see setSourceFieldExp + * @see layer + */ + QString sourceFieldExp() const { return mSourceFieldExp; } + + /** Sets the pen to use when drawing histogram bars. If set to Qt::NoPen then the + * pen will be automatically calculated. If ranges have been set using @link setGraduatedRanges @endlink + * then the pen and brush will have no effect. + * @param pen histogram pen + * @see pen + * @see setBrush + */ + void setPen( const QPen& pen ) { mPen = pen; } + + /** Returns the pen used when drawing histogram bars. + * @see setPen + * @see brush + */ + QPen pen() const { return mPen; } + + /** Sets the brush used for drawing histogram bars. If ranges have been set using @link setGraduatedRanges @endlink + * then the pen and brush will have no effect. + * @param brush histogram brush + * @see brush + * @see setPen + */ + void setBrush( const QBrush& brush ) { mBrush = brush; } + + /** Returns the brush used when drawing histogram bars. + * @see setBrush + * @see pen + */ + QBrush brush() const { return mBrush; } + + /** Sets the graduated ranges associated with the histogram. If set, the ranges will be used to colour the histogram + * bars and for showing vertical dividers at the histogram breaks. + * @param ranges graduated range list + * @see graduatedRanges + */ + void setGraduatedRanges( const QgsRangeList& ranges ) { mRanges = ranges; } + + /** Returns the graduated ranges associated with the histogram. If set, the ranges will be used to colour the histogram + * bars and for showing vertical dividers at the histogram breaks. + * @returns graduated range list + * @see setGraduatedRanges + */ + QgsRangeList graduatedRanges() const { return mRanges; } + + public slots: + + /** Triggers a refresh of the histogram when the widget is next repainted. + */ + void refreshHistogram(); + + /** Sets the vector layer associated with the histogram. + * @param layer source vector layer + * @see setSourceFieldExp + */ + void setLayer( QgsVectorLayer* layer ); + + /** Sets the source field or expression to use for values in the histogram. + * @param fieldOrExp field name or expression string + * @see setLayer + */ + void setSourceFieldExp( const QString& fieldOrExp ); + + protected: + + /** Updates and redraws the histogram. + */ + virtual void drawHistogram(); + + virtual void paintEvent( QPaintEvent * event ) override; + + QwtPlot* mPlot; + QgsRangeList mRanges; + QList< QwtPlotMarker* > mRangeMarkers; + bool mRedrawRequired; + + private: + + QgsVectorLayer * mVectorLayer; + QString mSourceFieldExp; + QList mValues; + QgsStatisticalSummary mStats; + QgsHistogram mHistogram; + QVector mHistoColors; + QPen mPen; + QBrush mBrush; + QPen mMeanPen; + QPen mStdevPen; + QPen mGridPen; + +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 + QwtPlotHistogram* createPlotHistogram( const QString& title, const QBrush &brush, const QPen &pen = Qt::NoPen ) const; +#else + HistogramItem* createHistoItem( const QString& title, const QBrush& brush, const QPen& pen = Qt::NoPen ) const; +#endif + +}; + +#endif //QGSHISTOGRAMWIDGET_H diff --git a/src/gui/raster/qwt5_histogram_item.cpp b/src/gui/raster/qwt5_histogram_item.cpp new file mode 100644 index 00000000000..cd142d6a9dd --- /dev/null +++ b/src/gui/raster/qwt5_histogram_item.cpp @@ -0,0 +1,397 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ +#include + +#if defined( QWT_VERSION ) && QWT_VERSION<0x060000 + +#include +#include + +#include "qwt5_histogram_item.h" + +HistogramItem::HistogramItem( const QwtText &title ): + QwtPlotItem( title ) +{ + init(); +} + +HistogramItem::HistogramItem( const QString &title ): + QwtPlotItem( QwtText( title ) ) +{ + init(); +} + +HistogramItem::~HistogramItem() +{ + delete d_data; +} + +void HistogramItem::init() +{ + d_data = new PrivateData(); + d_data->reference = 0.0; + d_data->attributes = HistogramItem::Auto; + d_data->flat = false; + d_data->spacing = 1; + d_data->pen = Qt::NoPen; + + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Legend, true ); + + setZ( 20.0 ); +} + +void HistogramItem::setBaseline( double reference ) +{ + if ( d_data->reference != reference ) + { + d_data->reference = reference; + itemChanged(); + } +} + +double HistogramItem::baseline() const +{ + return d_data->reference; +} + +void HistogramItem::setData( const QwtIntervalData &data ) +{ + d_data->data = data; + itemChanged(); +} + +const QwtIntervalData &HistogramItem::data() const +{ + return d_data->data; +} + +void HistogramItem::setColor( const QColor &color ) +{ + if ( d_data->color != color ) + { + d_data->color = color; + itemChanged(); + } +} + +QColor HistogramItem::color() const +{ + return d_data->color; +} + +void HistogramItem::setFlat( bool flat ) +{ + if ( d_data->flat != flat ) + { + d_data->flat = flat; + itemChanged(); + } +} + +bool HistogramItem::flat() const +{ + return d_data->flat; +} + +void HistogramItem::setSpacing( int spacing ) +{ + if ( d_data->spacing != spacing ) + { + d_data->spacing = spacing; + itemChanged(); + } +} + +int HistogramItem::spacing() const +{ + return d_data->spacing; +} + +void HistogramItem::setPen( const QPen &pen ) +{ + if ( d_data->pen != pen ) + { + d_data->pen = pen; + itemChanged(); + } +} + +QPen HistogramItem::pen() const +{ + return d_data->pen; +} + +QwtDoubleRect HistogramItem::boundingRect() const +{ + QwtDoubleRect rect = d_data->data.boundingRect(); + if ( !rect.isValid() ) + return rect; + + if ( d_data->attributes & Xfy ) + { + rect = QwtDoubleRect( rect.y(), rect.x(), + rect.height(), rect.width() ); + + if ( rect.left() > d_data->reference ) + rect.setLeft( d_data->reference ); + else if ( rect.right() < d_data->reference ) + rect.setRight( d_data->reference ); + } + else + { + if ( rect.bottom() < d_data->reference ) + rect.setBottom( d_data->reference ); + else if ( rect.top() > d_data->reference ) + rect.setTop( d_data->reference ); + } + + return rect; +} + + +int HistogramItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotHistogram; +} + +void HistogramItem::setHistogramAttribute( HistogramAttribute attribute, bool on ) +{ + if ( bool( d_data->attributes & attribute ) == on ) + return; + + if ( on ) + d_data->attributes |= attribute; + else + d_data->attributes &= ~attribute; + + itemChanged(); +} + +bool HistogramItem::testHistogramAttribute( HistogramAttribute attribute ) const +{ + return d_data->attributes & attribute; +} + +void HistogramItem::draw( QPainter *painter, const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QRect & ) const +{ + const QwtIntervalData &iData = d_data->data; + const int x0 = xMap.transform( baseline() ); + const int y0 = yMap.transform( baseline() ); + + for ( int i = 0; i < ( int )iData.size(); i++ ) + { + if ( d_data->attributes & HistogramItem::Xfy ) + { + const int x2 = xMap.transform( iData.value( i ) ); + if ( x2 == x0 ) + continue; + + int y1 = yMap.transform( iData.interval( i ).minValue() ); + int y2 = yMap.transform( iData.interval( i ).maxValue() ); + if ( y1 > y2 ) + qSwap( y1, y2 ); + + if ( i < ( int )iData.size() - 2 ) + { + const int yy1 = yMap.transform( iData.interval( i + 1 ).minValue() ); + const int yy2 = yMap.transform( iData.interval( i + 1 ).maxValue() ); + + if ( y2 == qwtMin( yy1, yy2 ) ) + { + const int xx2 = xMap.transform( + iData.interval( i + 1 ).minValue() ); + if ( xx2 != x0 && (( xx2 < x0 && x2 < x0 ) || + ( xx2 > x0 && x2 > x0 ) ) ) + { + // distance between neighboured bars + y2 += d_data->spacing; + } + } + } + + drawBar( painter, Qt::Horizontal, + QRect( x0, y1, x2 - x0, y2 - y1 ) ); + } + else + { + const int y2 = yMap.transform( iData.value( i ) ); + if ( y2 == y0 ) + continue; + + int x1 = xMap.transform( iData.interval( i ).minValue() ); + int x2 = xMap.transform( iData.interval( i ).maxValue() ); + if ( x1 > x2 ) + qSwap( x1, x2 ); + + if ( i < ( int )iData.size() - 2 ) + { + const int xx1 = xMap.transform( iData.interval( i + 1 ).minValue() ); + const int xx2 = xMap.transform( iData.interval( i + 1 ).maxValue() ); + + if ( x2 == qwtMin( xx1, xx2 ) ) + { + const int yy2 = yMap.transform( iData.value( i + 1 ) ); + if ( yy2 != y0 && (( yy2 < y0 && y2 < y0 ) || + ( yy2 > y0 && y2 > y0 ) ) ) + { + //distance between neighboured bars + x2 -= d_data->spacing; + } + } + } + drawBar( painter, Qt::Vertical, + QRect( x1, y0, x2 - x1, y2 - y0 ) ); + } + } +} + +void HistogramItem::drawBar( QPainter *painter, + Qt::Orientation, const QRect& rect ) const +{ + painter->save(); + +#if QT_VERSION >= 0x040000 + const QRect r = rect.normalized(); +#else + const QRect r = rect.normalize(); +#endif + + painter->setBrush( d_data->color ); + + if ( d_data->flat ) + { + painter->setPen( d_data->pen ); + int penWidth = d_data->pen == Qt::NoPen ? 0 : + ( d_data->pen.isCosmetic() ? 1 : d_data->pen.width() ); + QwtPainter::drawRect( painter, r.x(), r.y(), + r.width(), r.height() - penWidth ); + } + else + { + const int factor = 125; + const QColor light( d_data->color.light( factor ) ); + const QColor dark( d_data->color.dark( factor ) ); + + QwtPainter::drawRect( painter, r.x() + 1, r.y() + 1, + r.width() - 2, r.height() - 2 ); + + painter->setBrush( Qt::NoBrush ); + + painter->setPen( QPen( light, 2 ) ); +#if QT_VERSION >= 0x040000 + QwtPainter::drawLine( painter, + r.left() + 1, r.top() + 2, r.right() + 1, r.top() + 2 ); +#else + QwtPainter::drawLine( painter, + r.left(), r.top() + 2, r.right() + 1, r.top() + 2 ); +#endif + + painter->setPen( QPen( dark, 2 ) ); +#if QT_VERSION >= 0x040000 + QwtPainter::drawLine( painter, + r.left() + 1, r.bottom(), r.right() + 1, r.bottom() ); +#else + QwtPainter::drawLine( painter, + r.left(), r.bottom(), r.right() + 1, r.bottom() ); +#endif + + painter->setPen( QPen( light, 1 ) ); + +#if QT_VERSION >= 0x040000 + QwtPainter::drawLine( painter, + r.left(), r.top() + 1, r.left(), r.bottom() ); + QwtPainter::drawLine( painter, + r.left() + 1, r.top() + 2, r.left() + 1, r.bottom() - 1 ); +#else + QwtPainter::drawLine( painter, + r.left(), r.top() + 1, r.left(), r.bottom() + 1 ); + QwtPainter::drawLine( painter, + r.left() + 1, r.top() + 2, r.left() + 1, r.bottom() ); +#endif + + painter->setPen( QPen( dark, 1 ) ); + +#if QT_VERSION >= 0x040000 + QwtPainter::drawLine( painter, + r.right() + 1, r.top() + 1, r.right() + 1, r.bottom() ); + QwtPainter::drawLine( painter, + r.right(), r.top() + 2, r.right(), r.bottom() - 1 ); +#else + QwtPainter::drawLine( painter, + r.right() + 1, r.top() + 1, r.right() + 1, r.bottom() + 1 ); + QwtPainter::drawLine( painter, + r.right(), r.top() + 2, r.right(), r.bottom() ); +#endif + } + + painter->restore(); +} + +//! Update the widget that represents the curve on the legend +// this was adapted from QwtPlotCurve::updateLegend() +void HistogramItem::updateLegend( QwtLegend *legend ) const +{ + if ( !legend ) + return; + + QwtPlotItem::updateLegend( legend ); + + QWidget *widget = legend->find( this ); + if ( !widget || !widget->inherits( "QwtLegendItem" ) ) + return; + + QwtLegendItem *legendItem = ( QwtLegendItem * )widget; + +#if QT_VERSION < 0x040000 + const bool doUpdate = legendItem->isUpdatesEnabled(); +#else + const bool doUpdate = legendItem->updatesEnabled(); +#endif + legendItem->setUpdatesEnabled( false ); + + const int policy = legend->displayPolicy(); + + if ( policy == QwtLegend::FixedIdentifier ) + { + int mode = legend->identifierMode(); + + legendItem->setCurvePen( QPen( color() ) ); + + if ( mode & QwtLegendItem::ShowText ) + legendItem->setText( title() ); + else + legendItem->setText( QwtText() ); + + legendItem->setIdentifierMode( mode ); + } + else if ( policy == QwtLegend::AutoIdentifier ) + { + int mode = 0; + + legendItem->setCurvePen( QPen( color() ) ); + mode |= QwtLegendItem::ShowLine; + if ( !title().isEmpty() ) + { + legendItem->setText( title() ); + mode |= QwtLegendItem::ShowText; + } + else + { + legendItem->setText( QwtText() ); + } + legendItem->setIdentifierMode( mode ); + } + + legendItem->setUpdatesEnabled( doUpdate ); + legendItem->update(); +} + +#endif diff --git a/src/gui/raster/qwt5_histogram_item.h b/src/gui/raster/qwt5_histogram_item.h index 9940f686ac1..7fb558390a2 100644 --- a/src/gui/raster/qwt5_histogram_item.h +++ b/src/gui/raster/qwt5_histogram_item.h @@ -6,6 +6,9 @@ * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ +#include + +#if defined(QWT_VERSION) && QWT_VERSION<0x060000 #ifndef HISTOGRAM_ITEM_H #define HISTOGRAM_ITEM_H @@ -31,6 +34,15 @@ class HistogramItem: public QwtPlotItem void setColor( const QColor & ); QColor color() const; + void setFlat( bool flat ); + bool flat() const; + + void setSpacing( int spacing ); + int spacing() const; + + void setPen( const QPen& pen ); + QPen pen() const; + virtual QwtDoubleRect boundingRect() const; virtual int rtti() const; @@ -77,333 +89,11 @@ class HistogramItem::PrivateData int attributes; QwtIntervalData data; QColor color; + QPen pen; double reference; + bool flat; + int spacing; }; -HistogramItem::HistogramItem( const QwtText &title ): - QwtPlotItem( title ) -{ - init(); -} - -HistogramItem::HistogramItem( const QString &title ): - QwtPlotItem( QwtText( title ) ) -{ - init(); -} - -HistogramItem::~HistogramItem() -{ - delete d_data; -} - -void HistogramItem::init() -{ - d_data = new PrivateData(); - d_data->reference = 0.0; - d_data->attributes = HistogramItem::Auto; - - setItemAttribute( QwtPlotItem::AutoScale, true ); - setItemAttribute( QwtPlotItem::Legend, true ); - - setZ( 20.0 ); -} - -void HistogramItem::setBaseline( double reference ) -{ - if ( d_data->reference != reference ) - { - d_data->reference = reference; - itemChanged(); - } -} - -double HistogramItem::baseline() const -{ - return d_data->reference; -} - -void HistogramItem::setData( const QwtIntervalData &data ) -{ - d_data->data = data; - itemChanged(); -} - -const QwtIntervalData &HistogramItem::data() const -{ - return d_data->data; -} - -void HistogramItem::setColor( const QColor &color ) -{ - if ( d_data->color != color ) - { - d_data->color = color; - itemChanged(); - } -} - -QColor HistogramItem::color() const -{ - return d_data->color; -} - -QwtDoubleRect HistogramItem::boundingRect() const -{ - QwtDoubleRect rect = d_data->data.boundingRect(); - if ( !rect.isValid() ) - return rect; - - if ( d_data->attributes & Xfy ) - { - rect = QwtDoubleRect( rect.y(), rect.x(), - rect.height(), rect.width() ); - - if ( rect.left() > d_data->reference ) - rect.setLeft( d_data->reference ); - else if ( rect.right() < d_data->reference ) - rect.setRight( d_data->reference ); - } - else - { - if ( rect.bottom() < d_data->reference ) - rect.setBottom( d_data->reference ); - else if ( rect.top() > d_data->reference ) - rect.setTop( d_data->reference ); - } - - return rect; -} - - -int HistogramItem::rtti() const -{ - return QwtPlotItem::Rtti_PlotHistogram; -} - -void HistogramItem::setHistogramAttribute( HistogramAttribute attribute, bool on ) -{ - if ( bool( d_data->attributes & attribute ) == on ) - return; - - if ( on ) - d_data->attributes |= attribute; - else - d_data->attributes &= ~attribute; - - itemChanged(); -} - -bool HistogramItem::testHistogramAttribute( HistogramAttribute attribute ) const -{ - return d_data->attributes & attribute; -} - -void HistogramItem::draw( QPainter *painter, const QwtScaleMap &xMap, - const QwtScaleMap &yMap, const QRect & ) const -{ - const QwtIntervalData &iData = d_data->data; - - painter->setPen( QPen( d_data->color ) ); - - const int x0 = xMap.transform( baseline() ); - const int y0 = yMap.transform( baseline() ); - - for ( int i = 0; i < ( int )iData.size(); i++ ) - { - if ( d_data->attributes & HistogramItem::Xfy ) - { - const int x2 = xMap.transform( iData.value( i ) ); - if ( x2 == x0 ) - continue; - - int y1 = yMap.transform( iData.interval( i ).minValue() ); - int y2 = yMap.transform( iData.interval( i ).maxValue() ); - if ( y1 > y2 ) - qSwap( y1, y2 ); - - if ( i < ( int )iData.size() - 2 ) - { - const int yy1 = yMap.transform( iData.interval( i + 1 ).minValue() ); - const int yy2 = yMap.transform( iData.interval( i + 1 ).maxValue() ); - - if ( y2 == qwtMin( yy1, yy2 ) ) - { - const int xx2 = xMap.transform( - iData.interval( i + 1 ).minValue() ); - if ( xx2 != x0 && (( xx2 < x0 && x2 < x0 ) || - ( xx2 > x0 && x2 > x0 ) ) ) - { - // One pixel distance between neighboured bars - y2++; - } - } - } - - drawBar( painter, Qt::Horizontal, - QRect( x0, y1, x2 - x0, y2 - y1 ) ); - } - else - { - const int y2 = yMap.transform( iData.value( i ) ); - if ( y2 == y0 ) - continue; - - int x1 = xMap.transform( iData.interval( i ).minValue() ); - int x2 = xMap.transform( iData.interval( i ).maxValue() ); - if ( x1 > x2 ) - qSwap( x1, x2 ); - - if ( i < ( int )iData.size() - 2 ) - { - const int xx1 = xMap.transform( iData.interval( i + 1 ).minValue() ); - const int xx2 = xMap.transform( iData.interval( i + 1 ).maxValue() ); - - if ( x2 == qwtMin( xx1, xx2 ) ) - { - const int yy2 = yMap.transform( iData.value( i + 1 ) ); - if ( yy2 != y0 && (( yy2 < y0 && y2 < y0 ) || - ( yy2 > y0 && y2 > y0 ) ) ) - { - // One pixel distance between neighboured bars - x2--; - } - } - } - drawBar( painter, Qt::Vertical, - QRect( x1, y0, x2 - x1, y2 - y0 ) ); - } - } -} - -void HistogramItem::drawBar( QPainter *painter, - Qt::Orientation, const QRect& rect ) const -{ - painter->save(); - - const QColor color( painter->pen().color() ); -#if QT_VERSION >= 0x040000 - const QRect r = rect.normalized(); -#else - const QRect r = rect.normalize(); #endif - - const int factor = 125; - const QColor light( color.light( factor ) ); - const QColor dark( color.dark( factor ) ); - - painter->setBrush( color ); - painter->setPen( Qt::NoPen ); - QwtPainter::drawRect( painter, r.x() + 1, r.y() + 1, - r.width() - 2, r.height() - 2 ); - painter->setBrush( Qt::NoBrush ); - - painter->setPen( QPen( light, 2 ) ); -#if QT_VERSION >= 0x040000 - QwtPainter::drawLine( painter, - r.left() + 1, r.top() + 2, r.right() + 1, r.top() + 2 ); -#else - QwtPainter::drawLine( painter, - r.left(), r.top() + 2, r.right() + 1, r.top() + 2 ); -#endif - - painter->setPen( QPen( dark, 2 ) ); -#if QT_VERSION >= 0x040000 - QwtPainter::drawLine( painter, - r.left() + 1, r.bottom(), r.right() + 1, r.bottom() ); -#else - QwtPainter::drawLine( painter, - r.left(), r.bottom(), r.right() + 1, r.bottom() ); -#endif - - painter->setPen( QPen( light, 1 ) ); - -#if QT_VERSION >= 0x040000 - QwtPainter::drawLine( painter, - r.left(), r.top() + 1, r.left(), r.bottom() ); - QwtPainter::drawLine( painter, - r.left() + 1, r.top() + 2, r.left() + 1, r.bottom() - 1 ); -#else - QwtPainter::drawLine( painter, - r.left(), r.top() + 1, r.left(), r.bottom() + 1 ); - QwtPainter::drawLine( painter, - r.left() + 1, r.top() + 2, r.left() + 1, r.bottom() ); -#endif - - painter->setPen( QPen( dark, 1 ) ); - -#if QT_VERSION >= 0x040000 - QwtPainter::drawLine( painter, - r.right() + 1, r.top() + 1, r.right() + 1, r.bottom() ); - QwtPainter::drawLine( painter, - r.right(), r.top() + 2, r.right(), r.bottom() - 1 ); -#else - QwtPainter::drawLine( painter, - r.right() + 1, r.top() + 1, r.right() + 1, r.bottom() + 1 ); - QwtPainter::drawLine( painter, - r.right(), r.top() + 2, r.right(), r.bottom() ); -#endif - - painter->restore(); -} - -//! Update the widget that represents the curve on the legend -// this was adapted from QwtPlotCurve::updateLegend() -void HistogramItem::updateLegend( QwtLegend *legend ) const -{ - if ( !legend ) - return; - - QwtPlotItem::updateLegend( legend ); - - QWidget *widget = legend->find( this ); - if ( !widget || !widget->inherits( "QwtLegendItem" ) ) - return; - - QwtLegendItem *legendItem = ( QwtLegendItem * )widget; - -#if QT_VERSION < 0x040000 - const bool doUpdate = legendItem->isUpdatesEnabled(); -#else - const bool doUpdate = legendItem->updatesEnabled(); -#endif - legendItem->setUpdatesEnabled( false ); - - const int policy = legend->displayPolicy(); - - if ( policy == QwtLegend::FixedIdentifier ) - { - int mode = legend->identifierMode(); - - legendItem->setCurvePen( QPen( color() ) ); - - if ( mode & QwtLegendItem::ShowText ) - legendItem->setText( title() ); - else - legendItem->setText( QwtText() ); - - legendItem->setIdentifierMode( mode ); - } - else if ( policy == QwtLegend::AutoIdentifier ) - { - int mode = 0; - - legendItem->setCurvePen( QPen( color() ) ); - mode |= QwtLegendItem::ShowLine; - if ( !title().isEmpty() ) - { - legendItem->setText( title() ); - mode |= QwtLegendItem::ShowText; - } - else - { - legendItem->setText( QwtText() ); - } - legendItem->setIdentifierMode( mode ); - } - - legendItem->setUpdatesEnabled( doUpdate ); - legendItem->update(); -} - #endif diff --git a/src/gui/symbology-ng/qgsgraduatedhistogramwidget.cpp b/src/gui/symbology-ng/qgsgraduatedhistogramwidget.cpp new file mode 100644 index 00000000000..21b698643d4 --- /dev/null +++ b/src/gui/symbology-ng/qgsgraduatedhistogramwidget.cpp @@ -0,0 +1,203 @@ +/*************************************************************************** + qgsgraduatedhistogramwidget.cpp + ------------------------------- + begin : May 2015 + copyright : (C) 2015 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsgraduatedhistogramwidget.h" +#include "qgsgraduatedsymbolrendererv2.h" +#include "qgsgraduatedsymbolrendererv2widget.h" +#include "qgsapplication.h" +#include "qgsvectorlayer.h" +#include "qgsstatisticalsummary.h" + +#include +#include +#include + +// QWT Charting widget +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 +#include +#include +#else +#include "../raster/qwt5_histogram_item.h" +#endif + + +QgsGraduatedHistogramWidget::QgsGraduatedHistogramWidget( QWidget *parent ) + : QgsHistogramWidget( parent ) + , mRenderer( 0 ) + , mHistoPicker( 0 ) + , mPressedValue( 0 ) +{ + + mFilter = new QgsGraduatedHistogramEventFilter( mPlot ); + + connect( mFilter, SIGNAL( mousePress( double ) ), this, SLOT( mousePress( double ) ) ); + connect( mFilter, SIGNAL( mouseRelease( double ) ), this, SLOT( mouseRelease( double ) ) ); + + mHistoPicker = new QwtPlotPicker( mPlot->canvas() ); + mHistoPicker->setTrackerMode( QwtPicker::ActiveOnly ); + mHistoPicker->setRubberBand( QwtPicker::VLineRubberBand ); +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 + mHistoPicker->setStateMachine( new QwtPickerDragPointMachine ); +#else + mHistoPicker->setSelectionFlags( QwtPicker::PointSelection | QwtPicker::DragSelection ); +#endif +} + + +QgsGraduatedHistogramWidget::~QgsGraduatedHistogramWidget() +{ +} + +void QgsGraduatedHistogramWidget::setRenderer( QgsGraduatedSymbolRendererV2 *renderer ) +{ + mRenderer = renderer; + mRedrawRequired = true; +} + +void QgsGraduatedHistogramWidget::drawHistogram() +{ + if ( !mRenderer ) + return; + + mRanges = mRenderer->ranges(); + QgsHistogramWidget::drawHistogram(); + + // histo picker + mHistoPicker->setEnabled( true ); +} + +void QgsGraduatedHistogramWidget::mousePress( double value ) +{ + mPressedValue = value; + + int closestRangeIndex = 0; + int minPixelDistance = 9999; + findClosestRange( mPressedValue, closestRangeIndex, minPixelDistance ); + + if ( minPixelDistance <= 6 ) + { + //moving a break, so hide the break line + mRangeMarkers.at( closestRangeIndex )->hide(); + mRedrawRequired = true; + mPlot->replot(); + } +} + +void QgsGraduatedHistogramWidget::mouseRelease( double value ) +{ + int closestRangeIndex = 0; + int minPixelDistance = 9999; + findClosestRange( mPressedValue, closestRangeIndex, minPixelDistance ); + + if ( minPixelDistance <= 6 ) + { + //do a sanity check - don't allow users to drag a break line + //into the middle of a different range. Doing so causes overlap + //of the ranges + + //new value needs to be within range covered by closestRangeIndex or + //closestRangeIndex + 1 + if ( value <= mRenderer->ranges().at( closestRangeIndex ).lowerValue() || + value >= mRenderer->ranges().at( closestRangeIndex + 1 ).upperValue() ) + { + refreshHistogram(); + return; + } + + mRenderer->updateRangeUpperValue( closestRangeIndex, value ); + mRenderer->updateRangeLowerValue( closestRangeIndex + 1, value ); + emit rangesModified( false ); + } + else + { + //if distance from markers is too big, add a break + mRenderer->addBreak( value ); + emit rangesModified( true ); + } + + drawHistogram(); +} + +void QgsGraduatedHistogramWidget::findClosestRange( double value, int &closestRangeIndex, int& pixelDistance ) const +{ + const QgsRangeList& ranges = mRenderer->ranges(); + + double minDistance = DBL_MAX; + int pressedPixel = mPlot->canvasMap( QwtPlot::xBottom ).transform( value ); + for ( int i = 0; i < ranges.count() - 1; ++i ) + { + if ( qAbs( mPressedValue - ranges.at( i ).upperValue() ) < minDistance ) + { + closestRangeIndex = i; + minDistance = qAbs( mPressedValue - ranges.at( i ).upperValue() ); + pixelDistance = qAbs( pressedPixel - mPlot->canvasMap( QwtPlot::xBottom ).transform( ranges.at( i ).upperValue() ) ); + } + } +} + +QgsGraduatedHistogramEventFilter::QgsGraduatedHistogramEventFilter( QwtPlot *plot ) + : QObject( plot ) + , mPlot( plot ) +{ + mPlot->canvas()->installEventFilter( this ); +} + +bool QgsGraduatedHistogramEventFilter::eventFilter( QObject *object, QEvent *event ) +{ + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + const QMouseEvent* mouseEvent = dynamic_cast( event ); + if ( mouseEvent->button() == Qt::LeftButton ) + { + emit mousePress( posToValue( mouseEvent->pos() ) ); + } + break; + } + case QEvent::MouseButtonRelease: + { + const QMouseEvent* mouseEvent = dynamic_cast( event ); + if ( mouseEvent->button() == Qt::LeftButton ) + { + emit mouseRelease( posToValue( mouseEvent->pos() ) ); + } + break; + } + default: + break; + } + + return QObject::eventFilter( object, event ); +} + +double QgsGraduatedHistogramEventFilter::posToValue( const QPointF &point ) const +{ + if ( !mPlot ) + return -99999999; + + return mPlot->canvasMap( QwtPlot::xBottom ).invTransform( point.x() ); +} diff --git a/src/gui/symbology-ng/qgsgraduatedhistogramwidget.h b/src/gui/symbology-ng/qgsgraduatedhistogramwidget.h new file mode 100644 index 00000000000..90eddf7e130 --- /dev/null +++ b/src/gui/symbology-ng/qgsgraduatedhistogramwidget.h @@ -0,0 +1,114 @@ +/*************************************************************************** + qgsgraduatedhistogramwidget.h + ----------------------------- + begin : May 2015 + copyright : (C) 2015 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSGRADUATEDHISTOGRAMWIDGET_H +#define QGSGRADUATEDHISTOGRAMWIDGET_H + +#include "qgshistogramwidget.h" + +class QwtPlotPicker; +class QgsGraduatedHistogramEventFilter; + +/** \ingroup gui + * \class QgsGraduatedHistogramWidget + * \brief Graphical histogram for displaying distribution of field values and + * editing range breaks for a QgsGraduatedSymbolRendererV2 renderer. + * + * \note Added in version 2.9 + */ + +class GUI_EXPORT QgsGraduatedHistogramWidget : public QgsHistogramWidget +{ + Q_OBJECT + + public: + + /** QgsGraduatedHistogramWidget constructor + * @param parent parent widget + */ + QgsGraduatedHistogramWidget( QWidget *parent = 0 ); + ~QgsGraduatedHistogramWidget(); + + /** Sets the QgsGraduatedSymbolRendererV2 renderer associated with the histogram. + * The histogram will fetch the ranges from the renderer before every refresh. + * @param renderer associated QgsGraduatedSymbolRendererV2 + */ + void setRenderer( QgsGraduatedSymbolRendererV2* renderer ); + + signals: + + /** Emitted when the user modifies the graduated ranges using the histogram widget. + * @param rangesAdded true if the user has added ranges, false if the user has just + * modified existing range breaks + */ + void rangesModified( bool rangesAdded ); + + protected: + + virtual void drawHistogram() override; + + private slots: + + void mousePress( double value ); + void mouseRelease( double value ); + + private: + + QgsGraduatedSymbolRendererV2* mRenderer; + QwtPlotPicker* mHistoPicker; + QgsGraduatedHistogramEventFilter* mFilter; + double mPressedValue; + + void findClosestRange( double value, int &closestRangeIndex, int &pixelDistance ) const; + +#if defined(QWT_VERSION) && QWT_VERSION>=0x060000 + QwtPlotHistogram* createPlotHistogram( const QString& title, const QColor& color ) const; +#else + HistogramItem* createHistoItem( const QString& title, const QColor& color ) const; +#endif + +}; + +// +// NOTE: +// For private use by QgsGraduatedHistogramWidget only, +// not part of stable api or exposed to Python bindings +// + +class GUI_EXPORT QgsGraduatedHistogramEventFilter: public QObject +{ + Q_OBJECT + + public: + + QgsGraduatedHistogramEventFilter( QwtPlot *plot ); + + virtual ~QgsGraduatedHistogramEventFilter() {} + + virtual bool eventFilter( QObject* object, QEvent* event ) override; + + signals: + + void mousePress( double ); + void mouseRelease( double ); + + private: + + QwtPlot* mPlot; + double posToValue( const QPointF& point ) const; +}; + +#endif //QGSGRADUATEDHISTOGRAMWIDGET_H diff --git a/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.cpp b/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.cpp index 85c15d2335d..84433c17660 100644 --- a/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.cpp +++ b/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.cpp @@ -335,9 +335,16 @@ void QgsGraduatedSymbolRendererV2Model::sort( int column, Qt::SortOrder order ) QgsDebugMsg( "Done" ); } -void QgsGraduatedSymbolRendererV2Model::updateSymbology() +void QgsGraduatedSymbolRendererV2Model::updateSymbology( bool resetModel ) { - emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) ); + if ( resetModel ) + { + reset(); + } + else + { + emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) ); + } } void QgsGraduatedSymbolRendererV2Model::updateLabels() @@ -465,6 +472,11 @@ QgsGraduatedSymbolRendererV2Widget::QgsGraduatedSymbolRendererV2Widget( QgsVecto connect( mDataDefinedMenus, SIGNAL( sizeScaleFieldChanged( QString ) ), this, SLOT( sizeScaleFieldChanged( QString ) ) ); connect( mDataDefinedMenus, SIGNAL( scaleMethodChanged( QgsSymbolV2::ScaleMethod ) ), this, SLOT( scaleMethodChanged( QgsSymbolV2::ScaleMethod ) ) ); btnAdvanced->setMenu( advMenu ); + + mHistogramWidget->setLayer( mLayer ); + mHistogramWidget->setRenderer( mRenderer ); + connect( mHistogramWidget, SIGNAL( rangesModified( bool ) ), this, SLOT( refreshRanges( bool ) ) ); + connect( mExpressionWidget, SIGNAL( fieldChanged( QString ) ), mHistogramWidget, SLOT( setSourceFieldExp( QString ) ) ); } void QgsGraduatedSymbolRendererV2Widget::on_mSizeUnitWidget_changed() @@ -543,6 +555,7 @@ void QgsGraduatedSymbolRendererV2Widget::updateUiFromRenderer( bool updateCount // set column QString attrName = mRenderer->classAttribute(); mExpressionWidget->setField( attrName ); + mHistogramWidget->setSourceFieldExp( attrName ); // set source symbol if ( mRenderer->sourceSymbol() ) @@ -593,6 +606,8 @@ void QgsGraduatedSymbolRendererV2Widget::updateUiFromRenderer( bool updateCount viewGraduated->resizeColumnToContents( 1 ); viewGraduated->resizeColumnToContents( 2 ); + mHistogramWidget->refreshHistogram(); + connectUpdateHandlers(); } @@ -627,6 +642,14 @@ void QgsGraduatedSymbolRendererV2Widget::on_methodComboBox_currentIndexChanged( } } +void QgsGraduatedSymbolRendererV2Widget::refreshRanges( bool reset ) +{ + if ( !mModel ) + return; + + mModel->updateSymbology( reset ); +} + void QgsGraduatedSymbolRendererV2Widget::classifyGraduated() { QString attrName = mExpressionWidget->currentField(); @@ -1052,6 +1075,7 @@ void QgsGraduatedSymbolRendererV2Widget::rowsMoved() void QgsGraduatedSymbolRendererV2Widget::modelDataChanged() { + mHistogramWidget->refreshHistogram(); } void QgsGraduatedSymbolRendererV2Widget::keyPressEvent( QKeyEvent* event ) diff --git a/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.h b/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.h index 5072ac2785f..f4b515fd7b6 100644 --- a/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.h +++ b/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.h @@ -49,7 +49,7 @@ class GUI_EXPORT QgsGraduatedSymbolRendererV2Model : public QAbstractItemModel void deleteRows( QList rows ); void removeAllRows(); void sort( int column, Qt::SortOrder order = Qt::AscendingOrder ) override; - void updateSymbology(); + void updateSymbology( bool resetModel = false ); void updateLabels(); signals: @@ -111,6 +111,7 @@ class GUI_EXPORT QgsGraduatedSymbolRendererV2Widget : public QgsRendererV2Widget void modelDataChanged(); void on_mSizeUnitWidget_changed(); void on_methodComboBox_currentIndexChanged( int ); + void refreshRanges( bool reset = false ); protected: void updateUiFromRenderer( bool updateCount = true ); diff --git a/src/ui/qgsgraduatedsymbolrendererv2widget.ui b/src/ui/qgsgraduatedsymbolrendererv2widget.ui index fa606cb9b04..5ba8fbae91a 100644 --- a/src/ui/qgsgraduatedsymbolrendererv2widget.ui +++ b/src/ui/qgsgraduatedsymbolrendererv2widget.ui @@ -7,53 +7,213 @@ 0 0 759 - 638 + 394 - - + + 0 - + + 0 + + + 0 + + + 0 + + + + + 0 + + + + Classes + + + + + + + + Mode + + + cboGraduatedMode + + + + + + + + Equal Interval + + + + + Quantile (Equal Count) + + + + + Natural Breaks (Jenks) + + + + + Standard Deviation + + + + + Pretty Breaks + + + + + + + + Classes + + + spinGraduatedClasses + + + + + + + + 0 + 0 + + + + 1 + + + 999 + + + 5 + + + + + + + Classify + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::CustomContextMenu + + + true + + + QAbstractItemView::InternalMove + + + QAbstractItemView::ExtendedSelection + + + false + + + false + + + true + + + + + + + + + Add class + + + + + + + Delete + + + + + + + Delete all + + + + + + + Link class boundaries + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Histogram + + + + + + + + + + - - - - Classify - - - - - - - Add class - - - - - - - Delete - - - - - - - Delete all - - - - - - - Link class boundaries - - - true - - - @@ -76,11 +236,22 @@ - - + + 6 + + + + Template for the legend text associated with each classification. +Use "%1" for the lower bound of the classification, and "%2" for the upper bound. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + @@ -107,45 +278,65 @@ Negative rounds to powers of 10 - - - - Check to remove trailing zeroes after the decimal point from the upper and lower values in the legend. - - - Trim - - - - - - - - Equal Interval - - - - - Quantile (Equal Count) - - - - - Natural Breaks (Jenks) - - - - - Standard Deviation - - - - - Pretty Breaks - - + + + + Column + + + + + + + + 10 + 0 + + + + + 500 + 16777215 + + + + Qt::StrongFocus + + + + + + + + 1 + 0 + + + + Change... + + + + + + + Precision + + + spinPrecision + + + + + + + Symbol + + + btnChangeGraduatedSymbol + @@ -155,35 +346,6 @@ Negative rounds to powers of 10 - - - - Classes - - - spinGraduatedClasses - - - - - - - - 0 - 0 - - - - 1 - - - 999 - - - 5 - - - @@ -197,17 +359,20 @@ Negative rounds to powers of 10 - - - - Mode + + + + + + + Check to remove trailing zeroes after the decimal point from the upper and lower values in the legend. - - cboGraduatedMode + + Trim - + @@ -226,7 +391,16 @@ Negative rounds to powers of 10 - + + 0 + + + 0 + + + 0 + + 0 @@ -266,7 +440,16 @@ Negative rounds to powers of 10 - + + 0 + + + 0 + + + 0 + + 0 @@ -293,7 +476,7 @@ Negative rounds to powers of 10 1.000000000000000 - + false @@ -319,7 +502,7 @@ Negative rounds to powers of 10 10.000000000000000 - + false @@ -341,106 +524,8 @@ Negative rounds to powers of 10 - - - - Symbol - - - btnChangeGraduatedSymbol - - - - - - - Precision - - - spinPrecision - - - - - - - Column - - - - - - - - 10 - 0 - - - - - 500 - 16777215 - - - - Qt::StrongFocus - - - - - - - Template for the legend text associated with each classification. -Use "%1" for the lower bound of the classification, and "%2" for the upper bound. - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 1 - 0 - - - - Change... - - - - - - - - - - Qt::CustomContextMenu - - - true - - - QAbstractItemView::InternalMove - - - QAbstractItemView::ExtendedSelection - - - false - - - false - - - true - - - @@ -449,6 +534,12 @@ Use "%1" for the lower bound of the classification, and "%2" QDoubleSpinBox
qgsdoublespinbox.h
+ + QgsUnitSelectionWidget + QWidget +
qgsunitselectionwidget.h
+ 1 +
QgsFieldExpressionWidget QWidget @@ -461,32 +552,23 @@ Use "%1" for the lower bound of the classification, and "%2"
qgscolorrampcombobox.h
- QgsUnitSelectionWidget + QgsGraduatedHistogramWidget QWidget -
qgsunitselectionwidget.h
+
qgsgraduatedhistogramwidget.h
1
mExpressionWidget - cboGraduatedMode btnChangeGraduatedSymbol - spinGraduatedClasses txtLegendFormat spinPrecision - cbxTrimTrailingZeroes methodComboBox cboGraduatedColorRamp cbxInvertedColorRamp minSizeSpinBox maxSizeSpinBox mSizeUnitWidget - viewGraduated - btnGraduatedClassify - btnGraduatedAdd - btnGraduatedDelete - btnDeleteAllClasses - cbxLinkBoundaries btnAdvanced diff --git a/src/ui/qgshistogramwidgetbase.ui b/src/ui/qgshistogramwidgetbase.ui new file mode 100644 index 00000000000..ca5613513a2 --- /dev/null +++ b/src/ui/qgshistogramwidgetbase.ui @@ -0,0 +1,108 @@ + + + QgsHistogramWidgetBase + + + + 0 + 0 + 694 + 128 + + + + + 0 + 0 + + + + + 0 + 60 + + + + Form + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + + + + + + Histogram bins + + + + + + + 1 + + + 999 + + + 50 + + + + + + + Show mean value + + + + + + + Show standard deviation + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QwtPlot + QFrame +
qwt_plot.h
+ 1 +
+
+ + +