From 183286a006ec8e5184e47b890c8f0374ed7a4460 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 13 Apr 2015 19:39:09 +1000 Subject: [PATCH] [FEATURE] Add a graphical histogram for the graduated renderer This adds a new histogram tab to the graduated renderer, which shows an interactive histogram of the values from the assigned field or expression. Class breaks can be moved or added using the histogram widget. A base class, QgsHistogramWidget, has been created to display histograms for a field or expression. In future this could be used to show a histogram within a "selection statistics" panel. Sponsored by ADUGA (http://www.aduga.org) --- python/gui/gui.sip | 2 + python/gui/qgshistogramwidget.sip | 107 ++++ .../qgsgraduatedhistogramwidget.sip | 42 ++ src/core/qgshistogram.cpp | 2 +- .../qgsgraduatedsymbolrendererv2.cpp | 31 + .../qgsgraduatedsymbolrendererv2.h | 10 + src/gui/CMakeLists.txt | 5 + src/gui/qgshistogramwidget.cpp | 353 ++++++++++++ src/gui/qgshistogramwidget.h | 174 ++++++ src/gui/raster/qwt5_histogram_item.cpp | 397 +++++++++++++ src/gui/raster/qwt5_histogram_item.h | 340 +---------- .../qgsgraduatedhistogramwidget.cpp | 203 +++++++ .../qgsgraduatedhistogramwidget.h | 114 ++++ .../qgsgraduatedsymbolrendererv2widget.cpp | 28 +- .../qgsgraduatedsymbolrendererv2widget.h | 3 +- src/ui/qgsgraduatedsymbolrendererv2widget.ui | 542 ++++++++++-------- src/ui/qgshistogramwidgetbase.ui | 108 ++++ 17 files changed, 1902 insertions(+), 559 deletions(-) create mode 100644 python/gui/qgshistogramwidget.sip create mode 100644 python/gui/symbology-ng/qgsgraduatedhistogramwidget.sip create mode 100644 src/gui/qgshistogramwidget.cpp create mode 100644 src/gui/qgshistogramwidget.h create mode 100644 src/gui/raster/qwt5_histogram_item.cpp create mode 100644 src/gui/symbology-ng/qgsgraduatedhistogramwidget.cpp create mode 100644 src/gui/symbology-ng/qgsgraduatedhistogramwidget.h create mode 100644 src/ui/qgshistogramwidgetbase.ui 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 +
+
+ + +