From fe049d372c95237aebe97f7540db7cf9cc38cd82 Mon Sep 17 00:00:00 2001 From: pierreloicq Date: Tue, 12 Sep 2017 01:01:55 +0200 Subject: [PATCH] classify symmetric squashed (17 commits) to rebase easily on master; 13 may 2018 --- .../qgsgraduatedsymbolrenderer.sip.in | 72 ++- .../symbology/qgsgraduatedsymbolrenderer.sip | 535 ++++++++++++++++++ .../qgsgraduatedsymbolrendererwidget.sip | 100 ++++ .../symbology/qgsgraduatedsymbolrenderer.cpp | 187 ++++-- .../symbology/qgsgraduatedsymbolrenderer.h | 67 ++- src/core/symbology/qgssymbollayerutils.cpp | 18 + .../qgsgraduatedsymbolrendererwidget.cpp | 126 ++++- src/ui/qgsgraduatedsymbolrendererv2widget.ui | 86 ++- 8 files changed, 1134 insertions(+), 57 deletions(-) create mode 100644 python/core/symbology/qgsgraduatedsymbolrenderer.sip create mode 100644 python/gui/symbology/qgsgraduatedsymbolrendererwidget.sip diff --git a/python/core/auto_generated/symbology/qgsgraduatedsymbolrenderer.sip.in b/python/core/auto_generated/symbology/qgsgraduatedsymbolrenderer.sip.in index e96a880b94a2..27564869925e 100644 --- a/python/core/auto_generated/symbology/qgsgraduatedsymbolrenderer.sip.in +++ b/python/core/auto_generated/symbology/qgsgraduatedsymbolrenderer.sip.in @@ -225,7 +225,63 @@ Tests whether classes assigned to the renderer have gaps between the ranges. Mode mode() const; void setMode( Mode mode ); - void updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses ); + bool useSymmetricMode(); +%Docstring +get mUseSymmetricMode, which says if we want to classify symmetric around a given value + +.. versionadded:: 3.2 +%End + + void setUseSymmetricMode( bool useSymmetricMode ); +%Docstring +set mUseSymmetricMode, which says if we want to classify symmetric around a given value + +.. versionadded:: 3.2 +%End + + double symmetryPoint(); +%Docstring +get mSymmetryPoint, the pivot value for symmetric classification + +.. versionadded:: 3.2 +%End + + void setSymmetryPoint( double symmetryPoint ); +%Docstring +set the pivot point + +.. versionadded:: 3.2 +%End + + QStringList listForCboPrettyBreaks(); +%Docstring +get mListForCboPrettyBreaks, which is need to recover this list in saved configuration, or when property window in closed and reopened + +.. versionadded:: 3.2 +%End + + void setListForCboPrettyBreaks( QStringList listForCboPrettyBreaks ); +%Docstring +set mListForCboPrettyBreaks, which is need to recover this list in saved configuration, or when property window in closed and reopened + +.. versionadded:: 3.2 +%End + + bool astride(); +%Docstring +get mAstride, a bool saying if we want to have a central class astride the pivot value + +.. versionadded:: 3.2 +%End + + void setAstride( bool astride ); +%Docstring +set mAstride, a bool saying if we want to have a central class astride the pivot value + +.. versionadded:: 3.2 +%End + + void updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses, bool useSymmetricMode = false, double symmetryPoint = 0.0, bool astride = false ); %Docstring Recalculate classes for a layer @@ -234,6 +290,11 @@ Recalculate classes for a layer :param nclasses: The number of classes to calculate (approximate for some modes) .. versionadded:: 2.6 +:param useSymmetricMode: A bool indicating if we want to have classes and hence colors ramp symmetric around a value +:param symmetryPoint: The value around which the classes will be symmetric if useSymmetricMode is checked +:param astride: A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] ) + +.. versionadded:: 3.2 %End const QgsRendererRangeLabelFormat &labelFormat() const; @@ -266,6 +327,10 @@ Reset the label decimal places to a numberbased on the minimum class interval const QString &attrName, int classes, Mode mode, + bool useSymmetricMode, + double symmetryPoint, + QStringList listForCboPrettyBreaks, + bool astride, QgsSymbol *symbol /Transfer/, QgsColorRamp *ramp /Transfer/, const QgsRendererRangeLabelFormat &legendFormat = QgsRendererRangeLabelFormat() ); @@ -276,6 +341,10 @@ Creates a new graduated renderer. :param attrName: attribute to classify :param classes: number of classes :param mode: classification mode +:param useSymmetricMode: A bool indicating if we want to have classes and hence colors ramp symmetric around a value +:param symmetryPoint: The value around which the classes will be symmetric if useSymmetricMode is checked +:param listForCboPrettyBreaks: The list of potential pivot values for symmetric mode with prettybreaks mode +:param astride: A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] ) :param symbol: base symbol :param ramp: color ramp for classes :param legendFormat: @@ -440,6 +509,7 @@ Will return null if the functionality is disabled. + QgsSymbol *symbolForValue( double value ) const; %Docstring Gets the symbol which is used to represent ``value``. diff --git a/python/core/symbology/qgsgraduatedsymbolrenderer.sip b/python/core/symbology/qgsgraduatedsymbolrenderer.sip new file mode 100644 index 000000000000..8a6a03b403ed --- /dev/null +++ b/python/core/symbology/qgsgraduatedsymbolrenderer.sip @@ -0,0 +1,535 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/symbology/qgsgraduatedsymbolrenderer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsRendererRange +{ + +%TypeHeaderCode +#include "qgsgraduatedsymbolrenderer.h" +%End + public: + + QgsRendererRange(); +%Docstring +Constructor for QgsRendererRange. +%End + QgsRendererRange( double lowerValue, double upperValue, QgsSymbol *symbol /Transfer/, const QString &label, bool render = true ); + QgsRendererRange( const QgsRendererRange &range ); + + + bool operator<( const QgsRendererRange &other ) const; + + double lowerValue() const; + double upperValue() const; + + QgsSymbol *symbol() const; + QString label() const; + + void setSymbol( QgsSymbol *s /Transfer/ ); + void setLabel( const QString &label ); + void setLowerValue( double lowerValue ); + void setUpperValue( double upperValue ); + + bool renderState() const; + void setRenderState( bool render ); + + QString dump() const; + + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props, bool firstRange = false ) const; +%Docstring +Creates a DOM element representing the range in SLD format. + +:param doc: DOM document +:param element: destination DOM element +:param props: graduated renderer properties +:param firstRange: set to true if the range is the first range, where the lower value uses a <= test +rather than a < test. +%End + + protected: + + void swap( QgsRendererRange &other ); +}; + +typedef QList QgsRangeList; + + +class QgsRendererRangeLabelFormat +{ +%Docstring + +.. versionadded:: 2.6 +%End + +%TypeHeaderCode +#include "qgsgraduatedsymbolrenderer.h" +%End + public: + QgsRendererRangeLabelFormat(); + QgsRendererRangeLabelFormat( const QString &format, int precision = 4, bool trimTrailingZeroes = false ); + + bool operator==( const QgsRendererRangeLabelFormat &other ) const; + bool operator!=( const QgsRendererRangeLabelFormat &other ) const; + + QString format() const; + void setFormat( const QString &format ); + + int precision() const; + void setPrecision( int precision ); + + bool trimTrailingZeroes() const; + void setTrimTrailingZeroes( bool trimTrailingZeroes ); + + QString labelForRange( double lower, double upper ) const /PyName=labelForLowerUpper/; +%Docstring + +.. note:: + + labelForLowerUpper in Python bindings +%End + QString labelForRange( const QgsRendererRange &range ) const; + QString formatNumber( double value ) const; + + void setFromDomElement( QDomElement &element ); + void saveToDomElement( QDomElement &element ); + + static const int MAX_PRECISION; + static const int MIN_PRECISION; + + protected: +}; + + +class QgsGraduatedSymbolRenderer : QgsFeatureRenderer +{ + +%TypeHeaderCode +#include "qgsgraduatedsymbolrenderer.h" +%End + public: + + QgsGraduatedSymbolRenderer( const QString &attrName = QString(), const QgsRangeList &ranges = QgsRangeList() ); + + ~QgsGraduatedSymbolRenderer(); + + virtual QgsSymbol *symbolForFeature( QgsFeature &feature, QgsRenderContext &context ); + + virtual QgsSymbol *originalSymbolForFeature( QgsFeature &feature, QgsRenderContext &context ); + + virtual void startRender( QgsRenderContext &context, const QgsFields &fields ); + + virtual void stopRender( QgsRenderContext &context ); + + virtual QSet usedAttributes( const QgsRenderContext &context ) const; + + virtual QString dump() const; + + virtual QgsGraduatedSymbolRenderer *clone() const /Factory/; + + virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const; + + virtual QgsFeatureRenderer::Capabilities capabilities(); + virtual QgsSymbolList symbols( QgsRenderContext &context ); + + + QString classAttribute() const; + void setClassAttribute( const QString &attr ); + + const QgsRangeList &ranges() const; + + bool updateRangeSymbol( int rangeIndex, QgsSymbol *symbol /Transfer/ ); + bool updateRangeLabel( int rangeIndex, const QString &label ); + bool updateRangeUpperValue( int rangeIndex, double value ); + bool updateRangeLowerValue( int rangeIndex, double value ); + bool updateRangeRenderState( int rangeIndex, bool render ); +%Docstring + +.. versionadded:: 2.5 +%End + + void addClass( QgsSymbol *symbol ); + void addClass( const QgsRendererRange &range ) /PyName=addClassRange/; +%Docstring + +.. note:: + + available in Python bindings as addClassRange +%End + void addClass( double lower, double upper ) /PyName=addClassLowerUpper/; +%Docstring + +.. note:: + + available in Python bindings as addClassLowerUpper +%End + + void addBreak( double breakValue, bool updateSymbols = true ); +%Docstring +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 + +.. versionadded:: 2.9 +%End + + void deleteClass( int idx ); + void deleteAllClasses(); + + void moveClass( int from, int to ); +%Docstring +Moves the category at index position from to index position to. +%End + + bool rangesOverlap() const; +%Docstring +Tests whether classes assigned to the renderer have ranges which overlap. + +:return: true if ranges overlap + +.. versionadded:: 2.10 +%End + + bool rangesHaveGaps() const; +%Docstring +Tests whether classes assigned to the renderer have gaps between the ranges. + +:return: true if ranges have gaps + +.. versionadded:: 2.10 +%End + + void sortByValue( Qt::SortOrder order = Qt::AscendingOrder ); + void sortByLabel( Qt::SortOrder order = Qt::AscendingOrder ); + + enum Mode + { + EqualInterval, + Quantile, + Jenks, + StdDev, + Pretty, + Custom + }; + + Mode mode() const; + void setMode( Mode mode ); + + bool useSymmetricMode(); +%Docstring +get mUseSymmetricMode, which says if we want to classify symmetric around a given value + +.. versionadded:: 3.2 +%End + + void setUseSymmetricMode( bool useSymmetricMode ); +%Docstring +set mUseSymmetricMode, which says if we want to classify symmetric around a given value + +.. versionadded:: 3.2 +%End + + double symmetryPoint(); +%Docstring +get mSymmetryPoint, the pivot value for symmetric classification + +.. versionadded:: 3.2 +%End + + void setSymmetryPoint( double symmetryPoint ); +%Docstring +set the pivot point + +.. versionadded:: 3.2 +%End + + QStringList listForCboPrettyBreaks(); +%Docstring +get mListForCboPrettyBreaks, which is need to recover this list in saved configuration, or when property window in closed and reopened + +.. versionadded:: 3.2 +%End + + void setListForCboPrettyBreaks( QStringList listForCboPrettyBreaks ); +%Docstring +set mListForCboPrettyBreaks, which is need to recover this list in saved configuration, or when property window in closed and reopened + +.. versionadded:: 3.2 +%End + + bool astride(); +%Docstring +get mAstride, a bool saying if we want to have a central class astride the pivot value + +.. versionadded:: 3.2 +%End + + void setAstride( bool astride ); +%Docstring +set mAstride, a bool saying if we want to have a central class astride the pivot value + +.. versionadded:: 3.2 +%End + + void updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses, bool useSymmetricMode = false, double symmetryPoint = 0.0, bool astride = false ); +%Docstring +Recalculate classes for a layer + +:param vlayer: The layer being rendered (from which data values are calculated) +:param mode: The calculation mode +:param nclasses: The number of classes to calculate (approximate for some modes) + +.. versionadded:: 2.6 +:param useSymmetricMode: A bool indicating if we want to have classes and hence colors ramp symmetric around a value +:param symmetryPoint: The value around which the classes will be symmetric if useSymmetricMode is checked +:param astride: A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] ) + +.. versionadded:: 3.2 +%End + + const QgsRendererRangeLabelFormat &labelFormat() const; +%Docstring +Return the label format used to generate default classification labels + +.. versionadded:: 2.6 +%End + + void setLabelFormat( const QgsRendererRangeLabelFormat &labelFormat, bool updateRanges = false ); +%Docstring +Set the label format used to generate default classification labels + +:param labelFormat: The string appended to classification labels +:param updateRanges: If true then ranges ending with the old unit string are updated to the new. + +.. versionadded:: 2.6 +%End + + void calculateLabelPrecision( bool updateRanges = true ); +%Docstring +Reset the label decimal places to a numberbased on the minimum class interval + +:param updateRanges: if true then ranges currently using the default label will be updated + +.. versionadded:: 2.6 +%End + + static QgsGraduatedSymbolRenderer *createRenderer( QgsVectorLayer *vlayer, + const QString &attrName, + int classes, + Mode mode, + bool useSymmetricMode, + double symmetryPoint, + QStringList listForCboPrettyBreaks, + bool astride, + QgsSymbol *symbol /Transfer/, + QgsColorRamp *ramp /Transfer/, + const QgsRendererRangeLabelFormat &legendFormat = QgsRendererRangeLabelFormat() ); +%Docstring +Creates a new graduated renderer. + +:param vlayer: vector layer +:param attrName: attribute to classify +:param classes: number of classes +:param mode: classification mode +:param useSymmetricMode: A bool indicating if we want to have classes and hence colors ramp symmetric around a value +:param symmetryPoint: The value around which the classes will be symmetric if useSymmetricMode is checked +:param listForCboPrettyBreaks: The list of potential pivot values for symmetric mode with prettybreaks mode +:param astride: A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] ) +:param symbol: base symbol +:param ramp: color ramp for classes +:param legendFormat: + +:return: new QgsGraduatedSymbolRenderer object +%End + + static QgsFeatureRenderer *create( QDomElement &element, const QgsReadWriteContext &context ) /Factory/; +%Docstring +create renderer from XML element +%End + + virtual QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ); + + virtual QgsLegendSymbolList legendSymbolItems() const; + + virtual QSet< QString > legendKeysForFeature( QgsFeature &feature, QgsRenderContext &context ); + + + QgsSymbol *sourceSymbol(); +%Docstring +Returns the renderer's source symbol, which is the base symbol used for the each classes' symbol before applying +the classes' color. + +.. seealso:: :py:func:`setSourceSymbol` + +.. seealso:: :py:func:`sourceColorRamp` +%End + + void setSourceSymbol( QgsSymbol *sym /Transfer/ ); +%Docstring +Sets the source symbol for the renderer, which is the base symbol used for the each classes' symbol before applying +the classes' color. + +:param sym: source symbol, ownership is transferred to the renderer + +.. seealso:: :py:func:`sourceSymbol` + +.. seealso:: :py:func:`setSourceColorRamp` +%End + + QgsColorRamp *sourceColorRamp(); +%Docstring +Returns the source color ramp, from which each classes' color is derived. + +.. seealso:: :py:func:`setSourceColorRamp` + +.. seealso:: :py:func:`sourceSymbol` +%End + + void setSourceColorRamp( QgsColorRamp *ramp /Transfer/ ); +%Docstring +Sets the source color ramp. + +:param ramp: color ramp. Ownership is transferred to the renderer +%End + + void updateColorRamp( QgsColorRamp *ramp /Transfer/ = 0 ); +%Docstring +Update the color ramp used. Also updates all symbols colors. +Doesn't alter current breaks. + +:param ramp: color ramp. Ownership is transferred to the renderer +%End + + void updateSymbols( QgsSymbol *sym ); +%Docstring +Update all the symbols but leave breaks and colors. This method also sets the source +symbol for the renderer. + +:param sym: source symbol to use for classes. Ownership is not transferred. + +.. seealso:: :py:func:`setSourceSymbol` +%End + + void setSymbolSizes( double minSize, double maxSize ); +%Docstring +set varying symbol size for classes + +.. note:: + + the classes must already be set so that symbols exist + +.. versionadded:: 2.10 +%End + + double minSymbolSize() const; +%Docstring +return the min symbol size when graduated by size + +.. versionadded:: 2.10 +%End + + double maxSymbolSize() const; +%Docstring +return the max symbol size when graduated by size + +.. versionadded:: 2.10 +%End + + enum GraduatedMethod + { + GraduatedColor, + GraduatedSize + }; + + GraduatedMethod graduatedMethod() const; +%Docstring +return the method used for graduation (either size or color) + +.. versionadded:: 2.10 +%End + + void setGraduatedMethod( GraduatedMethod method ); +%Docstring +set the method used for graduation (either size or color) + +.. versionadded:: 2.10 +%End + + virtual bool legendSymbolItemsCheckable() const; + + virtual bool legendSymbolItemChecked( const QString &key ); + + virtual void checkLegendSymbolItem( const QString &key, bool state = true ); + + virtual void setLegendSymbolItem( const QString &key, QgsSymbol *symbol /Transfer/ ); + + virtual QString legendClassificationAttribute() const; + + static QgsGraduatedSymbolRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer ) /Factory/; +%Docstring +creates a QgsGraduatedSymbolRenderer from an existing renderer. + +:return: a new renderer if the conversion was possible, otherwise 0. + +.. versionadded:: 2.6 +%End + + void setDataDefinedSizeLegend( QgsDataDefinedSizeLegend *settings /Transfer/ ); +%Docstring +Configures appearance of legend when renderer is configured to use data-defined size for marker symbols. +This allows configuring for which values (symbol sizes) should be shown in the legend, whether to display +different symbol sizes collapsed in one legend node or separated across multiple legend nodes etc. + +When renderer does not use data-defined size or does not use marker symbols, these settings will be ignored. +Takes ownership of the passed settings objects. Null pointer is a valid input that disables data-defined +size legend. + +.. versionadded:: 3.0 +%End + + QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const; +%Docstring +Returns configuration of appearance of legend when using data-defined size for marker symbols. +Will return null if the functionality is disabled. + +.. versionadded:: 3.0 +%End + + protected: + + + + + QgsSymbol *symbolForValue( double value ); +%Docstring +attribute index (derived from attribute name in startRender) +%End + + QString legendKeyForValue( double value ) const; +%Docstring +Returns the matching legend key for a value. +%End + + + private: + QgsGraduatedSymbolRenderer( const QgsGraduatedSymbolRenderer & ); + QgsGraduatedSymbolRenderer &operator=( const QgsGraduatedSymbolRenderer & ); +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/symbology/qgsgraduatedsymbolrenderer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/symbology/qgsgraduatedsymbolrendererwidget.sip b/python/gui/symbology/qgsgraduatedsymbolrendererwidget.sip new file mode 100644 index 000000000000..a994c8037e64 --- /dev/null +++ b/python/gui/symbology/qgsgraduatedsymbolrendererwidget.sip @@ -0,0 +1,100 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/symbology/qgsgraduatedsymbolrendererwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsGraduatedSymbolRendererWidget : QgsRendererWidget +{ + +%TypeHeaderCode +#include "qgsgraduatedsymbolrendererwidget.h" +%End + public: + static QgsRendererWidget *create( QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer ) /Factory/; + + QgsGraduatedSymbolRendererWidget( QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer ); + ~QgsGraduatedSymbolRendererWidget(); + + virtual QgsFeatureRenderer *renderer(); + + + public slots: + void changeGraduatedSymbol(); + void graduatedColumnChanged( const QString &field ); + void classifyGraduated(); + void reapplyColorRamp(); + void reapplySizes(); + void rangesDoubleClicked( const QModelIndex &idx ); + void rangesClicked( const QModelIndex &idx ); + void changeCurrentValue( QStandardItem *item ); + + void addClass(); +%Docstring +Adds a class manually to the classification +%End + void deleteClasses(); +%Docstring +Removes currently selected classes +%End + void deleteAllClasses(); +%Docstring +Removes all classes from the classification +%End + void toggleBoundariesLink( bool linked ); +%Docstring +Toggle the link between classes boundaries +%End + + void labelFormatChanged(); + + void showSymbolLevels(); + + void rowsMoved(); + void modelDataChanged(); + void refreshRanges( bool reset = false ); + + protected: + void updateUiFromRenderer( bool updateCount = true ); + void connectUpdateHandlers(); + void disconnectUpdateHandlers(); + bool rowsOrdered(); + + void updateGraduatedSymbolIcon(); + + QList selectedClasses(); +%Docstring +return a list of indexes for the classes under selection +%End + QgsRangeList selectedRanges(); + + void changeRangeSymbol( int rangeIdx ); + void changeRange( int rangeIdx ); + + void changeSelectedSymbols(); + + virtual QList selectedSymbols(); + + QgsSymbol *findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const; + virtual void refreshSymbolView(); + + + virtual void keyPressEvent( QKeyEvent *event ); + + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/symbology/qgsgraduatedsymbolrendererwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/core/symbology/qgsgraduatedsymbolrenderer.cpp b/src/core/symbology/qgsgraduatedsymbolrenderer.cpp index 5108044a6d8e..717851006e4b 100644 --- a/src/core/symbology/qgsgraduatedsymbolrenderer.cpp +++ b/src/core/symbology/qgsgraduatedsymbolrenderer.cpp @@ -288,7 +288,8 @@ QgsGraduatedSymbolRenderer::QgsGraduatedSymbolRenderer( const QString &attrName, { mRanges << range; } - + mUseSymmetricMode = false; + mAstride = false; } QgsGraduatedSymbolRenderer::~QgsGraduatedSymbolRenderer() @@ -484,6 +485,11 @@ QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::clone() const { QgsGraduatedSymbolRenderer *r = new QgsGraduatedSymbolRenderer( mAttrName, mRanges ); r->setMode( mMode ); + r->setUseSymmetricMode( mUseSymmetricMode ); + r->setSymmetryPoint( mSymmetryPoint ); + r->setListForCboPrettyBreaks( mListForCboPrettyBreaks ); + r->setAstride( mAstride ); + if ( mSourceSymbol ) r->setSourceSymbol( mSourceSymbol->clone() ); if ( mSourceColorRamp ) @@ -525,29 +531,83 @@ QgsSymbolList QgsGraduatedSymbolRenderer::symbols( QgsRenderContext &context ) c return lst; } -static QList _calcEqualIntervalBreaks( double minimum, double maximum, int classes ) +static void _makeBreaksSymmetric( QList &breaks, double symmetryPoint, bool astride ) { + // remove the breaks that are above the existing opposite sign classes + // to keep colors symmetrically balanced around symmetryPoint + // if astride is true, remove the symmetryPoint break so that + // the 2 classes form only one - // Equal interval algorithm - // - // Returns breaks based on dividing the range ('minimum' to 'maximum') - // into 'classes' parts. - - double step = ( maximum - minimum ) / classes; + if ( breaks.size() > 1 ) //to avoid crash when only 1 class + { + std::sort( breaks.begin(), breaks.end() ); + // breaks contain the maximum of the distrib but not the minimum + double distBelowSymmetricValue = std::abs( breaks[0] - symmetryPoint ); + double distAboveSymmetricValue = std::abs( breaks[ breaks.size() - 2 ] - symmetryPoint ) ; + double absMin = std::min( std::abs( distAboveSymmetricValue ), std::abs( distBelowSymmetricValue ) ); + // make symmetric + for ( int i = 0; i <= breaks.size() - 2; ++i ) + { + if ( std::abs( breaks.at( i ) - symmetryPoint ) > absMin ) + { + breaks.removeAt( i ); + --i; + } + } + // remove symmetry point + if ( true == astride ) // && breaks.indexOf( symmetryPoint ) != -1) // if symmetryPoint is found + { + breaks.removeAt( breaks.indexOf( symmetryPoint ) ); + } + } +} +static QList _calcEqualIntervalBreaks( double minimum, double maximum, int classes, bool useSymmetricMode, double symmetryPoint, bool astride ) +{ + // Equal interval algorithm + // Returns breaks based on dividing the range ('minimum' to 'maximum') into 'classes' parts. QList breaks; - double value = minimum; - breaks.reserve( classes ); - for ( int i = 0; i < classes; i++ ) + if ( false == useSymmetricMode ) // nomal mode { - value += step; - breaks.append( value ); + double step = ( maximum - minimum ) / classes; + + double value = minimum; + breaks.reserve( classes ); + for ( int i = 0; i < classes; i++ ) + { + value += step; + breaks.append( value ); + } + // floating point arithmetics is not precise: + // set the last break to be exactly maximum so we do not miss it + breaks[classes - 1] = maximum; } + else if ( true == useSymmetricMode ) // symmetric mode + { + double distBelowSymmetricValue = std::abs( minimum - symmetryPoint ); + double distAboveSymmetricValue = std::abs( maximum - symmetryPoint ) ; - // floating point arithmetics is not precise: - // set the last break to be exactly maximum so we do not miss it - breaks[classes - 1] = maximum; + if ( true == astride ) + { + ( 0 == classes % 2 ) ? ++classes : classes; // we want odd number of classes + } + else + { + ( 1 == classes % 2 ) ? ++classes : classes; // we want even number of classes + } + double step = 2 * std::min( distBelowSymmetricValue, distAboveSymmetricValue ) / classes; + + double value; + breaks.reserve( classes ); + value = ( distBelowSymmetricValue < distAboveSymmetricValue ) ? minimum : maximum - classes * step; + for ( int i = 0; i < classes; i++ ) + { + value += step; + breaks.append( value ); + } + breaks[classes - 1] = maximum; + } return breaks; } @@ -593,7 +653,7 @@ static QList _calcQuantileBreaks( QList values, int classes ) return breaks; } -static QList _calcStdDevBreaks( QList values, int classes, QList &labels ) +static QList _calcStdDevBreaks( QList values, int classes, QList &labels, bool useSymmetricMode, double symmetryPoint, bool astride ) { // C++ implementation of the standard deviation class interval algorithm @@ -629,13 +689,17 @@ static QList _calcStdDevBreaks( QList values, int classes, QList } stdDev = std::sqrt( stdDev / n ); - QList breaks = QgsSymbolLayerUtils::prettyBreaks( ( minimum - mean ) / stdDev, ( maximum - mean ) / stdDev, classes ); - for ( int i = 0; i < breaks.count(); i++ ) + if ( false == useSymmetricMode ) + symmetryPoint = mean; // otherwise symmetryPoint = symmetryPoint + + QList breaks = QgsSymbolLayerUtils::prettyBreaks( ( minimum - symmetryPoint ) / stdDev, ( maximum - symmetryPoint ) / stdDev, classes ); + _makeBreaksSymmetric( breaks, 0.0, astride ); //0.0 because breaks where computed on a centered distribution + + for ( int i = 0; i < breaks.count(); i++ ) //unNormalize breaks and put labels { labels.append( breaks[i] ); - breaks[i] = ( breaks[i] * stdDev ) + mean; + breaks[i] = ( breaks[i] * stdDev ) + symmetryPoint; } - return breaks; } // _calcStdDevBreaks @@ -769,12 +833,25 @@ static QList _calcJenksBreaks( QList values, int classes, return breaks.toList(); } //_calcJenksBreaks +static QStringList _breaksAsStrings( QList breaks ) // get QStringList from QList without maxi break (min is not in) +{ + QStringList breaksAsTrings; + for ( int i = 0; i < breaks.count() - 1; i++ ) + { + breaksAsTrings << QString::number( breaks.at( i ), 'f', 2 ); + } + return breaksAsTrings; +} QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::createRenderer( QgsVectorLayer *vlayer, const QString &attrName, int classes, Mode mode, + bool useSymmetricMode, + double symmetryPoint, + QStringList listForCboPrettyBreaks, + bool astride, QgsSymbol *symbol, QgsColorRamp *ramp, const QgsRendererRangeLabelFormat &labelFormat @@ -785,17 +862,25 @@ QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::createRenderer( r->setSourceSymbol( symbol->clone() ); r->setSourceColorRamp( ramp->clone() ); r->setMode( mode ); + r->setUseSymmetricMode( useSymmetricMode ); + r->setSymmetryPoint( symmetryPoint ); + r->setListForCboPrettyBreaks( listForCboPrettyBreaks ); + r->setAstride( astride ); r->setLabelFormat( labelFormat ); - r->updateClasses( vlayer, mode, classes ); + r->updateClasses( vlayer, mode, classes, useSymmetricMode, symmetryPoint, astride ); return r; } -void QgsGraduatedSymbolRenderer::updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses ) +void QgsGraduatedSymbolRenderer::updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses, + bool useSymmetricMode, double symmetryPoint, bool astride ) { if ( mAttrName.isEmpty() ) return; - setMode( mode ); + setSymmetryPoint( symmetryPoint ); + setUseSymmetricMode( useSymmetricMode ); + setAstride( astride ); + // Custom classes are not recalculated if ( mode == Custom ) return; @@ -831,13 +916,18 @@ void QgsGraduatedSymbolRenderer::updateClasses( QgsVectorLayer *vlayer, Mode mod QgsDebugMsg( QString( "min %1 // max %2" ).arg( minimum ).arg( maximum ) ); QList breaks; QList labels; + if ( mode == EqualInterval ) { - breaks = _calcEqualIntervalBreaks( minimum, maximum, nclasses ); + breaks = _calcEqualIntervalBreaks( minimum, maximum, nclasses, mUseSymmetricMode, symmetryPoint, astride ); } else if ( mode == Pretty ) { breaks = QgsSymbolLayerUtils::prettyBreaks( minimum, maximum, nclasses ); + setListForCboPrettyBreaks( _breaksAsStrings( breaks ) ); + + if ( true == useSymmetricMode ) + _makeBreaksSymmetric( breaks, symmetryPoint, astride ); } else if ( mode == Quantile || mode == Jenks || mode == StdDev ) { @@ -846,20 +936,13 @@ void QgsGraduatedSymbolRenderer::updateClasses( QgsVectorLayer *vlayer, Mode mod { values = QgsVectorLayerUtils::getDoubleValues( vlayer, mAttrName, ok ); } - // calculate the breaks if ( mode == Quantile ) - { breaks = _calcQuantileBreaks( values, nclasses ); - } else if ( mode == Jenks ) - { breaks = _calcJenksBreaks( values, nclasses, minimum, maximum ); - } else if ( mode == StdDev ) - { - breaks = _calcStdDevBreaks( values, nclasses, labels ); - } + breaks = _calcStdDevBreaks( values, nclasses, labels, mUseSymmetricMode, symmetryPoint, astride ); } else { @@ -871,7 +954,6 @@ void QgsGraduatedSymbolRenderer::updateClasses( QgsVectorLayer *vlayer, Mode mod deleteAllClasses(); // "breaks" list contains all values at class breaks plus maximum as last break - int i = 0; for ( QList::iterator it = breaks.begin(); it != breaks.end(); ++it, ++i ) { @@ -904,6 +986,7 @@ void QgsGraduatedSymbolRenderer::updateClasses( QgsVectorLayer *vlayer, Mode mod updateColorRamp( nullptr ); } + QgsFeatureRenderer *QgsGraduatedSymbolRenderer::create( QDomElement &element, const QgsReadWriteContext &context ) { QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) ); @@ -989,6 +1072,21 @@ QgsFeatureRenderer *QgsGraduatedSymbolRenderer::create( QDomElement &element, co r->setMode( Pretty ); } + // symmetric mode + QDomElement symmetricModeElem = element.firstChildElement( QStringLiteral( "symmetricMode" ) ); + if ( !symmetricModeElem.isNull() ) + { + QString symmetricEnabled = symmetricModeElem.attribute( QStringLiteral( "enabled" ) ); + symmetricEnabled == QLatin1String( "true" ) ? r->setUseSymmetricMode( true ) : r->setUseSymmetricMode( false ); + + QString symmetricPointString = symmetricModeElem.attribute( QStringLiteral( "symmetryPoint" ) ); + r->setSymmetryPoint( symmetricPointString.toDouble() ); + QString breaksForPretty = symmetricModeElem.attribute( QStringLiteral( "valueForCboPrettyBreaks" ) ); + r->setListForCboPrettyBreaks( breaksForPretty.split( "/" ) ); + + QString astrideEnabled = symmetricModeElem.attribute( QStringLiteral( "astride" ) ); + astrideEnabled == QLatin1String( "true" ) ? r->setAstride( true ) : r->setAstride( false ); + } QDomElement rotationElem = element.firstChildElement( QStringLiteral( "rotation" ) ); if ( !rotationElem.isNull() && !rotationElem.attribute( QStringLiteral( "field" ) ).isEmpty() ) { @@ -1001,7 +1099,6 @@ QgsFeatureRenderer *QgsGraduatedSymbolRenderer::create( QDomElement &element, co convertSymbolRotation( r->mSourceSymbol.get(), rotationElem.attribute( QStringLiteral( "field" ) ) ); } } - QDomElement sizeScaleElem = element.firstChildElement( QStringLiteral( "sizescale" ) ); if ( !sizeScaleElem.isNull() && !sizeScaleElem.attribute( QStringLiteral( "field" ) ).isEmpty() ) { @@ -1032,7 +1129,6 @@ QgsFeatureRenderer *QgsGraduatedSymbolRenderer::create( QDomElement &element, co { r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readXml( ddsLegendSizeElem, context ) ); } - // TODO: symbol levels return r; } @@ -1108,6 +1204,25 @@ QDomElement QgsGraduatedSymbolRenderer::save( QDomDocument &doc, const QgsReadWr rendererElem.appendChild( modeElem ); } + // symmetry + QDomElement symmetricModeElem = doc.createElement( QStringLiteral( "symmetricMode" ) ); + symmetricModeElem.setAttribute( QStringLiteral( "enabled" ), mUseSymmetricMode ? QStringLiteral( "true" ) : QStringLiteral( "false" ) ); + symmetricModeElem.setAttribute( QStringLiteral( "symmetryPoint" ), mSymmetryPoint ); + symmetricModeElem.setAttribute( QStringLiteral( "astride" ), mAstride ? QStringLiteral( "true" ) : QStringLiteral( "false" ) ); + if ( Pretty == mMode ) + { + QString breaks; + for ( int i = 0; i < mListForCboPrettyBreaks.size() - 1; i++ ) // -1 to write 1/2/3 instead of 1/2/3/ + { + breaks.append( mListForCboPrettyBreaks.at( i ) ); + breaks.append( QStringLiteral( "/" ) ); + } + breaks.append( mListForCboPrettyBreaks.at( mListForCboPrettyBreaks.size() - 1 ) ); + symmetricModeElem.setAttribute( QStringLiteral( "valueForCboPrettyBreaks" ), breaks ); + } + + rendererElem.appendChild( symmetricModeElem ); + QDomElement rotationElem = doc.createElement( QStringLiteral( "rotation" ) ); rendererElem.appendChild( rotationElem ); diff --git a/src/core/symbology/qgsgraduatedsymbolrenderer.h b/src/core/symbology/qgsgraduatedsymbolrenderer.h index 6f05a2ffdf05..de6e9ed21589 100644 --- a/src/core/symbology/qgsgraduatedsymbolrenderer.h +++ b/src/core/symbology/qgsgraduatedsymbolrenderer.h @@ -222,14 +222,66 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer Mode mode() const { return mMode; } void setMode( Mode mode ) { mMode = mode; } + /** + * get mUseSymmetricMode, which says if we want to classify symmetric around a given value + * \since QGIS 3.2 + */ + bool useSymmetricMode() { return mUseSymmetricMode; } + + /** + * set mUseSymmetricMode, which says if we want to classify symmetric around a given value + * \since QGIS 3.2 + */ + void setUseSymmetricMode( bool useSymmetricMode ) { mUseSymmetricMode = useSymmetricMode; } + + /** + * get mSymmetryPoint, the pivot value for symmetric classification + * \since QGIS 3.2 + */ + double symmetryPoint() { return mSymmetryPoint; } + + /** + * set the pivot point + * \since QGIS 3.2 + */ + void setSymmetryPoint( double symmetryPoint ) { mSymmetryPoint = symmetryPoint; } + + /** + * get mListForCboPrettyBreaks, which is need to recover this list in saved configuration, or when property window in closed and reopened + * \since QGIS 3.2 + */ + QStringList listForCboPrettyBreaks() { return mListForCboPrettyBreaks; } + + /** + * set mListForCboPrettyBreaks, which is need to recover this list in saved configuration, or when property window in closed and reopened + * \since QGIS 3.2 + */ + void setListForCboPrettyBreaks( QStringList listForCboPrettyBreaks ) { mListForCboPrettyBreaks = listForCboPrettyBreaks; } + + /** + * get mAstride, a bool saying if we want to have a central class astride the pivot value + * \since QGIS 3.2 + */ + bool astride() { return mAstride; } + + /** + * set mAstride, a bool saying if we want to have a central class astride the pivot value + * \since QGIS 3.2 + */ + void setAstride( bool astride ) { mAstride = astride; } + /** * Recalculate classes for a layer * \param vlayer The layer being rendered (from which data values are calculated) * \param mode The calculation mode * \param nclasses The number of classes to calculate (approximate for some modes) * \since QGIS 2.6 + * \param useSymmetricMode A bool indicating if we want to have classes and hence colors ramp symmetric around a value + * \param symmetryPoint The value around which the classes will be symmetric if useSymmetricMode is checked + * \param astride A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] ) + * \since QGIS 3.2 */ - void updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses ); + void updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses, bool useSymmetricMode = false, double symmetryPoint = 0.0, bool astride = false ); /** * Returns the label format used to generate default classification labels @@ -258,6 +310,10 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer * \param attrName attribute to classify * \param classes number of classes * \param mode classification mode + * \param useSymmetricMode A bool indicating if we want to have classes and hence colors ramp symmetric around a value + * \param symmetryPoint The value around which the classes will be symmetric if useSymmetricMode is checked + * \param listForCboPrettyBreaks The list of potential pivot values for symmetric mode with prettybreaks mode + * \param astride A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] ) * \param symbol base symbol * \param ramp color ramp for classes * \param legendFormat @@ -267,6 +323,10 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer const QString &attrName, int classes, Mode mode, + bool useSymmetricMode, + double symmetryPoint, + QStringList listForCboPrettyBreaks, + bool astride, QgsSymbol *symbol SIP_TRANSFER, QgsColorRamp *ramp SIP_TRANSFER, const QgsRendererRangeLabelFormat &legendFormat = QgsRendererRangeLabelFormat() ); @@ -396,6 +456,10 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer QString mAttrName; QgsRangeList mRanges; Mode mMode = Custom; + bool mUseSymmetricMode; + double mSymmetryPoint; + QStringList mListForCboPrettyBreaks; + bool mAstride; std::unique_ptr mSourceSymbol; std::unique_ptr mSourceColorRamp; QgsRendererRangeLabelFormat mLabelFormat; @@ -403,6 +467,7 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer std::unique_ptr mExpression; GraduatedMethod mGraduatedMethod = GraduatedColor; //! attribute index (derived from attribute name in startRender) + int mAttrNum = -1; bool mCounting = false; diff --git a/src/core/symbology/qgssymbollayerutils.cpp b/src/core/symbology/qgssymbollayerutils.cpp index 47bd5059f877..b7a0f2d89047 100644 --- a/src/core/symbology/qgssymbollayerutils.cpp +++ b/src/core/symbology/qgssymbollayerutils.cpp @@ -4068,6 +4068,24 @@ QList QgsSymbolLayerUtils::prettyBreaks( double minimum, double maximum, breaks[breaks.count() - 1] = maximum; } + // because sometimes when number of classes is big, + // break supposed to be at zero is something like -2.22045e-16 + if ( minimum < 0.0 && maximum > 0.0 ) //then there should be a zero somewhere + { + QList breaksMinusZero; // compute difference "each break - 0" + for ( int i = 0; i < breaks.count(); i++ ) + { + breaksMinusZero.append( breaks[i] - 0.0 ); + } + int posOfMin = 0; + for ( int i = 1; i < breaks.count(); i++ ) // find position of minimal difference + { + if ( std::abs( breaksMinusZero[i] ) < std::abs( breaksMinusZero[i - 1] ) ) + posOfMin = i; + } + breaks[posOfMin] = 0.0; + } + return breaks; } diff --git a/src/gui/symbology/qgsgraduatedsymbolrendererwidget.cpp b/src/gui/symbology/qgsgraduatedsymbolrendererwidget.cpp index 0cb7359061e7..11d083f54c62 100644 --- a/src/gui/symbology/qgsgraduatedsymbolrendererwidget.cpp +++ b/src/gui/symbology/qgsgraduatedsymbolrendererwidget.cpp @@ -435,10 +435,7 @@ QgsExpressionContext QgsGraduatedSymbolRendererWidget::createExpressionContext() QgsGraduatedSymbolRendererWidget::QgsGraduatedSymbolRendererWidget( QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer ) : QgsRendererWidget( layer, style ) - { - - // try to recognize the previous renderer // (null renderer means "no previous renderer") if ( renderer ) @@ -513,7 +510,6 @@ QgsGraduatedSymbolRendererWidget::QgsGraduatedSymbolRendererWidget( QgsVectorLay connect( btnDeleteAllClasses, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteAllClasses ); connect( btnGraduatedAdd, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::addClass ); connect( cbxLinkBoundaries, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::toggleBoundariesLink ); - connect( mSizeUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed ); connectUpdateHandlers(); @@ -563,7 +559,6 @@ QgsFeatureRenderer *QgsGraduatedSymbolRendererWidget::renderer() } // Connect/disconnect event handlers which trigger updating renderer - void QgsGraduatedSymbolRendererWidget::connectUpdateHandlers() { connect( spinGraduatedClasses, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); @@ -577,10 +572,14 @@ void QgsGraduatedSymbolRendererWidget::connectUpdateHandlers() connect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved ); connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged ); + + connect( cbxClassifySymmetric, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); + connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); + connect( cboSymmetryPointForPretty, static_cast( &QComboBox::activated ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); + connect( spinSymmetryPointForOtherMethods, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); } // Connect/disconnect event handlers which trigger updating renderer - void QgsGraduatedSymbolRendererWidget::disconnectUpdateHandlers() { disconnect( spinGraduatedClasses, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); @@ -594,17 +593,74 @@ void QgsGraduatedSymbolRendererWidget::disconnectUpdateHandlers() disconnect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved ); disconnect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged ); + + disconnect( cbxClassifySymmetric, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); + disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); + disconnect( cboSymmetryPointForPretty, static_cast( &QComboBox::activated ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); + disconnect( spinSymmetryPointForOtherMethods, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated ); } void QgsGraduatedSymbolRendererWidget::updateUiFromRenderer( bool updateCount ) { disconnectUpdateHandlers(); - updateGraduatedSymbolIcon(); + spinSymmetryPointForOtherMethods->setShowClearButton( false ); // update UI from the graduated renderer (update combo boxes, view) if ( mRenderer->mode() < cboGraduatedMode->count() ) + { cboGraduatedMode->setCurrentIndex( mRenderer->mode() ); + } + + // symmetric classification + if ( cboGraduatedMode->currentIndex() == 0 || cboGraduatedMode->currentIndex() == 3 ) // EqualInterval or StdDev + { + cbxClassifySymmetric->setVisible( true ); + cbxAstride->setVisible( true ); + cboSymmetryPointForPretty->setVisible( false ); + spinSymmetryPointForOtherMethods->setVisible( true ); + spinSymmetryPointForOtherMethods->setValue( mRenderer->symmetryPoint() ); + } + else if ( cboGraduatedMode->currentIndex() == 4 ) // Pretty + { + cbxClassifySymmetric->setVisible( true ); + cbxAstride->setVisible( true ); + spinSymmetryPointForOtherMethods->setVisible( false ); + cboSymmetryPointForPretty->setVisible( true ); + cboSymmetryPointForPretty->clear(); + cboSymmetryPointForPretty->addItems( mRenderer->listForCboPrettyBreaks() ); + // replace the combobox on the good old value + cboSymmetryPointForPretty->setCurrentText( QString::number( mRenderer->symmetryPoint(), 'f', 2 ) ); + } + else // quantile or Jenks + { + cbxClassifySymmetric->setVisible( false ); + cbxAstride->setVisible( false ); + cboSymmetryPointForPretty->setVisible( false ); + spinSymmetryPointForOtherMethods->setVisible( false ); + spinSymmetryPointForOtherMethods->setValue( mRenderer->symmetryPoint() ); + } + + if ( true == mRenderer->useSymmetricMode() ) + { + cbxClassifySymmetric->setChecked( true ); + spinSymmetryPointForOtherMethods->setEnabled( true ); + cbxAstride->setEnabled( true ); + cboSymmetryPointForPretty->setEnabled( true ); + } + else + { + cbxClassifySymmetric->setChecked( false ); + spinSymmetryPointForOtherMethods->setEnabled( false ); + cbxAstride->setEnabled( false ); + cboSymmetryPointForPretty->setEnabled( false ); + } + + if ( true == mRenderer->astride() ) + cbxAstride->setChecked( true ); + else + cbxAstride->setChecked( false ); + // Only update class count if different - otherwise typing value gets very messy int nclasses = mRenderer->ranges().count(); @@ -796,7 +852,6 @@ void QgsGraduatedSymbolRendererWidget::applyChangeToSymbol() void QgsGraduatedSymbolRendererWidget::classifyGraduated() { QString attrName = mExpressionWidget->currentField(); - int nclasses = spinGraduatedClasses->value(); std::unique_ptr ramp( btnColorRamp->colorRamp() ); @@ -807,16 +862,53 @@ void QgsGraduatedSymbolRendererWidget::classifyGraduated() } QgsGraduatedSymbolRenderer::Mode mode; - if ( cboGraduatedMode->currentIndex() == 0 ) + bool useSymmetricMode = false; + bool astride = false; + double symmetryPoint = 0.0; + int attrNum = mLayer->fields().lookupField( attrName ); + double minimum = mLayer->minimumValue( attrNum ).toDouble(); + double maximum = mLayer->maximumValue( attrNum ).toDouble(); + spinSymmetryPointForOtherMethods->setMinimum( minimum ); + spinSymmetryPointForOtherMethods->setMaximum( maximum ); + + if ( cboGraduatedMode->currentIndex() == 0 ) // EqualInterval + { mode = QgsGraduatedSymbolRenderer::EqualInterval; - else if ( cboGraduatedMode->currentIndex() == 2 ) + if ( cbxClassifySymmetric->isChecked() ) + { + useSymmetricMode = true; + symmetryPoint = spinSymmetryPointForOtherMethods->value(); + cbxAstride->isChecked() ? astride = true : astride = false; + } + } + else if ( cboGraduatedMode->currentIndex() == 2 ) // Jenks + { mode = QgsGraduatedSymbolRenderer::Jenks; - else if ( cboGraduatedMode->currentIndex() == 3 ) + } + else if ( cboGraduatedMode->currentIndex() == 3 ) // StdDev + { mode = QgsGraduatedSymbolRenderer::StdDev; - else if ( cboGraduatedMode->currentIndex() == 4 ) + if ( cbxClassifySymmetric->isChecked() ) + { + useSymmetricMode = true; + symmetryPoint = spinSymmetryPointForOtherMethods->value(); + cbxAstride->isChecked() ? astride = true : astride = false; + } + } + else if ( cboGraduatedMode->currentIndex() == 4 ) // Pretty + { mode = QgsGraduatedSymbolRenderer::Pretty; + if ( cbxClassifySymmetric->isChecked() ) + { + useSymmetricMode = true; + cbxAstride->isChecked() ? astride = true : astride = false; + symmetryPoint = cboSymmetryPointForPretty->currentText().toDouble(); //selected number + } + } else // default should be quantile for now - mode = QgsGraduatedSymbolRenderer::Quantile; + { + mode = QgsGraduatedSymbolRenderer::Quantile; // Quantile + } // Jenks is n^2 complexity, warn for big dataset (more than 50k records) // and give the user the chance to cancel @@ -825,11 +917,12 @@ void QgsGraduatedSymbolRendererWidget::classifyGraduated() if ( QMessageBox::Cancel == QMessageBox::question( this, tr( "Apply Classification" ), tr( "Natural break classification (Jenks) is O(n2) complexity, your classification may take a long time.\nPress cancel to abort breaks calculation or OK to continue." ), QMessageBox::Cancel, QMessageBox::Ok ) ) return; } - // create and set new renderer - mRenderer->setClassAttribute( attrName ); mRenderer->setMode( mode ); + mRenderer->setUseSymmetricMode( useSymmetricMode ); + mRenderer->setSymmetryPoint( symmetryPoint ); + mRenderer->setAstride( astride ); if ( methodComboBox->currentIndex() == 0 ) { @@ -846,7 +939,8 @@ void QgsGraduatedSymbolRendererWidget::classifyGraduated() } QApplication::setOverrideCursor( Qt::WaitCursor ); - mRenderer->updateClasses( mLayer, mode, nclasses ); + + mRenderer->updateClasses( mLayer, mode, nclasses, useSymmetricMode, symmetryPoint, astride ); if ( methodComboBox->currentIndex() == 1 ) mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() ); diff --git a/src/ui/qgsgraduatedsymbolrendererv2widget.ui b/src/ui/qgsgraduatedsymbolrendererv2widget.ui index 726a5bebd2b1..31a13d52c86a 100644 --- a/src/ui/qgsgraduatedsymbolrendererv2widget.ui +++ b/src/ui/qgsgraduatedsymbolrendererv2widget.ui @@ -6,7 +6,7 @@ 0 0 - 442 + 952 554 @@ -206,7 +206,7 @@ Negative rounds to powers of 10 1.000000000000000 - + false @@ -238,7 +238,7 @@ Negative rounds to powers of 10 10.000000000000000 - + false @@ -316,6 +316,9 @@ Negative rounds to powers of 10 + + false + Equal Interval @@ -343,6 +346,9 @@ Negative rounds to powers of 10 + + + @@ -387,6 +393,80 @@ Negative rounds to powers of 10 + + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + true + + + Symmetric around: + + + true + + + false + + + false + + + + + + + + + + false + + + + + + + + + + with a class astride that value + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + +