Merge pull request #31500 from 3nids/refactor_classification

Refactor graduated symbol renderer
This commit is contained in:
Denis Rouzaud 2019-09-02 14:57:18 +02:00 committed by GitHub
commit b3d52df67b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 2838 additions and 806 deletions

View File

@ -58,6 +58,7 @@ IF(WITH_APIDOC)
${CMAKE_SOURCE_DIR}/src/core/annotations
${CMAKE_SOURCE_DIR}/src/core/auth
${CMAKE_SOURCE_DIR}/src/core/callouts
${CMAKE_SOURCE_DIR}/src/core/classification
${CMAKE_SOURCE_DIR}/src/core/diagram
${CMAKE_SOURCE_DIR}/src/core/dxf
${CMAKE_SOURCE_DIR}/src/core/effects

View File

@ -118,6 +118,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/core/scalebar
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/core/classification
${CMAKE_SOURCE_DIR}/src/core/validity
${CMAKE_SOURCE_DIR}/src/plugins
${CMAKE_SOURCE_DIR}/external

View File

@ -0,0 +1,44 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationcustom.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsClassificationCustom : QgsClassificationMethod
{
%Docstring
QgsClassificationCustom is a dummy implementation of QgsClassification
which does not compute any break.
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsclassificationcustom.h"
%End
public:
QgsClassificationCustom();
virtual QgsClassificationMethod *clone() const;
virtual QString name() const;
virtual QString id() const;
static const QString METHOD_ID;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationcustom.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,43 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationequalinterval.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsClassificationEqualInterval : QgsClassificationMethod
{
%Docstring
QgsClassificationEqualInterval is an implementation of QgsClassificationMethod
for equal intervals
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsclassificationequalinterval.h"
%End
public:
QgsClassificationEqualInterval();
virtual QString name() const;
virtual QString id() const;
virtual QgsClassificationMethod *clone() const;
static const QString METHOD_ID;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationequalinterval.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,41 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationjenks.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsClassificationJenks : QgsClassificationMethod
{
%Docstring
QgsClassificationJenks is an implementation of QgsClassificationMethod
for natural breaks based on Jenks method
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsclassificationjenks.h"
%End
public:
QgsClassificationJenks();
virtual QString name() const;
virtual QString id() const;
virtual QgsClassificationMethod *clone() const;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationjenks.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,308 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationmethod.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
// This is required for the ConvertToSubClassCode to work properly
// so RTTI for casting is available in the whole module.
%ModuleCode
#include "qgsclassificationequalinterval.h"
#include "qgsclassificationjenks.h"
#include "qgsclassificationprettybreaks.h"
#include "qgsclassificationquantile.h"
#include "qgsclassificationstandarddeviation.h"
%End
class QgsClassificationRange
{
%Docstring
QgsClassificationRange contains the information about a classification range
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsclassificationmethod.h"
%End
public:
QgsClassificationRange( const QString &label, const double &lowerBound, const double &upperBound );
%Docstring
Constructor
%End
double lowerBound() const;
%Docstring
Returns the lower bound
%End
double upperBound() const;
%Docstring
Returns the upper bound
%End
QString label() const;
%Docstring
Returns the lower bound
%End
};
class QgsClassificationMethod /Abstract/
{
%Docstring
QgsClassificationMethod is an abstract class for implementations of classification methods
.. seealso:: :py:class:`QgsClassificationMethodRegistry`
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsclassificationmethod.h"
%End
%ConvertToSubClassCode
if ( dynamic_cast<QgsClassificationEqualInterval *>( sipCpp ) )
sipType = sipType_QgsClassificationEqualInterval;
else if ( dynamic_cast<QgsClassificationJenks *>( sipCpp ) )
sipType = sipType_QgsClassificationJenks;
else if ( dynamic_cast<QgsClassificationPrettyBreaks *>( sipCpp ) )
sipType = sipType_QgsClassificationPrettyBreaks;
else if ( dynamic_cast<QgsClassificationQuantile *>( sipCpp ) )
sipType = sipType_QgsClassificationQuantile;
else if ( dynamic_cast<QgsClassificationStandardDeviation *>( sipCpp ) )
sipType = sipType_QgsClassificationStandardDeviation;
else
sipType = 0;
%End
public:
enum MethodProperty
{
NoFlag,
ValuesNotRequired,
SymmetricModeAvailable,
};
typedef QFlags<QgsClassificationMethod::MethodProperty> MethodProperties;
enum ClassPosition
{
LowerBound,
Inner,
UpperBound
};
explicit QgsClassificationMethod( MethodProperties properties = NoFlag, int codeComplexity = 1 );
%Docstring
Creates a classification method.
:param properties: The properties of the implemented method
:param codeComplexity: as the exponent in the big O notation
%End
virtual ~QgsClassificationMethod();
virtual QgsClassificationMethod *clone() const = 0 /Factory/;
%Docstring
Returns a clone of the method.
Implementation can take advantage of copyBase method which copies the parameters of the base class
%End
virtual QString name() const = 0;
%Docstring
The readable and translate name of the method
%End
virtual QString id() const = 0;
%Docstring
The id of the method as saved in the project, must be unique in registry
%End
virtual QString labelForRange( const double &lowerValue, const double &upperValue, ClassPosition position = Inner ) const;
%Docstring
Returns the label for a range
%End
virtual void writeXml( QDomElement &element, const QgsReadWriteContext &context ) const;
%Docstring
Writes extra information about the method
%End
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context );
%Docstring
Reads extra information to apply it to the method
%End
bool valuesRequired() const;
%Docstring
Returns if the method requires values to calculate the classes
If not, bounds are sufficient
%End
int codeComplexity() const;
%Docstring
Code complexity as the exponent in Big O notation
%End
bool symmetricModeAvailable() const;
%Docstring
Returns if the method supports symmetric calculation
%End
bool symmetricModeEnabled() const;
%Docstring
Returns if the symmetric mode is enabled
%End
double symmetryPoint() const;
%Docstring
Returns the symmetry point for symmetric mode
%End
bool symmetryAstride() const;
%Docstring
Returns if the symmetric mode is astride
if ``True``, it will remove the symmetry point break so that the 2 classes form only one
%End
void setSymmetricMode( bool enabled, double symmetryPoint = 0, bool symmetryAstride = false );
%Docstring
Defines if the symmetric mode is enables and configures its parameters.
If the symmetric mode is not available in the current implementation, calling this method has no effect.
:param enabled: if the symmetric mode is enabled
:param symmetryPoint: the value of the symmetry point
:param symmetryAstride: if ``True``, it will remove the symmetry point break so that the 2 classes form only one
%End
QString labelFormat() const;
%Docstring
Returns the format of the label for the classes
%End
void setLabelFormat( const QString &format );
%Docstring
Defines the format of the labels for the classes, using %1 and %2 for the bounds
%End
int labelPrecision() const;
%Docstring
Returns the precision for the formatting of the labels
%End
void setLabelPrecision( int labelPrecision );
%Docstring
Defines the precision for the formatting of the labels
%End
bool labelTrimTrailingZeroes() const;
%Docstring
Returns if the trailing 0 are trimmed in the label
%End
void setLabelTrimTrailingZeroes( bool trimTrailingZeroes );
%Docstring
Defines if the trailing 0 are trimmed in the label
%End
static QList<double> listToValues( const QList<QgsClassificationRange> classes );
%Docstring
Transforms a list of classes to a list of breaks
%End
QList<QgsClassificationRange> classes( const QgsVectorLayer *layer, const QString &expression, int nclasses );
%Docstring
This will calculate the classes for a given layer to define the classes.
:param layer: The vector layer
:param expression: The name of the field on which the classes are calculated
:param nclasses: The number of classes to be returned
%End
QList<QgsClassificationRange> classes( const QList<double> &values, int nclasses );
%Docstring
This will calculate the classes for a list of values.
:param values: The list of values
:param nclasses: The number of classes to be returned
%End
QList<QgsClassificationRange> classes( double minimum, double maximum, int nclasses );
%Docstring
This will calculate the classes for defined bounds without any values.
.. warning::
If the method implementation requires values, this will return an empty list.
:param minimum: The minimum value for the breaks
:param maximum: The maximum value for the breaks
:param nclasses: The number of classes to be returned
%End
QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const;
%Docstring
Saves the method to a DOM element and return it
:param doc: the DOM document
:param context: the read/write context
%End
static QgsClassificationMethod *create( const QDomElement &element, const QgsReadWriteContext &context ) /Factory/;
%Docstring
Reads the DOM element and return a new classification method from it
:param element: the DOM element
:param context: the read/write context
%End
static void makeBreaksSymmetric( QList<double> &breaks /In,Out/, double symmetryPoint, bool astride );
%Docstring
Remove the breaks that are above the existing opposite sign classes to keep colors symmetrically balanced around symmetryPoint
Does not put a break on the symmetryPoint. This is done before.
:param breaks: The breaks of an already-done classification
:param symmetryPoint: The point around which we want a symmetry
:param astride: A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] )
%End
QString labelForRange( const QgsRendererRange &range, ClassPosition position = Inner ) const;
%Docstring
Returns the label for a range
%End
static const int MAX_PRECISION;
static const int MIN_PRECISION;
protected:
void copyBase( QgsClassificationMethod *c ) const;
%Docstring
Copy the parameters (shall be used in clone implementation)
%End
QString formatNumber( double value ) const;
%Docstring
Format the number according to label properties
%End
};
QFlags<QgsClassificationMethod::MethodProperty> operator|(QgsClassificationMethod::MethodProperty f1, QFlags<QgsClassificationMethod::MethodProperty> f2);
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationmethod.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,55 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationmethodregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsClassificationMethodRegistry
{
%Docstring
This class manages all known classification methods
QgsClassificationMethodRegistry is not usually directly created, but rather accessed through
:py:func:`QgsApplication.classificationMethodRegistry()`
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsclassificationmethodregistry.h"
%End
public:
QgsClassificationMethodRegistry();
bool addMethod( QgsClassificationMethod *method /Transfer/ );
%Docstring
Adds a method to the registry
Returns false if a method with same id already exists.
%End
QgsClassificationMethod *method( const QString &id ) /Factory/;
%Docstring
Returns a new instance of the method for the given id
%End
QMap<QString, QString> methodNames() const;
%Docstring
Returns a map <id, name> of all registered methods
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationmethodregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,41 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationprettybreaks.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsClassificationPrettyBreaks : QgsClassificationMethod
{
%Docstring
QgsClassificationPrettryBreaks is an implementation of QgsClassificationMethod
for pretty breaks
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsclassificationprettybreaks.h"
%End
public:
QgsClassificationPrettyBreaks();
virtual QString name() const;
virtual QString id() const;
virtual QgsClassificationMethod *clone() const;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationprettybreaks.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,42 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationquantile.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsClassificationQuantile : QgsClassificationMethod
{
%Docstring
QgsClassificationQuantile is an implementation of QgsClassificationMethod
based on quantiles
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsclassificationquantile.h"
%End
public:
QgsClassificationQuantile();
virtual QString name() const;
virtual QString id() const;
virtual QgsClassificationMethod *clone() const;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationquantile.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,49 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationstandarddeviation.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsClassificationStandardDeviation : QgsClassificationMethod
{
%Docstring
QgsClassificationCustom is an implementation of QgsClassificationMethod
based on standard deviation
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsclassificationstandarddeviation.h"
%End
public:
QgsClassificationStandardDeviation();
virtual QString name() const;
virtual QString id() const;
virtual QgsClassificationMethod *clone() const;
virtual QString labelForRange( const double &lowerValue, const double &upperValue, ClassPosition position ) const;
virtual void writeXml( QDomElement &element, const QgsReadWriteContext &context ) const;
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context );
static const QString METHOD_ID;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/classification/qgsclassificationstandarddeviation.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -711,6 +711,14 @@ Returns the application's plugin layer registry, used for managing plugin layer
.. versionadded:: 3.0
%End
static QgsClassificationMethodRegistry *classificationMethodRegistry() /KeepReference/;
%Docstring
Returns the application's classification methods registry, used in graduated renderer
.. versionadded:: 3.10
%End
static QgsMessageLog *messageLog();
%Docstring
Returns the application's message log.

View File

@ -117,6 +117,21 @@ Tests whether classes assigned to the renderer have gaps between the ranges.
void sortByValue( Qt::SortOrder order = Qt::AscendingOrder );
void sortByLabel( Qt::SortOrder order = Qt::AscendingOrder );
QgsClassificationMethod *classificationMethod() const;
%Docstring
Returns the classification method
.. versionadded:: 3.10
%End
void setClassificationMethod( QgsClassificationMethod *method /Transfer/ );
%Docstring
Defines the classification method
This will take ownership of the method
.. versionadded:: 3.10
%End
enum Mode
{
EqualInterval,
@ -127,54 +142,74 @@ Tests whether classes assigned to the renderer have gaps between the ranges.
Custom
};
Mode mode() const;
void setMode( Mode mode );
bool useSymmetricMode() const;
Mode mode() const /Deprecated/;
%Docstring
.. deprecated:: since QGIS 3.10 use classficationMethod instead
%End
void setMode( Mode mode ) /Deprecated/;
%Docstring
.. deprecated:: since QGIS 3.10 use classficationMethod instead
%End
bool useSymmetricMode() const /Deprecated/;
%Docstring
Returns if we want to classify symmetric around a given value
.. versionadded:: 3.4
.. deprecated:: since QGIS 3.10 use classficationMethod instead
%End
void setUseSymmetricMode( bool useSymmetricMode );
void setUseSymmetricMode( bool useSymmetricMode ) /Deprecated/;
%Docstring
Set if we want to classify symmetric around a given value
.. versionadded:: 3.4
.. deprecated:: since QGIS 3.10 use classficationMethod instead
%End
double symmetryPoint() const;
double symmetryPoint() const /Deprecated/;
%Docstring
Returns the pivot value for symmetric classification
.. versionadded:: 3.4
.. deprecated:: since QGIS 3.10 use classficationMethod instead
%End
void setSymmetryPoint( double symmetryPoint );
void setSymmetryPoint( double symmetryPoint ) /Deprecated/;
%Docstring
Set the pivot point
.. versionadded:: 3.4
.. deprecated:: since QGIS 3.10 use classficationMethod instead
%End
bool astride() const;
bool astride() const /Deprecated/;
%Docstring
Returns if we want to have a central class astride the pivot value
.. versionadded:: 3.4
.. deprecated:: since QGIS 3.10 use classficationMethod instead
%End
void setAstride( bool astride );
void setAstride( bool astride ) /Deprecated/;
%Docstring
Set if we want a central class astride the pivot value
.. versionadded:: 3.4
.. deprecated:: since QGIS 3.10 use classficationMethod instead
%End
static void makeBreaksSymmetric( QList<double> &breaks /In,Out/, double symmetryPoint, bool astride );
static void makeBreaksSymmetric( QList<double> &breaks /In,Out/, double symmetryPoint, bool astride ) /Deprecated/;
%Docstring
Remove the breaks that are above the existing opposite sign classes to keep colors symmetrically balanced around symmetryPoint
Does not put a break on the symmetryPoint. This is done before.
@ -184,9 +219,11 @@ Does not put a break on the symmetryPoint. This is done before.
:param astride: A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] )
.. versionadded:: 3.4
.. deprecated:: since QGIS 3.10, use QgsClassificationMethod.makeBreaksSymmetric instead
%End
static QList<double> calcEqualIntervalBreaks( double minimum, double maximum, int classes, bool useSymmetricMode, double symmetryPoint, bool astride );
static QList<double> calcEqualIntervalBreaks( double minimum, double maximum, int classes, bool useSymmetricMode, double symmetryPoint, bool astride );
%Docstring
Compute the equal interval classification
@ -196,9 +233,11 @@ Compute the equal interval classification
:param useSymmetricMode: A bool indicating if we want to have classes and hence colors ramp symmetric around a value
:param symmetryPoint: The point around which we want a symmetry
:param astride: A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] )
.. deprecated:: since QGIS 3.10 use QgsClassificationEqualInterval class instead
%End
void updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses, bool useSymmetricMode = false, double symmetryPoint = 0.0, bool astride = false );
void updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses, bool useSymmetricMode = false, double symmetryPoint = 0.0, bool astride = false ) /Deprecated/;
%Docstring
Recalculate classes for a layer
@ -210,16 +249,28 @@ Recalculate classes for a layer
:param astride: A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] )
.. versionadded:: 2.6
.. deprecated:: since QGIS 3.10
%End
const QgsRendererRangeLabelFormat &labelFormat() const;
void updateClasses( const QgsVectorLayer *vl, int nclasses );
%Docstring
Recalculate classes for a layer
:param vl: The layer being rendered (from which data values are calculated)
:param nclasses: the number of classes
%End
const QgsRendererRangeLabelFormat &labelFormat() const;
%Docstring
Returns the label format used to generate default classification labels
.. versionadded:: 2.6
.. deprecated:: since QGIS 3.10 use classificationMethod() and QgsClassificationMethod.setLabelFormat instead
%End
void setLabelFormat( const QgsRendererRangeLabelFormat &labelFormat, bool updateRanges = false );
void setLabelFormat( const QgsRendererRangeLabelFormat &labelFormat, bool updateRanges = false ) /Deprecated/;
%Docstring
Set the label format used to generate default classification labels
@ -227,6 +278,8 @@ Set the label format used to generate default classification labels
:param updateRanges: If ``True`` then ranges ending with the old unit string are updated to the new.
.. versionadded:: 2.6
.. deprecated:: since QGIS 3.10 use classificationMethod() and QgsClassificationMethod.setLabelFormat instead
%End
void calculateLabelPrecision( bool updateRanges = true );
@ -238,6 +291,7 @@ Reset the label decimal places to a numberbased on the minimum class interval
.. versionadded:: 2.6
%End
static QgsGraduatedSymbolRenderer *createRenderer( QgsVectorLayer *vlayer,
const QString &attrName,
int classes,
@ -248,7 +302,7 @@ Reset the label decimal places to a numberbased on the minimum class interval
bool useSymmetricMode = false,
double symmetryPoint = 0.0,
QStringList listForCboPrettyBreaks = QStringList(),
bool astride = false );
bool astride = false ) /Deprecated/;
%Docstring
Creates a new graduated renderer.
@ -265,6 +319,8 @@ Creates a new graduated renderer.
:param astride: A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] )
:return: new QgsGraduatedSymbolRenderer object
.. deprecated:: since QGIS 3.10
%End
static QgsFeatureRenderer *create( QDomElement &element, const QgsReadWriteContext &context ) /Factory/;
@ -418,6 +474,13 @@ Returns configuration of appearance of legend when using data-defined size for m
Will return ``None`` if the functionality is disabled.
.. versionadded:: 3.0
%End
void updateRangeLabels();
%Docstring
Updates the labels of the ranges
.. versionadded:: 3.10
%End
protected:

View File

@ -23,6 +23,15 @@ class QgsRendererRange
QgsRendererRange();
%Docstring
Constructor for QgsRendererRange.
%End
QgsRendererRange( const QgsClassificationRange &range, QgsSymbol *symbol /Transfer/, bool render = true );
%Docstring
Creates a renderer symbol range
:param range: The classification range
:param symbol: The symbol for this renderer range
:param render: If true, it will be renderered
%End
QgsRendererRange( double lowerValue, double upperValue, QgsSymbol *symbol /Transfer/, const QString &label, bool render = true );
QgsRendererRange( const QgsRendererRange &range );
@ -64,11 +73,14 @@ Creates a DOM element representing the range in SLD format.
typedef QList<QgsRendererRange> QgsRangeList;
class QgsRendererRangeLabelFormat
class QgsRendererRangeLabelFormat /Deprecated/
{
%Docstring
.. versionadded:: 2.6
.. deprecated:: since QGIS 3.10, use QgsClassificationMethod instead
%End
%TypeHeaderCode

View File

@ -158,6 +158,14 @@
%Include auto_generated/auth/qgsauthmethod.sip
%Include auto_generated/callouts/qgscallout.sip
%Include auto_generated/callouts/qgscalloutsregistry.sip
%Include auto_generated/classification/qgsclassificationmethodregistry.sip
%Include auto_generated/classification/qgsclassificationmethod.sip
%Include auto_generated/classification/qgsclassificationcustom.sip
%Include auto_generated/classification/qgsclassificationequalinterval.sip
%Include auto_generated/classification/qgsclassificationprettybreaks.sip
%Include auto_generated/classification/qgsclassificationquantile.sip
%Include auto_generated/classification/qgsclassificationjenks.sip
%Include auto_generated/classification/qgsclassificationstandarddeviation.sip
%Include auto_generated/diagram/qgsdiagram.sip
%Include auto_generated/diagram/qgspiediagram.sip
%Include auto_generated/diagram/qgstextdiagram.sip

View File

@ -20,6 +20,15 @@ SET(QGIS_CORE_SRCS
callouts/qgscallout.cpp
callouts/qgscalloutsregistry.cpp
classification/qgsclassificationmethodregistry.cpp
classification/qgsclassificationmethod.cpp
classification/qgsclassificationcustom.cpp
classification/qgsclassificationequalinterval.cpp
classification/qgsclassificationprettybreaks.cpp
classification/qgsclassificationquantile.cpp
classification/qgsclassificationjenks.cpp
classification/qgsclassificationstandarddeviation.cpp
gps/qgsgpsconnection.cpp
gps/qgsgpsconnectionregistry.cpp
gps/qgsgpsdconnection.cpp
@ -1075,6 +1084,15 @@ SET(QGIS_CORE_HDRS
callouts/qgscallout.h
callouts/qgscalloutsregistry.h
classification/qgsclassificationmethodregistry.h
classification/qgsclassificationmethod.h
classification/qgsclassificationcustom.h
classification/qgsclassificationequalinterval.h
classification/qgsclassificationprettybreaks.h
classification/qgsclassificationquantile.h
classification/qgsclassificationjenks.h
classification/qgsclassificationstandarddeviation.h
diagram/qgsdiagram.h
diagram/qgspiediagram.h
diagram/qgstextdiagram.h
@ -1294,6 +1312,7 @@ INCLUDE_DIRECTORIES(
3d
annotations
auth
classification
dxf
effects
${CMAKE_SOURCE_DIR}/src/core/expression

View File

@ -0,0 +1,53 @@
/***************************************************************************
qgsclassificationcustom.cpp
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 "qgsclassificationcustom.h"
const QString QgsClassificationCustom::METHOD_ID = QStringLiteral( "Custom" );
QgsClassificationCustom::QgsClassificationCustom()
: QgsClassificationMethod( ValuesNotRequired,
0 /*codeComplexity*/ )
{
}
QgsClassificationMethod *QgsClassificationCustom::clone() const
{
QgsClassificationCustom *c = new QgsClassificationCustom();
copyBase( c );
return c;
}
QString QgsClassificationCustom::name() const
{
return QObject::tr( "Custom" );
}
QString QgsClassificationCustom::id() const
{
return METHOD_ID;
}
QList<double> QgsClassificationCustom::calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses )
{
Q_UNUSED( minimum )
Q_UNUSED( maximum )
Q_UNUSED( values )
Q_UNUSED( nclasses )
return QList<double>();
}

View File

@ -0,0 +1,44 @@
/***************************************************************************
qgsclassificationcustom.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 QGSCLASSIFICATIONCUSTOM_H
#define QGSCLASSIFICATIONCUSTOM_H
#include "qgsclassificationmethod.h"
/**
* \ingroup core
* QgsClassificationCustom is a dummy implementation of QgsClassification
* which does not compute any break.
* \since QGIS 3.10
*/
class CORE_EXPORT QgsClassificationCustom : public QgsClassificationMethod
{
public:
QgsClassificationCustom();
QgsClassificationMethod *clone() const override;
QString name() const override;
QString id() const override;
static const QString METHOD_ID;
private:
QList<double> calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses ) override;
};
#endif // QGSCLASSIFICATIONCUSTOM_H

View File

@ -0,0 +1,97 @@
/***************************************************************************
qgsclassificationequalinterval.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 <QObject>
#include "qgsclassificationequalinterval.h"
const QString QgsClassificationEqualInterval::METHOD_ID = QStringLiteral( "EqualInterval" );
QgsClassificationEqualInterval::QgsClassificationEqualInterval()
: QgsClassificationMethod( ValuesNotRequired | SymmetricModeAvailable, 0 /*codeComplexity*/ )
{
}
QString QgsClassificationEqualInterval::name() const
{
return QObject::tr( "Equal Interval" );
}
QString QgsClassificationEqualInterval::id() const
{
return METHOD_ID;
}
QList<double> QgsClassificationEqualInterval::calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses )
{
Q_UNUSED( values )
// Equal interval algorithm
// Returns breaks based on dividing the range ('minimum' to 'maximum') into 'classes' parts.
QList<double> breaks;
if ( !symmetricModeEnabled() ) // normal mode
{
double step = ( maximum - minimum ) / nclasses;
double value = minimum;
breaks.reserve( nclasses );
for ( int i = 0; i < nclasses; i++ )
{
value += step;
breaks << value;
}
// floating point arithmetics is not precise:
// set the last break to be exactly maximum so we do not miss it
breaks[nclasses - 1] = maximum;
}
else // symmetric mode
{
double distBelowSymmetricValue = std::abs( minimum - symmetryPoint() );
double distAboveSymmetricValue = std::abs( maximum - symmetryPoint() ) ;
if ( symmetryAstride() )
{
if ( nclasses % 2 == 0 ) // we want odd number of classes
++nclasses;
}
else
{
if ( nclasses % 2 == 1 ) // we want even number of classes
++nclasses;
}
double step = 2 * std::min( distBelowSymmetricValue, distAboveSymmetricValue ) / nclasses;
breaks.reserve( nclasses );
double value = ( distBelowSymmetricValue < distAboveSymmetricValue ) ? minimum : maximum - nclasses * step;
for ( int i = 0; i < nclasses; i++ )
{
value += step;
breaks << value;
}
breaks[nclasses - 1] = maximum;
}
return breaks;
}
QgsClassificationMethod *QgsClassificationEqualInterval::clone() const
{
QgsClassificationEqualInterval *c = new QgsClassificationEqualInterval();
copyBase( c );
return c;
}

View File

@ -0,0 +1,46 @@
/***************************************************************************
qgsclassificationequalinterval.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 QGSCLASSIFICATIONEQUALINTERVAL_H
#define QGSCLASSIFICATIONEQUALINTERVAL_H
#include "qgis_core.h"
#include "qgsclassificationmethod.h"
/**
* \ingroup core
* QgsClassificationEqualInterval is an implementation of QgsClassificationMethod
* for equal intervals
* \since QGIS 3.10
*/
class CORE_EXPORT QgsClassificationEqualInterval : public QgsClassificationMethod
{
public:
QgsClassificationEqualInterval();
QString name() const override;
QString id() const override;
QgsClassificationMethod *clone() const override;
static const QString METHOD_ID;
private:
QList<double> calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses ) override;
};
#endif // QGSCLASSIFICATIONEQUALINTERVAL_H

View File

@ -0,0 +1,170 @@
/***************************************************************************
qgsclassificationjenks.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 <limits> // for jenks classification
#include "qgsclassificationjenks.h"
QgsClassificationJenks::QgsClassificationJenks()
: QgsClassificationMethod()
{
}
QString QgsClassificationJenks::name() const
{
return QObject::tr( "Natural Breaks (Jenks)" );
}
QString QgsClassificationJenks::id() const
{
return QStringLiteral( "Jenks" );
}
QgsClassificationMethod *QgsClassificationJenks::clone() const
{
QgsClassificationJenks *c = new QgsClassificationJenks();
copyBase( c );
return c;
}
QList<double> QgsClassificationJenks::calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses )
{
// Jenks Optimal (Natural Breaks) algorithm
// Based on the Jenks algorithm from the 'classInt' package available for
// the R statistical prgramming language, and from Python code from here:
// http://danieljlewis.org/2010/06/07/jenks-natural-breaks-algorithm-in-python/
// and is based on a JAVA and Fortran code available here:
// https://stat.ethz.ch/pipermail/r-sig-geo/2006-March/000811.html
// Returns class breaks such that classes are internally homogeneous while
// assuring heterogeneity among classes.
if ( values.isEmpty() )
return QList<double>();
if ( nclasses <= 1 )
{
return QList<double>() << maximum;
}
if ( nclasses >= values.size() )
{
return values;
}
QVector<double> sample;
// if we have lots of values, we need to take a random sample
if ( values.size() > mMaximumSize )
{
// for now, sample at least maximumSize values or a 10% sample, whichever
// is larger. This will produce a more representative sample for very large
// layers, but could end up being computationally intensive...
sample.resize( std::max( mMaximumSize, values.size() / 10 ) );
QgsDebugMsg( QStringLiteral( "natural breaks (jenks) sample size: %1" ).arg( sample.size() ) );
QgsDebugMsg( QStringLiteral( "values:%1" ).arg( values.size() ) );
sample[ 0 ] = minimum;
sample[ 1 ] = maximum;
for ( int i = 2; i < sample.size(); i++ )
{
// pick a random integer from 0 to n
double r = qrand();
int j = std::floor( r / RAND_MAX * ( values.size() - 1 ) );
sample[ i ] = values[ j ];
}
}
else
{
sample = values.toVector();
}
int n = sample.size();
// sort the sample values
std::sort( sample.begin(), sample.end() );
QVector< QVector<int> > matrixOne( n + 1 );
QVector< QVector<double> > matrixTwo( n + 1 );
for ( int i = 0; i <= n; i++ )
{
matrixOne[i].resize( nclasses + 1 );
matrixTwo[i].resize( nclasses + 1 );
}
for ( int i = 1; i <= nclasses; i++ )
{
matrixOne[0][i] = 1;
matrixOne[1][i] = 1;
matrixTwo[0][i] = 0.0;
for ( int j = 2; j <= n; j++ )
{
matrixTwo[j][i] = std::numeric_limits<double>::max();
}
}
for ( int l = 2; l <= n; l++ )
{
double s1 = 0.0;
double s2 = 0.0;
int w = 0;
double v = 0.0;
for ( int m = 1; m <= l; m++ )
{
int i3 = l - m + 1;
double val = sample[ i3 - 1 ];
s2 += val * val;
s1 += val;
w++;
v = s2 - ( s1 * s1 ) / static_cast< double >( w );
int i4 = i3 - 1;
if ( i4 != 0 )
{
for ( int j = 2; j <= nclasses; j++ )
{
if ( matrixTwo[l][j] >= v + matrixTwo[i4][j - 1] )
{
matrixOne[l][j] = i4;
matrixTwo[l][j] = v + matrixTwo[i4][j - 1];
}
}
}
}
matrixOne[l][1] = 1;
matrixTwo[l][1] = v;
}
QVector<double> breaks( nclasses );
breaks[nclasses - 1] = sample[n - 1];
for ( int j = nclasses, k = n; j >= 2; j-- )
{
int id = matrixOne[k][j] - 1;
breaks[j - 2] = sample[id];
k = matrixOne[k][j] - 1;
}
return breaks.toList();
}

View File

@ -0,0 +1,44 @@
/***************************************************************************
qgsclassificationjenks.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 QGSCLASSIFICATIONJENKS_H
#define QGSCLASSIFICATIONJENKS_H
#include "qgis_core.h"
#include "qgsclassificationmethod.h"
/**
* \ingroup core
* QgsClassificationJenks is an implementation of QgsClassificationMethod
* for natural breaks based on Jenks method
* \since QGIS 3.10
*/
class CORE_EXPORT QgsClassificationJenks : public QgsClassificationMethod
{
public:
QgsClassificationJenks();
QString name() const override;
QString id() const override;
QgsClassificationMethod *clone() const override;
private:
QList<double> calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses ) override;
int mMaximumSize = 3000;
};
#endif // QGSCLASSIFICATIONJENKS_H

View File

@ -0,0 +1,301 @@
/***************************************************************************
qgsclassificationmethod.cpp
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 <QRegularExpression>
#include "qgis.h"
#include "qgsclassificationmethod.h"
#include "qgsvectorlayerutils.h"
#include "qgsvectorlayer.h"
#include "qgsgraduatedsymbolrenderer.h"
#include "qgsapplication.h"
#include "qgsclassificationmethodregistry.h"
const int QgsClassificationMethod::MAX_PRECISION = 15;
const int QgsClassificationMethod::MIN_PRECISION = -6;
static const QRegularExpression RE_TRAILING_ZEROES = QRegularExpression( "[.,]?0*$" );
static const QRegularExpression RE_NEGATIVE_ZERO = QRegularExpression( "^\\-0(?:[.,]0*)?$" );
QList<double> QgsClassificationMethod::listToValues( const QList<QgsClassificationRange> classes )
{
QList<double> values;
values.reserve( classes.count() );
for ( int i = 0 ; i < classes.count(); i++ )
values << classes.at( i ).upperBound();
return values;
}
QgsClassificationMethod::QgsClassificationMethod( MethodProperties properties, int codeComplexity )
: mFlags( properties )
, mCodeComplexity( codeComplexity )
, mLabelFormat( QStringLiteral( "%1 - %2 " ) )
{
}
void QgsClassificationMethod::copyBase( QgsClassificationMethod *c ) const
{
c->setSymmetricMode( mSymmetricEnabled, mSymmetryPoint, mSymmetryAstride );
c->setLabelFormat( mLabelFormat );
c->setLabelPrecision( mLabelPrecision );
c->setLabelTrimTrailingZeroes( mLabelTrimTrailingZeroes );
}
QgsClassificationMethod *QgsClassificationMethod::create( const QDomElement &element, const QgsReadWriteContext &context )
{
const QString methodId = element.attribute( QStringLiteral( "id" ) );
QgsClassificationMethod *method = QgsApplication::classificationMethodRegistry()->method( methodId );
// symmetric
QDomElement symmetricModeElem = element.firstChildElement( QStringLiteral( "symmetricMode" ) );
if ( !symmetricModeElem.isNull() )
{
bool symmetricEnabled = symmetricModeElem.attribute( QStringLiteral( "enabled" ) ).toInt() == 1;
double symmetricPoint = symmetricModeElem.attribute( QStringLiteral( "symmetrypoint" ) ).toDouble();
bool astride = symmetricModeElem.attribute( QStringLiteral( "astride" ) ).toInt() == 1;
method->setSymmetricMode( symmetricEnabled, symmetricPoint, astride );
}
// label format
QDomElement labelFormatElem = element.firstChildElement( QStringLiteral( "labelformat" ) );
if ( !labelFormatElem.isNull() )
{
QString format = labelFormatElem.attribute( QStringLiteral( "format" ), "%1" + QStringLiteral( " - " ) + "%2" );
int precision = labelFormatElem.attribute( QStringLiteral( "labelprecision" ), QStringLiteral( "4" ) ).toInt();
bool trimTrailingZeroes = labelFormatElem.attribute( QStringLiteral( "trimtrailingzeroes" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
method->setLabelFormat( format );
method->setLabelPrecision( precision );
method->setLabelTrimTrailingZeroes( trimTrailingZeroes );
}
// Read specific properties from the implementation
QDomElement extraElem = element.firstChildElement( QStringLiteral( "extraInformation" ) );
if ( !extraElem.isNull() )
method->readXml( extraElem, context );
return method;
}
QDomElement QgsClassificationMethod::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
{
QDomElement methodElem = doc.createElement( QStringLiteral( "classificationMethod" ) );
methodElem.setAttribute( QStringLiteral( "id" ), id() );
// symmetric
QDomElement symmetricModeElem = doc.createElement( QStringLiteral( "symmetricMode" ) );
symmetricModeElem.setAttribute( QStringLiteral( "enabled" ), symmetricModeEnabled() ? 1 : 0 );
symmetricModeElem.setAttribute( QStringLiteral( "symmetrypoint" ), symmetryPoint() );
symmetricModeElem.setAttribute( QStringLiteral( "astride" ), mSymmetryAstride ? 1 : 0 );
methodElem.appendChild( symmetricModeElem );
// label format
QDomElement labelFormatElem = doc.createElement( QStringLiteral( "labelFormat" ) );
labelFormatElem.setAttribute( QStringLiteral( "format" ), labelFormat() );
labelFormatElem.setAttribute( QStringLiteral( "labelprecision" ), labelPrecision() );
labelFormatElem.setAttribute( QStringLiteral( "trimtrailingzeroes" ), labelTrimTrailingZeroes() ? 1 : 0 );
methodElem.appendChild( labelFormatElem );
// extra information
QDomElement extraElem = doc.createElement( QStringLiteral( "extraInformation" ) );
writeXml( extraElem, context );
methodElem.appendChild( extraElem );
return methodElem;
}
void QgsClassificationMethod::setSymmetricMode( bool enabled, double symmetryPoint, bool astride )
{
mSymmetricEnabled = enabled;
mSymmetryPoint = symmetryPoint;
mSymmetryAstride = astride;
}
void QgsClassificationMethod::setLabelPrecision( int precision )
{
// Limit the range of decimal places to a reasonable range
precision = qBound( MIN_PRECISION, precision, MAX_PRECISION );
mLabelPrecision = precision;
mLabelNumberScale = 1.0;
mLabelNumberSuffix.clear();
while ( precision < 0 )
{
precision++;
mLabelNumberScale /= 10.0;
mLabelNumberSuffix.append( '0' );
}
}
QString QgsClassificationMethod::formatNumber( double value ) const
{
if ( mLabelPrecision > 0 )
{
QString valueStr = QLocale().toString( value, 'f', mLabelPrecision );
if ( mLabelTrimTrailingZeroes )
valueStr = valueStr.remove( RE_TRAILING_ZEROES );
if ( RE_NEGATIVE_ZERO.match( valueStr ).hasMatch() )
valueStr = valueStr.mid( 1 );
return valueStr;
}
else
{
QString valueStr = QLocale().toString( value * mLabelNumberScale, 'f', 0 );
if ( valueStr == QLatin1String( "-0" ) )
valueStr = '0';
if ( valueStr != QLatin1String( "0" ) )
valueStr = valueStr + mLabelNumberSuffix;
return valueStr;
}
}
QList<QgsClassificationRange> QgsClassificationMethod::classes( const QgsVectorLayer *layer, const QString &expression, int nclasses )
{
if ( expression.isEmpty() )
return QList<QgsClassificationRange>();
if ( nclasses < 1 )
nclasses = 1;
QList<double> values;
double minimum;
double maximum;
int fieldIndex = layer->fields().indexFromName( expression );
bool ok;
if ( valuesRequired() || fieldIndex == -1 )
{
values = QgsVectorLayerUtils::getDoubleValues( layer, expression, ok );
if ( !ok || values.isEmpty() )
return QList<QgsClassificationRange>();
auto result = std::minmax_element( values.begin(), values.end() );
minimum = *result.first;
maximum = *result.second;
}
else
{
minimum = layer->minimumValue( fieldIndex ).toDouble();
maximum = layer->maximumValue( fieldIndex ).toDouble();
}
// get the breaks
QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses );
breaks.insert( 0, minimum );
// create classes
return breaksToClasses( breaks );
}
QList<QgsClassificationRange> QgsClassificationMethod::classes( const QList<double> &values, int nclasses )
{
auto result = std::minmax_element( values.begin(), values.end() );
double minimum = *result.first;
double maximum = *result.second;
// get the breaks
QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses );
breaks.insert( 0, minimum );
// create classes
return breaksToClasses( breaks );
}
QList<QgsClassificationRange> QgsClassificationMethod::classes( double minimum, double maximum, int nclasses )
{
if ( valuesRequired() )
{
QgsDebugMsg( QString( "The classification method %1 tries to calculate classes without values while they are required." ).arg( name() ) );
}
// get the breaks
QList<double> breaks = calculateBreaks( minimum, maximum, QList<double>(), nclasses );
breaks.insert( 0, minimum );
// create classes
return breaksToClasses( breaks );
}
QList<QgsClassificationRange> QgsClassificationMethod::breaksToClasses( const QList<double> &breaks ) const
{
QList<QgsClassificationRange> classes;
QString label;
for ( int i = 1; i < breaks.count(); i++ )
{
const double lowerValue = breaks.at( i - 1 );
const double upperValue = breaks.at( i );
ClassPosition pos = Inner;
if ( i == 0 )
pos = LowerBound;
else if ( i == breaks.count() - 1 )
pos = UpperBound;
QString label = labelForRange( lowerValue, upperValue, pos );
classes << QgsClassificationRange( label, lowerValue, upperValue );
}
return classes;
}
void QgsClassificationMethod::makeBreaksSymmetric( QList<double> &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
if ( breaks.count() < 2 )
return;
std::sort( breaks.begin(), breaks.end() );
// breaks contain the maximum of the distrib but not the minimum
double distBelowSymmetricValue = std::fabs( breaks[0] - symmetryPoint );
double distAboveSymmetricValue = std::fabs( breaks[ breaks.size() - 2 ] - symmetryPoint ) ;
double absMin = std::min( distAboveSymmetricValue, distBelowSymmetricValue );
// make symmetric
for ( int i = 0; i <= breaks.size() - 2; ++i )
{
// part after "absMin" is for doubles rounding issues
if ( std::fabs( breaks.at( i ) - symmetryPoint ) >= ( absMin - std::fabs( breaks[0] - breaks[1] ) / 100. ) )
{
breaks.removeAt( i );
--i;
}
}
// remove symmetry point
if ( astride ) // && breaks.indexOf( symmetryPoint ) != -1) // if symmetryPoint is found
{
breaks.removeAt( breaks.indexOf( symmetryPoint ) );
}
}
QString QgsClassificationMethod::labelForRange( const QgsRendererRange &range, QgsClassificationMethod::ClassPosition position ) const
{
return labelForRange( range.lowerValue(), range.upperValue(), position );
}
QString QgsClassificationMethod::labelForRange( const double &lowerValue, const double &upperValue, ClassPosition position ) const
{
Q_UNUSED( position )
const QString lowerLabel = valueToLabel( lowerValue );
const QString upperLabel = valueToLabel( upperValue );
QString label( mLabelFormat );
label.replace( QLatin1String( "%1" ), lowerLabel ).replace( QLatin1String( "%2" ), upperLabel );
return label;
}

View File

@ -0,0 +1,311 @@
/***************************************************************************
qgsclassificationmethod.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 QGSCLASSIFICATIONMETHOD_H
#define QGSCLASSIFICATIONMETHOD_H
#include "qgis_sip.h"
#include "qgis_core.h"
#include "qgsprocessingparameters.h"
class QgsVectorLayer;
class QgsRendererRange;
#ifdef SIP_RUN
// This is required for the ConvertToSubClassCode to work properly
// so RTTI for casting is available in the whole module.
% ModuleCode
#include "qgsclassificationequalinterval.h"
#include "qgsclassificationjenks.h"
#include "qgsclassificationprettybreaks.h"
#include "qgsclassificationquantile.h"
#include "qgsclassificationstandarddeviation.h"
% End
#endif
/**
* \ingroup core
* QgsClassificationRange contains the information about a classification range
* \since QGIS 3.10
*/
class CORE_EXPORT QgsClassificationRange
{
public:
//! Constructor
QgsClassificationRange( const QString &label, const double &lowerBound, const double &upperBound )
: mLabel( label )
, mLowerBound( lowerBound )
, mUpperBound( upperBound )
{}
//! Returns the lower bound
double lowerBound() const {return mLowerBound;}
//! Returns the upper bound
double upperBound() const {return mUpperBound;}
//! Returns the lower bound
QString label() const {return mLabel;}
private:
QString mLabel;
double mLowerBound;
double mUpperBound;
};
/**
* \ingroup core
* QgsClassificationMethod is an abstract class for implementations of classification methods
* \see QgsClassificationMethodRegistry
* \since QGIS 3.10
*/
class CORE_EXPORT QgsClassificationMethod SIP_ABSTRACT
{
#ifdef SIP_RUN
SIP_CONVERT_TO_SUBCLASS_CODE
if ( dynamic_cast<QgsClassificationEqualInterval *>( sipCpp ) )
sipType = sipType_QgsClassificationEqualInterval;
else if ( dynamic_cast<QgsClassificationJenks *>( sipCpp ) )
sipType = sipType_QgsClassificationJenks;
else if ( dynamic_cast<QgsClassificationPrettyBreaks *>( sipCpp ) )
sipType = sipType_QgsClassificationPrettyBreaks;
else if ( dynamic_cast<QgsClassificationQuantile *>( sipCpp ) )
sipType = sipType_QgsClassificationQuantile;
else if ( dynamic_cast<QgsClassificationStandardDeviation *>( sipCpp ) )
sipType = sipType_QgsClassificationStandardDeviation;
else
sipType = 0;
SIP_END
#endif
public:
//! Flags for the classification method
enum MethodProperty
{
NoFlag = 0, //!< No flag
ValuesNotRequired = 1 << 1, //!< Values are not required to calculate, min/max are enough
SymmetricModeAvailable = 1 << 2, //!< This allows using symmetric classification
};
Q_DECLARE_FLAGS( MethodProperties, MethodProperty )
//! Defines the class position
enum ClassPosition
{
LowerBound, //!< The class is at the lower bound
Inner, //!< The class is not at a bound
UpperBound //!< The class is at the upper bound
};
/**
* Creates a classification method.
* \param properties The properties of the implemented method
* \param codeComplexity as the exponent in the big O notation
*/
explicit QgsClassificationMethod( MethodProperties properties = NoFlag, int codeComplexity = 1 );
virtual ~QgsClassificationMethod() = default;
/**
* Returns a clone of the method.
* Implementation can take advantage of copyBase method which copies the parameters of the base class
*/
virtual QgsClassificationMethod *clone() const = 0 SIP_FACTORY;
//! The readable and translate name of the method
virtual QString name() const = 0;
//! The id of the method as saved in the project, must be unique in registry
virtual QString id() const = 0;
/**
* Returns the label for a range
*/
virtual QString labelForRange( const double &lowerValue, const double &upperValue, ClassPosition position = Inner ) const;
//! Writes extra information about the method
virtual void writeXml( QDomElement &element, const QgsReadWriteContext &context ) const {Q_UNUSED( element ); Q_UNUSED( context )}
//! Reads extra information to apply it to the method
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context ) {Q_UNUSED( element ); Q_UNUSED( context )}
// *******************
// non-virtual methods
/**
* Returns if the method requires values to calculate the classes
* If not, bounds are sufficient
*/
bool valuesRequired() const {return !mFlags.testFlag( ValuesNotRequired );}
//! Code complexity as the exponent in Big O notation
int codeComplexity() const {return mCodeComplexity;}
/**
* Returns if the method supports symmetric calculation
*/
bool symmetricModeAvailable() const {return mFlags.testFlag( SymmetricModeAvailable );}
/**
* Returns if the symmetric mode is enabled
*/
bool symmetricModeEnabled() const {return symmetricModeAvailable() && mSymmetricEnabled;}
/**
* Returns the symmetry point for symmetric mode
*/
double symmetryPoint() const {return mSymmetryPoint;}
/**
* Returns if the symmetric mode is astride
* if TRUE, it will remove the symmetry point break so that the 2 classes form only one
*/
bool symmetryAstride() const {return mSymmetryAstride;}
/**
* Defines if the symmetric mode is enables and configures its parameters.
* If the symmetric mode is not available in the current implementation, calling this method has no effect.
* \param enabled if the symmetric mode is enabled
* \param symmetryPoint the value of the symmetry point
* \param symmetryAstride if TRUE, it will remove the symmetry point break so that the 2 classes form only one
*/
void setSymmetricMode( bool enabled, double symmetryPoint = 0, bool symmetryAstride = false );
// Label properties
//! Returns the format of the label for the classes
QString labelFormat() const { return mLabelFormat; }
//! Defines the format of the labels for the classes, using %1 and %2 for the bounds
void setLabelFormat( const QString &format ) { mLabelFormat = format; }
//! Returns the precision for the formatting of the labels
int labelPrecision() const { return mLabelPrecision; }
//! Defines the precision for the formatting of the labels
void setLabelPrecision( int labelPrecision );
//! Returns if the trailing 0 are trimmed in the label
bool labelTrimTrailingZeroes() const { return mLabelTrimTrailingZeroes; }
//! Defines if the trailing 0 are trimmed in the label
void setLabelTrimTrailingZeroes( bool trimTrailingZeroes ) { mLabelTrimTrailingZeroes = trimTrailingZeroes; }
//! Transforms a list of classes to a list of breaks
static QList<double> listToValues( const QList<QgsClassificationRange> classes );
/**
* This will calculate the classes for a given layer to define the classes.
* \param layer The vector layer
* \param expression The name of the field on which the classes are calculated
* \param nclasses The number of classes to be returned
*/
QList<QgsClassificationRange> classes( const QgsVectorLayer *layer, const QString &expression, int nclasses );
/**
* This will calculate the classes for a list of values.
* \param values The list of values
* \param nclasses The number of classes to be returned
*/
QList<QgsClassificationRange> classes( const QList<double> &values, int nclasses );
/**
* This will calculate the classes for defined bounds without any values.
* \warning If the method implementation requires values, this will return an empty list.
* \param minimum The minimum value for the breaks
* \param maximum The maximum value for the breaks
* \param nclasses The number of classes to be returned
*/
QList<QgsClassificationRange> classes( double minimum, double maximum, int nclasses );
/**
* Saves the method to a DOM element and return it
* \param doc the DOM document
* \param context the read/write context
*/
QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const;
/**
* Reads the DOM element and return a new classification method from it
* \param element the DOM element
* \param context the read/write context
*/
static QgsClassificationMethod *create( const QDomElement &element, const QgsReadWriteContext &context ) SIP_FACTORY;
/**
* Remove the breaks that are above the existing opposite sign classes to keep colors symmetrically balanced around symmetryPoint
* Does not put a break on the symmetryPoint. This is done before.
* \param breaks The breaks of an already-done classification
* \param symmetryPoint The point around which we want a symmetry
* \param astride A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] )
*/
static void makeBreaksSymmetric( QList<double> &breaks SIP_INOUT, double symmetryPoint, bool astride );
/**
* Returns the label for a range
*/
QString labelForRange( const QgsRendererRange &range, ClassPosition position = Inner ) const;
static const int MAX_PRECISION;
static const int MIN_PRECISION;
protected:
//! Copy the parameters (shall be used in clone implementation)
void copyBase( QgsClassificationMethod *c ) const;
//! Format the number according to label properties
QString formatNumber( double value ) const;
private:
/**
* Calculate the breaks, should be reimplemented, values might be an empty list
* If the symmetric mode is available, the implementation is responsible of applying the symmetry
* The maximum value is expected to be added at the end of the list, but not the minimum
*/
virtual QList<double> calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses ) = 0;
//! This is called after calculating the breaks or restoring from XML, so it can rely on private variables
virtual QString valueToLabel( const double &value ) const {return formatNumber( value );}
//! Create a list of ranges from a list of classes
QList<QgsClassificationRange> breaksToClasses( const QList<double> &breaks ) const;
// implementation properties (set by initialization)
MethodProperties mFlags = nullptr;
int mCodeComplexity = 1;
// parameters (set by setters)
// if some are added here, they should be handled in the clone method
bool mSymmetricEnabled = false;
double mSymmetryPoint = 0;
bool mSymmetryAstride = false;
int mLabelPrecision = 4;
bool mLabelTrimTrailingZeroes = true;
QString mLabelFormat;
// values used to manage number formatting - precision and trailing zeroes
double mLabelNumberScale = 1.0;
QString mLabelNumberSuffix;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsClassificationMethod::MethodProperties )
#endif // QGSCLASSIFICATIONMETHOD_H

View File

@ -0,0 +1,58 @@
/***************************************************************************
qgsclassificationmethodregistry.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 "qgsclassificationmethodregistry.h"
// classification methods
#include "qgsclassificationcustom.h"
#include "qgsclassificationequalinterval.h"
#include "qgsclassificationquantile.h"
#include "qgsclassificationjenks.h"
#include "qgsclassificationstandarddeviation.h"
#include "qgsclassificationprettybreaks.h"
QgsClassificationMethodRegistry::QgsClassificationMethodRegistry()
{
addMethod( new QgsClassificationEqualInterval() );
addMethod( new QgsClassificationQuantile() );
addMethod( new QgsClassificationJenks() );
addMethod( new QgsClassificationStandardDeviation() );
addMethod( new QgsClassificationPrettyBreaks() );
}
bool QgsClassificationMethodRegistry::addMethod( QgsClassificationMethod *method )
{
if ( mMethods.contains( method->id() ) )
return false;
mMethods.insert( method->id(), method );
return true;
}
QgsClassificationMethod *QgsClassificationMethodRegistry::method( const QString &id )
{
QgsClassificationMethod *method = mMethods.value( id, new QgsClassificationCustom() );
return method->clone();
}
QMap<QString, QString> QgsClassificationMethodRegistry::methodNames() const
{
QMap<QString, QString> methods;
for ( const QgsClassificationMethod *method : qgis::as_const( mMethods ) )
methods.insert( method->id(), method->name() );
return methods;
}

View File

@ -0,0 +1,58 @@
/***************************************************************************
qgsclassificationmethodregistry.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 QGSCLASSIFICATIONMETHODREGISTRY_H
#define QGSCLASSIFICATIONMETHODREGISTRY_H
#include <QMap>
#include "qgis_core.h"
#include "qgis_sip.h"
class QgsClassificationMethod;
/**
* \ingroup gui
* This class manages all known classification methods
*
* QgsClassificationMethodRegistry is not usually directly created, but rather accessed through
* QgsApplication::classificationMethodRegistry().
*
* \since QGIS 3.10
*/
class CORE_EXPORT QgsClassificationMethodRegistry
{
public:
QgsClassificationMethodRegistry();
/**
* Adds a method to the registry
* Returns false if a method with same id already exists.
*/
bool addMethod( QgsClassificationMethod *method SIP_TRANSFER );
//! Returns a new instance of the method for the given id
QgsClassificationMethod *method( const QString &id ) SIP_FACTORY;
//! Returns a map <id, name> of all registered methods
QMap<QString, QString> methodNames() const;
private:
QMap<QString, QgsClassificationMethod *> mMethods;
};
#endif // QGSCLASSIFICATIONMETHODREGISTRY_H

View File

@ -0,0 +1,54 @@
/***************************************************************************
qgsclassificationprettybreaks.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 <QObject>
#include "qgsclassificationprettybreaks.h"
#include "qgssymbollayerutils.h"
QgsClassificationPrettyBreaks::QgsClassificationPrettyBreaks()
: QgsClassificationMethod( ValuesNotRequired | SymmetricModeAvailable )
{
}
QString QgsClassificationPrettyBreaks::name() const
{
return QObject::tr( "Pretty Breaks" );
}
QString QgsClassificationPrettyBreaks::id() const
{
return QStringLiteral( "Pretty" );
}
QList<double> QgsClassificationPrettyBreaks::calculateBreaks( double minimum, double maximum, const QList<double> &values, int nclasses )
{
Q_UNUSED( values );
QList<double> breaks = QgsSymbolLayerUtils::prettyBreaks( minimum, maximum, nclasses );
if ( symmetricModeEnabled() )
makeBreaksSymmetric( breaks, symmetryPoint(), symmetryAstride() );
return breaks;
}
QgsClassificationMethod *QgsClassificationPrettyBreaks::clone() const
{
QgsClassificationPrettyBreaks *c = new QgsClassificationPrettyBreaks();
copyBase( c );
return c;
}

View File

@ -0,0 +1,42 @@
/***************************************************************************
qgsclassificationprettybreaks.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 QGSCLASSIFICATIONPRETTYBREAKS_H
#define QGSCLASSIFICATIONPRETTYBREAKS_H
#include "qgis_core.h"
#include "qgsclassificationmethod.h"
/**
* \ingroup core
* QgsClassificationPrettryBreaks is an implementation of QgsClassificationMethod
* for pretty breaks
* \since QGIS 3.10
*/
class CORE_EXPORT QgsClassificationPrettyBreaks : public QgsClassificationMethod
{
public:
QgsClassificationPrettyBreaks();
QString name() const override;
QString id() const override;
QgsClassificationMethod *clone() const override;
private:
QList<double> calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses ) override;
};
#endif // QGSCLASSIFICATIONPRETTYBREAKS_H

View File

@ -0,0 +1,88 @@
/***************************************************************************
qgsclassificationquantile.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 "qgsclassificationquantile.h"
QgsClassificationQuantile::QgsClassificationQuantile()
: QgsClassificationMethod()
{
}
QString QgsClassificationQuantile::name() const
{
return QObject::tr( "Quantile (Equal Count)" );
}
QString QgsClassificationQuantile::id() const
{
return QStringLiteral( "Quantile" );
}
QgsClassificationMethod *QgsClassificationQuantile::clone() const
{
QgsClassificationQuantile *c = new QgsClassificationQuantile();
copyBase( c );
return c;
}
QList<double> QgsClassificationQuantile::calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses )
{
Q_UNUSED( minimum )
Q_UNUSED( maximum )
// q-th quantile of a data set:
// value where q fraction of data is below and (1-q) fraction is above this value
// Xq = (1 - r) * X_NI1 + r * X_NI2
// NI1 = (int) (q * (n+1))
// NI2 = NI1 + 1
// r = q * (n+1) - (int) (q * (n+1))
// (indices of X: 1...n)
// sort the values first
QList<double> _values = values;
std::sort( _values.begin(), _values.end() );
QList<double> breaks;
// If there are no values to process: bail out
if ( _values.isEmpty() )
return QList<double>();
int n = _values.count();
double Xq = n > 0 ? _values[0] : 0.0;
breaks.reserve( nclasses );
for ( int i = 1; i < nclasses; i++ )
{
if ( n > 1 )
{
double q = i / static_cast< double >( nclasses );
double a = q * ( n - 1 );
int aa = static_cast< int >( a );
double r = a - aa;
Xq = ( 1 - r ) * _values[aa] + r * _values[aa + 1];
}
breaks.append( Xq );
}
breaks.append( _values[ n - 1 ] );
return breaks;
}

View File

@ -0,0 +1,43 @@
/***************************************************************************
qgsclassificationquantile.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 QGSCLASSIFICATIONQUANTILE_H
#define QGSCLASSIFICATIONQUANTILE_H
#include "qgis_core.h"
#include "qgsclassificationmethod.h"
/**
* \ingroup core
* QgsClassificationQuantile is an implementation of QgsClassificationMethod
* based on quantiles
* \since QGIS 3.10
*/
class CORE_EXPORT QgsClassificationQuantile : public QgsClassificationMethod
{
public:
QgsClassificationQuantile();
QString name() const override;
QString id() const override;
QgsClassificationMethod *clone() const override;
private:
QList<double> calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses ) override;
};
#endif // QGSCLASSIFICATIONQUANTILE_H

View File

@ -0,0 +1,137 @@
/***************************************************************************
qgsclassificationstandarddeviation.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 <QObject>
#include "qgsclassificationstandarddeviation.h"
#include "qgsgraduatedsymbolrenderer.h"
const QString QgsClassificationStandardDeviation::METHOD_ID = QStringLiteral( "StdDev" );
QgsClassificationStandardDeviation::QgsClassificationStandardDeviation()
: QgsClassificationMethod( SymmetricModeAvailable )
{
}
QString QgsClassificationStandardDeviation::name() const
{
return QObject::tr( "Standard Deviation" );
}
QString QgsClassificationStandardDeviation::id() const
{
return METHOD_ID;
}
QgsClassificationMethod *QgsClassificationStandardDeviation::clone() const
{
QgsClassificationStandardDeviation *c = new QgsClassificationStandardDeviation();
copyBase( c );
c->mStdDev = mStdDev;
return c;
}
QList<double> QgsClassificationStandardDeviation::calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses )
{
// C++ implementation of the standard deviation class interval algorithm
// as implemented in the 'classInt' package available for the R statistical
// prgramming language.
// Returns breaks based on 'prettyBreaks' of the centred and scaled
// values of 'values', and may have a number of classes different from 'classes'.
// If there are no values to process: bail out
if ( values.isEmpty() )
return QList<double>();
double mean = 0.0;
mStdDev = 0.0;
int n = values.count();
for ( int i = 0; i < n; i++ )
{
mean += values[i];
}
mean = mean / static_cast< double >( n );
double sd = 0.0;
for ( int i = 0; i < n; i++ )
{
sd = values[i] - mean;
mStdDev += sd * sd;
}
mStdDev = std::sqrt( mStdDev / n );
// if not symmetric, the symmetry point is the mean
mEffectiveSymmetryPoint = symmetricModeEnabled() ? symmetryPoint() : mean;
QList<double> breaks = QgsSymbolLayerUtils::prettyBreaks( ( minimum - mEffectiveSymmetryPoint ) / mStdDev, ( maximum - mEffectiveSymmetryPoint ) / mStdDev, nclasses );
makeBreaksSymmetric( breaks, 0.0, symmetryAstride() ); //0.0 because breaks where computed on a centered distribution
for ( int i = 0; i < breaks.count(); i++ )
breaks[i] = ( breaks[i] * mStdDev ) + mEffectiveSymmetryPoint;
return breaks;
}
QString QgsClassificationStandardDeviation::labelForRange( const double &lowerValue, const double &upperValue, QgsClassificationMethod::ClassPosition position ) const
{
const QString lowerLabel = valueToLabel( lowerValue );
const QString upperLabel = valueToLabel( upperValue );
switch ( position )
{
case LowerBound:
return "< " + upperLabel + " Std Dev";
case Inner:
{
QString label( labelFormat() );
label.replace( QLatin1String( "%1" ), lowerLabel ).replace( QLatin1String( "%2" ), upperLabel );
return label;
}
case UpperBound:
return ">= " + lowerLabel + " Std Dev";
}
}
QString QgsClassificationStandardDeviation::valueToLabel( const double &value ) const
{
double normalized = ( value - mEffectiveSymmetryPoint ) / mStdDev;
return QString::number( normalized, 'f', 2 ) + " Std Dev";
}
void QgsClassificationStandardDeviation::writeXml( QDomElement &element, const QgsReadWriteContext &context ) const
{
Q_UNUSED( context )
element.setAttribute( QLatin1Literal( "std_dev" ), QString::number( mStdDev, 'f', 16 ) );
element.setAttribute( QLatin1Literal( "effective_symmetry_point" ), QString::number( mEffectiveSymmetryPoint, 'f', 16 ) );
}
void QgsClassificationStandardDeviation::readXml( const QDomElement &element, const QgsReadWriteContext &context )
{
Q_UNUSED( context )
mStdDev = element.attribute( "std_dev", QLatin1Literal( "1.0" ) ).toDouble();
mEffectiveSymmetryPoint = element.attribute( "effective_symmetry_point", QLatin1Literal( "0.0" ) ).toDouble();
}

View File

@ -0,0 +1,52 @@
/***************************************************************************
qgsclassificationstandarddeviation.h
---------------------
begin : September 2019
copyright : (C) 2019 by Denis Rouzaud
email : denis@opengis.ch
***************************************************************************
* *
* 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 QGSCLASSIFICATIONSTANDARDDEVIATION_H
#define QGSCLASSIFICATIONSTANDARDDEVIATION_H
#include "qgis_core.h"
#include "qgsclassificationmethod.h"
/**
* \ingroup core
* QgsClassificationCustom is an implementation of QgsClassificationMethod
* based on standard deviation
* \since QGIS 3.10
*/
class CORE_EXPORT QgsClassificationStandardDeviation : public QgsClassificationMethod
{
public:
QgsClassificationStandardDeviation();
QString name() const override;
QString id() const override;
QgsClassificationMethod *clone() const override;
QString labelForRange( const double &lowerValue, const double &upperValue, ClassPosition position ) const override;
void writeXml( QDomElement &element, const QgsReadWriteContext &context ) const override;
void readXml( const QDomElement &element, const QgsReadWriteContext &context ) override;
static const QString METHOD_ID;
private:
QList<double> calculateBreaks( double minimum, double maximum,
const QList<double> &values, int nclasses ) override;
QString valueToLabel( const double &value ) const override;
double mStdDev = 1.0;
double mEffectiveSymmetryPoint = 0.0;
};
#endif // QGSCLASSIFICATIONSTANDARDDEVIATION_H

View File

@ -41,6 +41,7 @@
#include "qgssymbollayerutils.h"
#include "callouts/qgscalloutsregistry.h"
#include "qgspluginlayerregistry.h"
#include "qgsclassificationmethodregistry.h"
#include "qgsmessagelog.h"
#include "qgsannotationregistry.h"
#include "qgssettings.h"
@ -1920,6 +1921,11 @@ QgsPluginLayerRegistry *QgsApplication::pluginLayerRegistry()
return members()->mPluginLayerRegistry;
}
QgsClassificationMethodRegistry *QgsApplication::classificationMethodRegistry()
{
return members()->mClassificationMethodRegistry;
}
QgsMessageLog *QgsApplication::messageLog()
{
return members()->mMessageLog;
@ -1983,6 +1989,7 @@ QgsApplication::ApplicationMembers::ApplicationMembers()
mProjectStorageRegistry = new QgsProjectStorageRegistry();
mNetworkContentFetcherRegistry = new QgsNetworkContentFetcherRegistry();
mValidityCheckRegistry = new QgsValidityCheckRegistry();
mClassificationMethodRegistry = new QgsClassificationMethodRegistry();
}
QgsApplication::ApplicationMembers::~ApplicationMembers()
@ -2010,6 +2017,7 @@ QgsApplication::ApplicationMembers::~ApplicationMembers()
delete mSymbolLayerRegistry;
delete mTaskManager;
delete mNetworkContentFetcherRegistry;
delete mClassificationMethodRegistry;
}
QgsApplication::ApplicationMembers *QgsApplication::members()

View File

@ -40,6 +40,7 @@ class QgsRasterRendererRegistry;
class QgsGpsConnectionRegistry;
class QgsDataItemProviderRegistry;
class QgsPluginLayerRegistry;
class QgsClassificationMethodRegistry;
class QgsMessageLog;
class QgsProcessingRegistry;
class QgsAnnotationRegistry;
@ -637,6 +638,13 @@ class CORE_EXPORT QgsApplication : public QApplication
*/
static QgsPluginLayerRegistry *pluginLayerRegistry() SIP_KEEPREFERENCE;
/**
* Returns the application's classification methods registry, used in graduated renderer
* \since QGIS 3.10
*/
static QgsClassificationMethodRegistry *classificationMethodRegistry() SIP_KEEPREFERENCE;
/**
* Returns the application's message log.
* \since QGIS 3.0
@ -869,6 +877,7 @@ class CORE_EXPORT QgsApplication : public QApplication
QgsMessageLog *mMessageLog = nullptr;
QgsPaintEffectRegistry *mPaintEffectRegistry = nullptr;
QgsPluginLayerRegistry *mPluginLayerRegistry = nullptr;
QgsClassificationMethodRegistry *mClassificationMethodRegistry = nullptr;
QgsProcessingRegistry *mProcessingRegistry = nullptr;
QgsProjectStorageRegistry *mProjectStorageRegistry = nullptr;
QgsPageSizeRegistry *mPageSizeRegistry = nullptr;

View File

@ -13,6 +13,10 @@
* *
***************************************************************************/
#include <QDomDocument>
#include <QDomElement>
#include <ctime>
#include "qgsgraduatedsymbolrenderer.h"
@ -35,12 +39,11 @@
#include "qgsvectorlayerutils.h"
#include "qgsexpressioncontextutils.h"
#include "qgsstyleentityvisitor.h"
#include <QDomDocument>
#include <QDomElement>
#include <QSettings> // for legend
#include <limits> // for jenks classification
#include <ctime>
#include "qgsclassificationmethod.h"
#include "qgsclassificationequalinterval.h"
#include "qgsapplication.h"
#include "qgsclassificationmethodregistry.h"
#include "qgsclassificationcustom.h"
QgsGraduatedSymbolRenderer::QgsGraduatedSymbolRenderer( const QString &attrName, const QgsRangeList &ranges )
@ -57,6 +60,8 @@ QgsGraduatedSymbolRenderer::QgsGraduatedSymbolRenderer( const QString &attrName,
{
mRanges << range;
}
mClassificationMethod.reset( new QgsClassificationCustom() );
}
QgsGraduatedSymbolRenderer::~QgsGraduatedSymbolRenderer()
@ -232,9 +237,17 @@ bool QgsGraduatedSymbolRenderer::updateRangeUpperValue( int rangeIndex, double v
if ( rangeIndex < 0 || rangeIndex >= mRanges.size() )
return false;
QgsRendererRange &range = mRanges[rangeIndex];
bool isDefaultLabel = range.label() == mLabelFormat.labelForRange( range );
QgsClassificationMethod::ClassPosition pos = QgsClassificationMethod::Inner;
if ( rangeIndex == 0 )
pos = QgsClassificationMethod::LowerBound;
else if ( rangeIndex == mRanges.count() )
pos = QgsClassificationMethod::UpperBound;
bool isDefaultLabel = mClassificationMethod->labelForRange( range, pos ) == range.label();
range.setUpperValue( value );
if ( isDefaultLabel ) range.setLabel( mLabelFormat.labelForRange( range ) );
if ( isDefaultLabel )
range.setLabel( mClassificationMethod->labelForRange( range, pos ) );
return true;
}
@ -242,10 +255,19 @@ bool QgsGraduatedSymbolRenderer::updateRangeLowerValue( int rangeIndex, double v
{
if ( rangeIndex < 0 || rangeIndex >= mRanges.size() )
return false;
QgsRendererRange &range = mRanges[rangeIndex];
bool isDefaultLabel = range.label() == mLabelFormat.labelForRange( range );
QgsClassificationMethod::ClassPosition pos = QgsClassificationMethod::Inner;
if ( rangeIndex == 0 )
pos = QgsClassificationMethod::LowerBound;
else if ( rangeIndex == mRanges.count() )
pos = QgsClassificationMethod::UpperBound;
bool isDefaultLabel = mClassificationMethod->labelForRange( range, pos ) == range.label();
range.setLowerValue( value );
if ( isDefaultLabel ) range.setLabel( mLabelFormat.labelForRange( range ) );
if ( isDefaultLabel )
range.setLabel( mClassificationMethod->labelForRange( range, pos ) );
return true;
}
@ -268,11 +290,8 @@ QString QgsGraduatedSymbolRenderer::dump() const
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 );
r->setClassificationMethod( mClassificationMethod->clone() );
if ( mSourceSymbol )
r->setSourceSymbol( mSourceSymbol->clone() );
@ -282,7 +301,6 @@ QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::clone() const
}
r->setUsingSymbolLevels( usingSymbolLevels() );
r->setDataDefinedSizeLegend( mDataDefinedSizeLegend ? new QgsDataDefinedSizeLegend( *mDataDefinedSizeLegend ) : nullptr );
r->setLabelFormat( labelFormat() );
r->setGraduatedMethod( graduatedMethod() );
copyRendererData( r );
return r;
@ -308,8 +326,7 @@ QgsSymbolList QgsGraduatedSymbolRenderer::symbols( QgsRenderContext &context ) c
Q_UNUSED( context )
QgsSymbolList lst;
lst.reserve( mRanges.count() );
const auto constMRanges = mRanges;
for ( const QgsRendererRange &range : constMRanges )
for ( const QgsRendererRange &range : qgis::as_const( mRanges ) )
{
lst.append( range.symbol() );
}
@ -318,7 +335,7 @@ QgsSymbolList QgsGraduatedSymbolRenderer::symbols( QgsRenderContext &context ) c
bool QgsGraduatedSymbolRenderer::accept( QgsStyleEntityVisitorInterface *visitor ) const
{
for ( const QgsRendererRange &range : mRanges )
for ( const QgsRendererRange &range : qgis::as_const( mRanges ) )
{
QgsStyleSymbolEntity entity( range.symbol() );
if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "%1 - %2" ).arg( range.lowerValue() ).arg( range.upperValue() ), range.label() ) ) )
@ -337,317 +354,15 @@ bool QgsGraduatedSymbolRenderer::accept( QgsStyleEntityVisitorInterface *visitor
void QgsGraduatedSymbolRenderer::makeBreaksSymmetric( QList<double> &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
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::fabs( breaks[0] - symmetryPoint );
double distAboveSymmetricValue = std::fabs( breaks[ breaks.size() - 2 ] - symmetryPoint ) ;
double absMin = std::min( distAboveSymmetricValue, distBelowSymmetricValue );
// make symmetric
for ( int i = 0; i <= breaks.size() - 2; ++i )
{
// part after "absMin" is for doubles rounding issues
if ( std::fabs( breaks.at( i ) - symmetryPoint ) >= ( absMin - std::fabs( breaks[0] - breaks[1] ) / 100. ) )
{
breaks.removeAt( i );
--i;
}
}
// remove symmetry point
if ( astride ) // && breaks.indexOf( symmetryPoint ) != -1) // if symmetryPoint is found
{
breaks.removeAt( breaks.indexOf( symmetryPoint ) );
}
}
return QgsClassificationMethod::makeBreaksSymmetric( breaks, symmetryPoint, astride );
}
QList<double> QgsGraduatedSymbolRenderer::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<double> breaks;
if ( !useSymmetricMode ) // nomal mode
{
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 ( useSymmetricMode ) // symmetric mode
{
double distBelowSymmetricValue = std::abs( minimum - symmetryPoint );
double distAboveSymmetricValue = std::abs( maximum - symmetryPoint ) ;
if ( astride )
{
if ( classes % 2 == 0 ) // we want odd number of classes
++classes;
}
else
{
if ( classes % 2 == 1 ) // we want even number of classes
++classes;
}
double step = 2 * std::min( distBelowSymmetricValue, distAboveSymmetricValue ) / classes;
breaks.reserve( classes );
double 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;
}
static QList<double> _calcQuantileBreaks( QList<double> values, int classes )
{
// q-th quantile of a data set:
// value where q fraction of data is below and (1-q) fraction is above this value
// Xq = (1 - r) * X_NI1 + r * X_NI2
// NI1 = (int) (q * (n+1))
// NI2 = NI1 + 1
// r = q * (n+1) - (int) (q * (n+1))
// (indices of X: 1...n)
// sort the values first
std::sort( values.begin(), values.end() );
QList<double> breaks;
// If there are no values to process: bail out
if ( values.isEmpty() )
return breaks;
int n = values.count();
double Xq = n > 0 ? values[0] : 0.0;
breaks.reserve( classes );
for ( int i = 1; i < classes; i++ )
{
if ( n > 1 )
{
double q = i / static_cast< double >( classes );
double a = q * ( n - 1 );
int aa = static_cast< int >( a );
double r = a - aa;
Xq = ( 1 - r ) * values[aa] + r * values[aa + 1];
}
breaks.append( Xq );
}
breaks.append( values[ n - 1 ] );
return breaks;
}
static QList<double> _calcStdDevBreaks( QList<double> values, int classes, QList<double> &labels, bool useSymmetricMode, double symmetryPoint, bool astride )
{
// C++ implementation of the standard deviation class interval algorithm
// as implemented in the 'classInt' package available for the R statistical
// prgramming language.
// Returns breaks based on 'prettyBreaks' of the centred and scaled
// values of 'values', and may have a number of classes different from 'classes'.
// If there are no values to process: bail out
if ( values.isEmpty() )
return QList<double>();
double mean = 0.0;
double stdDev = 0.0;
int n = values.count();
double minimum = values[0];
double maximum = values[0];
for ( int i = 0; i < n; i++ )
{
mean += values[i];
minimum = std::min( values[i], minimum ); // could use precomputed max and min
maximum = std::max( values[i], maximum ); // but have to go through entire list anyway
}
mean = mean / static_cast< double >( n );
double sd = 0.0;
for ( int i = 0; i < n; i++ )
{
sd = values[i] - mean;
stdDev += sd * sd;
}
stdDev = std::sqrt( stdDev / n );
if ( !useSymmetricMode )
symmetryPoint = mean; // otherwise symmetryPoint = symmetryPoint
QList<double> breaks = QgsSymbolLayerUtils::prettyBreaks( ( minimum - symmetryPoint ) / stdDev, ( maximum - symmetryPoint ) / stdDev, classes );
QgsGraduatedSymbolRenderer::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 ) + symmetryPoint;
}
return breaks;
} // _calcStdDevBreaks
static QList<double> _calcJenksBreaks( QList<double> values, int classes,
double minimum, double maximum,
int maximumSize = 3000 )
{
// Jenks Optimal (Natural Breaks) algorithm
// Based on the Jenks algorithm from the 'classInt' package available for
// the R statistical prgramming language, and from Python code from here:
// http://danieljlewis.org/2010/06/07/jenks-natural-breaks-algorithm-in-python/
// and is based on a JAVA and Fortran code available here:
// https://stat.ethz.ch/pipermail/r-sig-geo/2006-March/000811.html
// Returns class breaks such that classes are internally homogeneous while
// assuring heterogeneity among classes.
if ( values.isEmpty() )
return QList<double>();
if ( classes <= 1 )
{
return QList<double>() << maximum;
}
if ( classes >= values.size() )
{
return values;
}
QVector<double> sample;
// if we have lots of values, we need to take a random sample
if ( values.size() > maximumSize )
{
// for now, sample at least maximumSize values or a 10% sample, whichever
// is larger. This will produce a more representative sample for very large
// layers, but could end up being computationally intensive...
sample.resize( std::max( maximumSize, values.size() / 10 ) );
QgsDebugMsg( QStringLiteral( "natural breaks (jenks) sample size: %1" ).arg( sample.size() ) );
QgsDebugMsg( QStringLiteral( "values:%1" ).arg( values.size() ) );
sample[ 0 ] = minimum;
sample[ 1 ] = maximum;
for ( int i = 2; i < sample.size(); i++ )
{
// pick a random integer from 0 to n
double r = qrand();
int j = std::floor( r / RAND_MAX * ( values.size() - 1 ) );
sample[ i ] = values[ j ];
}
}
else
{
sample = values.toVector();
}
int n = sample.size();
// sort the sample values
std::sort( sample.begin(), sample.end() );
QVector< QVector<int> > matrixOne( n + 1 );
QVector< QVector<double> > matrixTwo( n + 1 );
for ( int i = 0; i <= n; i++ )
{
matrixOne[i].resize( classes + 1 );
matrixTwo[i].resize( classes + 1 );
}
for ( int i = 1; i <= classes; i++ )
{
matrixOne[0][i] = 1;
matrixOne[1][i] = 1;
matrixTwo[0][i] = 0.0;
for ( int j = 2; j <= n; j++ )
{
matrixTwo[j][i] = std::numeric_limits<double>::max();
}
}
for ( int l = 2; l <= n; l++ )
{
double s1 = 0.0;
double s2 = 0.0;
int w = 0;
double v = 0.0;
for ( int m = 1; m <= l; m++ )
{
int i3 = l - m + 1;
double val = sample[ i3 - 1 ];
s2 += val * val;
s1 += val;
w++;
v = s2 - ( s1 * s1 ) / static_cast< double >( w );
int i4 = i3 - 1;
if ( i4 != 0 )
{
for ( int j = 2; j <= classes; j++ )
{
if ( matrixTwo[l][j] >= v + matrixTwo[i4][j - 1] )
{
matrixOne[l][j] = i4;
matrixTwo[l][j] = v + matrixTwo[i4][j - 1];
}
}
}
}
matrixOne[l][1] = 1;
matrixTwo[l][1] = v;
}
QVector<double> breaks( classes );
breaks[classes - 1] = sample[n - 1];
for ( int j = classes, k = n; j >= 2; j-- )
{
int id = matrixOne[k][j] - 1;
breaks[j - 2] = sample[id];
k = matrixOne[k][j] - 1;
}
return breaks.toList();
} //_calcJenksBreaks
static QStringList _breaksAsStrings( const QList<double> &breaks ) // get QStringList from QList<double> without maxi break (min is not in)
{
QStringList breaksAsStrings;
for ( int i = 0; i < breaks.count() - 1; i++ )
{
breaksAsStrings << QString::number( breaks.at( i ), 'f', 2 );
}
return breaksAsStrings;
QgsClassificationEqualInterval method;
method.setSymmetricMode( useSymmetricMode, symmetryPoint, astride );
QList<QgsClassificationRange> _classes = method.classes( minimum, maximum, classes );
return QgsClassificationMethod::listToValues( _classes );
}
QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::createRenderer(
@ -664,17 +379,26 @@ QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::createRenderer(
bool astride
)
{
Q_UNUSED( listForCboPrettyBreaks )
QgsRangeList ranges;
QgsGraduatedSymbolRenderer *r = new QgsGraduatedSymbolRenderer( attrName, ranges );
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, useSymmetricMode, symmetryPoint, astride );
QString methodId = methodIdFromMode( mode );
QgsClassificationMethod *method = QgsApplication::classificationMethodRegistry()->method( methodId );
if ( method )
{
method->setSymmetricMode( useSymmetricMode, symmetryPoint, astride );
method->setLabelFormat( labelFormat.format() );
method->setLabelTrimTrailingZeroes( labelFormat.trimTrailingZeroes() );
method->setLabelPrecision( labelFormat.precision() );
}
r->setClassificationMethod( method );
r->updateClasses( vlayer, classes );
return r;
}
@ -683,126 +407,40 @@ void QgsGraduatedSymbolRenderer::updateClasses( QgsVectorLayer *vlayer, Mode mod
{
if ( mAttrName.isEmpty() )
return;
setMode( mode );
setSymmetryPoint( symmetryPoint );
setUseSymmetricMode( useSymmetricMode );
setAstride( astride );
// Custom classes are not recalculated
if ( mode == Custom )
QString methodId = methodIdFromMode( mode );
QgsClassificationMethod *method = QgsApplication::classificationMethodRegistry()->method( methodId );
method->setSymmetricMode( useSymmetricMode, symmetryPoint, astride );
setClassificationMethod( method );
updateClasses( vlayer, nclasses );
}
void QgsGraduatedSymbolRenderer::updateClasses( const QgsVectorLayer *vl, int nclasses )
{
if ( mClassificationMethod->id() == QgsClassificationCustom::METHOD_ID )
return;
if ( nclasses < 1 )
nclasses = 1;
QList<QgsClassificationRange> classes = mClassificationMethod->classes( vl, mAttrName, nclasses );
QList<double> values;
bool valuesLoaded = false;
double minimum;
double maximum;
int attrNum = vlayer->fields().lookupField( mAttrName );
bool ok;
if ( attrNum == -1 )
{
values = QgsVectorLayerUtils::getDoubleValues( vlayer, mAttrName, ok );
if ( !ok || values.isEmpty() )
return;
auto result = std::minmax_element( values.begin(), values.end() );
minimum = *result.first;
maximum = *result.second;
valuesLoaded = true;
}
else
{
minimum = vlayer->minimumValue( attrNum ).toDouble();
maximum = vlayer->maximumValue( attrNum ).toDouble();
}
QgsDebugMsg( QStringLiteral( "min %1 // max %2" ).arg( minimum ).arg( maximum ) );
QList<double> breaks;
QList<double> labels;
switch ( mode )
{
case EqualInterval:
{
breaks = QgsGraduatedSymbolRenderer::calcEqualIntervalBreaks( minimum, maximum, nclasses, mUseSymmetricMode, symmetryPoint, astride );
break;
}
case Pretty:
{
breaks = QgsSymbolLayerUtils::prettyBreaks( minimum, maximum, nclasses );
setListForCboPrettyBreaks( _breaksAsStrings( breaks ) );
if ( useSymmetricMode )
QgsGraduatedSymbolRenderer::makeBreaksSymmetric( breaks, symmetryPoint, astride );
break;
}
case Quantile:
case Jenks:
case StdDev:
{
// get values from layer
if ( !valuesLoaded )
{
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, mUseSymmetricMode, symmetryPoint, astride );
break;
}
case Custom:
Q_ASSERT( false );
break;
}
double lower, upper = minimum;
QString label;
deleteAllClasses();
// "breaks" list contains all values at class breaks plus maximum as last break
int i = 0;
for ( QList<double>::iterator it = breaks.begin(); it != breaks.end(); ++it, ++i )
for ( QList<QgsClassificationRange>::iterator it = classes.begin(); it != classes.end(); ++it )
{
lower = upper; // upper border from last interval
upper = *it;
// Label - either StdDev label or default label for a range
if ( mode == StdDev )
{
if ( i == 0 )
{
label = "< " + QString::number( labels[i], 'f', 2 ) + " Std Dev";
}
else if ( i == labels.count() - 1 )
{
label = ">= " + QString::number( labels[i - 1], 'f', 2 ) + " Std Dev";
}
else
{
label = QString::number( labels[i - 1], 'f', 2 ) + " Std Dev" + " - " + QString::number( labels[i], 'f', 2 ) + " Std Dev";
}
}
else
{
label = mLabelFormat.labelForRange( lower, upper );
}
QgsSymbol *newSymbol = mSourceSymbol ? mSourceSymbol->clone() : QgsSymbol::defaultSymbol( vlayer->geometryType() );
addClass( QgsRendererRange( lower, upper, newSymbol, label ) );
QgsSymbol *newSymbol = mSourceSymbol ? mSourceSymbol->clone() : QgsSymbol::defaultSymbol( vl->geometryType() );
addClass( QgsRendererRange( *it, newSymbol ) );
}
updateColorRamp( nullptr );
}
const QgsRendererRangeLabelFormat &QgsGraduatedSymbolRenderer::labelFormat() const
{
// this is leaking but will be removed in QGIS 4
Q_NOWARN_DEPRECATED_PUSH
QgsRendererRangeLabelFormat *format = new QgsRendererRangeLabelFormat( mClassificationMethod->labelFormat(), mClassificationMethod->labelPrecision(), mClassificationMethod->labelTrimTrailingZeroes() );
Q_NOWARN_DEPRECATED_POP
return *format;
}
QgsFeatureRenderer *QgsGraduatedSymbolRenderer::create( QDomElement &element, const QgsReadWriteContext &context )
{
@ -873,37 +511,63 @@ QgsFeatureRenderer *QgsGraduatedSymbolRenderer::create( QDomElement &element, co
}
// try to load mode
QDomElement modeElem = element.firstChildElement( QStringLiteral( "mode" ) );
QDomElement modeElem = element.firstChildElement( QStringLiteral( "mode" ) ); // old format, backward compatibility
QDomElement methodElem = element.firstChildElement( QStringLiteral( "classificationMethod" ) );
QgsClassificationMethod *method = nullptr;
// TODO QGIS 4 Remove
// backward compatibility for QGIS project < 3.10
if ( !modeElem.isNull() )
{
QString modeString = modeElem.attribute( QStringLiteral( "name" ) );
QString methodId;
// the strings saved in the project does not match with the old Mode enum
if ( modeString == QLatin1String( "equal" ) )
r->setMode( EqualInterval );
methodId = QLatin1String( "EqualInterval" );
else if ( modeString == QLatin1String( "quantile" ) )
r->setMode( Quantile );
methodId = QLatin1String( "Quantile" );
else if ( modeString == QLatin1String( "jenks" ) )
r->setMode( Jenks );
methodId = QLatin1String( "Jenks" );
else if ( modeString == QLatin1String( "stddev" ) )
r->setMode( StdDev );
methodId = QLatin1String( "StdDev" );
else if ( modeString == QLatin1String( "pretty" ) )
r->setMode( Pretty );
}
methodId = QLatin1String( "Pretty" );
// symmetric mode
QDomElement symmetricModeElem = element.firstChildElement( QStringLiteral( "symmetricMode" ) );
if ( !symmetricModeElem.isNull() )
method = QgsApplication::classificationMethodRegistry()->method( methodId );
// symmetric mode
QDomElement symmetricModeElem = element.firstChildElement( QStringLiteral( "symmetricMode" ) );
if ( !symmetricModeElem.isNull() )
{
// symmetry
QString symmetricEnabled = symmetricModeElem.attribute( QStringLiteral( "enabled" ) );
QString symmetricPointString = symmetricModeElem.attribute( QStringLiteral( "symmetryPoint" ) );
QString astrideEnabled = symmetricModeElem.attribute( QStringLiteral( "astride" ) );
method->setSymmetricMode( symmetricEnabled == QLatin1String( "true" ), symmetricPointString.toDouble(), astrideEnabled == QLatin1String( "true" ) );
}
QDomElement labelFormatElem = element.firstChildElement( QStringLiteral( "labelformat" ) );
if ( !labelFormatElem.isNull() )
{
// label format
QString format = labelFormatElem.attribute( QStringLiteral( "format" ), "%1" + QStringLiteral( " - " ) + "%2" );
int precision = labelFormatElem.attribute( QStringLiteral( "decimalplaces" ), QStringLiteral( "4" ) ).toInt();
bool trimTrailingZeroes = labelFormatElem.attribute( QStringLiteral( "trimtrailingzeroes" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
method->setLabelFormat( format );
method->setLabelPrecision( precision );
method->setLabelTrimTrailingZeroes( trimTrailingZeroes );
}
// End of backward compatibility
}
else
{
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 );
// QGIS project 3.10+
method = QgsClassificationMethod::create( methodElem, context );
}
// apply the method
r->setClassificationMethod( method );
QDomElement rotationElem = element.firstChildElement( QStringLiteral( "rotation" ) );
if ( !rotationElem.isNull() && !rotationElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
{
@ -933,20 +597,12 @@ QgsFeatureRenderer *QgsGraduatedSymbolRenderer::create( QDomElement &element, co
}
}
QDomElement labelFormatElem = element.firstChildElement( QStringLiteral( "labelformat" ) );
if ( ! labelFormatElem.isNull() )
{
QgsRendererRangeLabelFormat labelFormat;
labelFormat.setFromDomElement( labelFormatElem );
r->setLabelFormat( labelFormat );
}
QDomElement ddsLegendSizeElem = element.firstChildElement( QStringLiteral( "data-defined-size-legend" ) );
if ( !ddsLegendSizeElem.isNull() )
{
r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readXml( ddsLegendSizeElem, context ) );
}
// TODO: symbol levels
// TODO: symbol levels
return r;
}
@ -1002,54 +658,9 @@ QDomElement QgsGraduatedSymbolRenderer::save( QDomDocument &doc, const QgsReadWr
rendererElem.appendChild( colorRampElem );
}
// save mode
QString modeString;
switch ( mMode )
{
case EqualInterval:
modeString = QStringLiteral( "equal" );
break;
case Quantile:
modeString = QStringLiteral( "quantile" );
break;
case Jenks:
modeString = QStringLiteral( "jenks" );
break;
case StdDev:
modeString = QStringLiteral( "stddev" );
break;
case Pretty:
modeString = QStringLiteral( "pretty" );
break;
case Custom:
break;
}
if ( !modeString.isEmpty() )
{
QDomElement modeElem = doc.createElement( QStringLiteral( "mode" ) );
modeElem.setAttribute( QStringLiteral( "name" ), modeString );
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( '/' );
}
if ( mListForCboPrettyBreaks.size() > 0 ) //make sure we can go at size-1
breaks.append( mListForCboPrettyBreaks.at( mListForCboPrettyBreaks.size() - 1 ) ); //add the last break
symmetricModeElem.setAttribute( QStringLiteral( "valueForCboPrettyBreaks" ), breaks );
}
rendererElem.appendChild( symmetricModeElem );
// save classification method
QDomElement classificationMethodElem = mClassificationMethod->save( doc, context );
rendererElem.appendChild( classificationMethodElem );
QDomElement rotationElem = doc.createElement( QStringLiteral( "rotation" ) );
rendererElem.appendChild( rotationElem );
@ -1057,10 +668,6 @@ QDomElement QgsGraduatedSymbolRenderer::save( QDomDocument &doc, const QgsReadWr
QDomElement sizeScaleElem = doc.createElement( QStringLiteral( "sizescale" ) );
rendererElem.appendChild( sizeScaleElem );
QDomElement labelFormatElem = doc.createElement( QStringLiteral( "labelformat" ) );
mLabelFormat.saveToDomElement( labelFormatElem );
rendererElem.appendChild( labelFormatElem );
if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
mPaintEffect->saveProperties( doc, rendererElem );
@ -1094,6 +701,43 @@ QgsLegendSymbolList QgsGraduatedSymbolRenderer::baseLegendSymbolItems() const
return lst;
}
Q_NOWARN_DEPRECATED_PUSH
QString QgsGraduatedSymbolRenderer::methodIdFromMode( QgsGraduatedSymbolRenderer::Mode mode )
{
switch ( mode )
{
case EqualInterval:
return QLatin1String( "EqualInterval" );
case Quantile:
return QLatin1String( "Quantile" );
case Jenks:
return QLatin1String( "Jenks" );
case StdDev:
return QLatin1String( "StdDev" );
case Pretty:
return QLatin1String( "Pretty" );
case Custom:
return QString();
}
}
QgsGraduatedSymbolRenderer::Mode QgsGraduatedSymbolRenderer::modeFromMethodId( const QString &methodId )
{
if ( methodId == QLatin1String( "EqualInterval" ) )
return EqualInterval;
if ( methodId == QLatin1String( "Quantile" ) )
return Quantile;
if ( methodId == QLatin1String( "Jenks" ) )
return Jenks;
if ( methodId == QLatin1String( "StdDev" ) )
return StdDev;
if ( methodId == QLatin1String( "Pretty" ) )
return Pretty;
else
return Custom;
}
Q_NOWARN_DEPRECATED_POP
QgsLegendSymbolList QgsGraduatedSymbolRenderer::legendSymbolItems() const
{
if ( mDataDefinedSizeLegend && mSourceSymbol && mSourceSymbol->type() == QgsSymbol::Marker )
@ -1318,7 +962,7 @@ void QgsGraduatedSymbolRenderer::addClass( QgsSymbol *symbol )
void QgsGraduatedSymbolRenderer::addClass( double lower, double upper )
{
QgsSymbol *newSymbol = mSourceSymbol->clone();
QString label = mLabelFormat.labelForRange( lower, upper );
QString label = mClassificationMethod->labelForRange( lower, upper );
mRanges.append( QgsRendererRange( lower, upper, newSymbol, label ) );
}
@ -1333,13 +977,14 @@ void QgsGraduatedSymbolRenderer::addBreak( double breakValue, bool updateSymbols
QgsRendererRange newRange = QgsRendererRange();
newRange.setLowerValue( breakValue );
newRange.setUpperValue( range.upperValue() );
newRange.setLabel( mLabelFormat.labelForRange( newRange ) );
newRange.setLabel( mClassificationMethod->labelForRange( newRange ) );
newRange.setSymbol( mSourceSymbol->clone() );
//update old range
bool isDefaultLabel = range.label() == mLabelFormat.labelForRange( range );
bool isDefaultLabel = range.label() == mClassificationMethod->labelForRange( range );
range.setUpperValue( breakValue );
if ( isDefaultLabel ) range.setLabel( mLabelFormat.labelForRange( range.lowerValue(), breakValue ) );
if ( isDefaultLabel )
range.setLabel( mClassificationMethod->labelForRange( range.lowerValue(), breakValue ) );
it.setValue( range );
it.insert( newRange );
@ -1378,14 +1023,27 @@ void QgsGraduatedSymbolRenderer::deleteAllClasses()
void QgsGraduatedSymbolRenderer::setLabelFormat( const QgsRendererRangeLabelFormat &labelFormat, bool updateRanges )
{
if ( updateRanges && labelFormat != mLabelFormat )
mClassificationMethod->setLabelFormat( labelFormat.format() );
mClassificationMethod->setLabelPrecision( labelFormat.precision() );
mClassificationMethod->setLabelTrimTrailingZeroes( labelFormat.trimTrailingZeroes() );
if ( updateRanges )
{
for ( QgsRangeList::iterator it = mRanges.begin(); it != mRanges.end(); ++it )
{
it->setLabel( labelFormat.labelForRange( *it ) );
}
updateRangeLabels();
}
}
void QgsGraduatedSymbolRenderer::updateRangeLabels()
{
for ( int i = 0; i < mRanges.count(); i++ )
{
QgsClassificationMethod::ClassPosition pos = QgsClassificationMethod::Inner;
if ( i == 0 )
pos = QgsClassificationMethod::LowerBound;
else if ( i == mRanges.count() - 1 )
pos = QgsClassificationMethod::UpperBound;
mRanges[i].setLabel( mClassificationMethod->labelForRange( mRanges[i], pos ) );
}
mLabelFormat = labelFormat;
}
@ -1415,8 +1073,9 @@ void QgsGraduatedSymbolRenderer::calculateLabelPrecision( bool updateRanges )
ndp--;
nextDpMinRange *= 10.0;
}
mLabelFormat.setPrecision( ndp );
if ( updateRanges ) setLabelFormat( mLabelFormat, true );
mClassificationMethod->setLabelPrecision( ndp );
if ( updateRanges )
updateRangeLabels();
}
void QgsGraduatedSymbolRenderer::moveClass( int from, int to )
@ -1520,6 +1179,38 @@ void QgsGraduatedSymbolRenderer::sortByLabel( Qt::SortOrder order )
}
}
QgsClassificationMethod *QgsGraduatedSymbolRenderer::classificationMethod() const
{
return mClassificationMethod.get();
}
void QgsGraduatedSymbolRenderer::setClassificationMethod( QgsClassificationMethod *method )
{
mClassificationMethod.reset( method );
}
void QgsGraduatedSymbolRenderer::setMode( QgsGraduatedSymbolRenderer::Mode mode )
{
QString methodId = methodIdFromMode( mode );
QgsClassificationMethod *method = QgsApplication::classificationMethodRegistry()->method( methodId );
setClassificationMethod( method );
}
void QgsGraduatedSymbolRenderer::setUseSymmetricMode( bool useSymmetricMode ) SIP_DEPRECATED
{
mClassificationMethod->setSymmetricMode( useSymmetricMode, mClassificationMethod->symmetryPoint(), mClassificationMethod->symmetryAstride() );
}
void QgsGraduatedSymbolRenderer::setSymmetryPoint( double symmetryPoint ) SIP_DEPRECATED
{
mClassificationMethod->setSymmetricMode( mClassificationMethod->symmetricModeEnabled(), symmetryPoint, mClassificationMethod->symmetryAstride() );
}
void QgsGraduatedSymbolRenderer::setAstride( bool astride ) SIP_DEPRECATED
{
mClassificationMethod->setSymmetricMode( mClassificationMethod->symmetricModeEnabled(), mClassificationMethod->symmetryPoint(), astride );
}
QgsGraduatedSymbolRenderer *QgsGraduatedSymbolRenderer::convertFromRenderer( const QgsFeatureRenderer *renderer )
{
QgsGraduatedSymbolRenderer *r = nullptr;
@ -1581,3 +1272,5 @@ const char *QgsGraduatedSymbolRenderer::graduatedMethodStr( GraduatedMethod meth
}
return "";
}

View File

@ -20,11 +20,11 @@
#include "qgis.h"
#include "qgssymbol.h"
#include "qgsrenderer.h"
#include "qgsrendererrange.h"
#include "qgsexpression.h"
#include "qgsdatadefinedsizelegend.h"
#include "qgsrendererrange.h"
#include "qgsclassificationmethod.h"
#include <QRegExp>
class QgsVectorLayer;
class QgsColorRamp;
@ -105,6 +105,23 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
void sortByValue( Qt::SortOrder order = Qt::AscendingOrder );
void sortByLabel( Qt::SortOrder order = Qt::AscendingOrder );
/**
* Returns the classification method
* \since QGIS 3.10
*/
QgsClassificationMethod *classificationMethod() const;
/**
* Defines the classification method
* This will take ownership of the method
* \since QGIS 3.10
*/
void setClassificationMethod( QgsClassificationMethod *method SIP_TRANSFER );
/**
* Classification mode
* \deprecated since QGIS 3.10 use QgsClassificationMethod::MethodId instead
*/
enum Mode
{
EqualInterval,
@ -114,59 +131,58 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
Pretty,
Custom
};
// TODO QGIS 4: remove
// this could not be tagged with Q_DECL_DEPRECATED due to Doxygen warning
// migth be fixed in newer Doxygen (does not on 1.8.13, might be ok on 1.8.16)
Mode mode() const { return mMode; }
void setMode( Mode mode ) { mMode = mode; }
//! \deprecated since QGIS 3.10 use classficationMethod instead
Q_DECL_DEPRECATED Mode mode() const SIP_DEPRECATED { return modeFromMethodId( mClassificationMethod->id() ); }
//! \deprecated since QGIS 3.10 use classficationMethod instead
Q_DECL_DEPRECATED void setMode( Mode mode ) SIP_DEPRECATED;
/**
* Returns if we want to classify symmetric around a given value
* \since QGIS 3.4
* \deprecated since QGIS 3.10 use classficationMethod instead
*/
bool useSymmetricMode() const { return mUseSymmetricMode; }
Q_DECL_DEPRECATED bool useSymmetricMode() const SIP_DEPRECATED { return mClassificationMethod->symmetricModeEnabled(); }
/**
* Set if we want to classify symmetric around a given value
* \since QGIS 3.4
* \deprecated since QGIS 3.10 use classficationMethod instead
*/
void setUseSymmetricMode( bool useSymmetricMode ) { mUseSymmetricMode = useSymmetricMode; }
Q_DECL_DEPRECATED void setUseSymmetricMode( bool useSymmetricMode ) SIP_DEPRECATED;
/**
* Returns the pivot value for symmetric classification
* \since QGIS 3.4
* \deprecated since QGIS 3.10 use classficationMethod instead
*/
double symmetryPoint() const { return mSymmetryPoint; }
Q_DECL_DEPRECATED double symmetryPoint() const SIP_DEPRECATED { return mClassificationMethod->symmetryPoint(); }
/**
* Set the pivot point
* \since QGIS 3.4
* \deprecated since QGIS 3.10 use classficationMethod instead
*/
void setSymmetryPoint( double symmetryPoint ) { mSymmetryPoint = symmetryPoint; }
Q_DECL_DEPRECATED void setSymmetryPoint( double symmetryPoint ) SIP_DEPRECATED;
/**
* Returns the list of breaks used in the prettybreaks mode. Needed to recover this list in saved configuration, or when property window in closed and reopened
* \note Not available in Python bindings, not stable API
* \since QGIS 3.4
*/
QStringList listForCboPrettyBreaks() const { return mListForCboPrettyBreaks; } SIP_SKIP
/**
* Set the list of breaks used in the prettybreaks mode, which is needed to recover this list in saved configuration, or when property window is closed and reopened
* \note Not available in Python bindings, not stable API
* \since QGIS 3.4
*/
void setListForCboPrettyBreaks( const QStringList &listForCboPrettyBreaks ) { mListForCboPrettyBreaks = listForCboPrettyBreaks; } SIP_SKIP
/**
* Returns if we want to have a central class astride the pivot value
* \since QGIS 3.4
* \deprecated since QGIS 3.10 use classficationMethod instead
*/
bool astride() const { return mAstride; }
Q_DECL_DEPRECATED bool astride() const SIP_DEPRECATED { return mClassificationMethod->symmetryAstride(); }
/**
* Set if we want a central class astride the pivot value
* \since QGIS 3.4
* \deprecated since QGIS 3.10 use classficationMethod instead
*/
void setAstride( bool astride ) { mAstride = astride; }
Q_DECL_DEPRECATED void setAstride( bool astride ) SIP_DEPRECATED;
/**
* Remove the breaks that are above the existing opposite sign classes to keep colors symmetrically balanced around symmetryPoint
@ -175,8 +191,9 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
* \param symmetryPoint The point around which we want a symmetry
* \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.4
* \deprecated since QGIS 3.10, use QgsClassificationMethod::makeBreaksSymmetric instead
*/
static void makeBreaksSymmetric( QList<double> &breaks SIP_INOUT, double symmetryPoint, bool astride );
Q_DECL_DEPRECATED static void makeBreaksSymmetric( QList<double> &breaks SIP_INOUT, double symmetryPoint, bool astride ) SIP_DEPRECATED;
/**
* Compute the equal interval classification
@ -186,8 +203,9 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
* \param useSymmetricMode A bool indicating if we want to have classes and hence colors ramp symmetric around a value
* \param symmetryPoint The point around which we want a symmetry
* \param astride A bool indicating if the symmetry is made astride the symmetryPoint or not ( [-1,1] vs. [-1,0][0,1] )
* \deprecated since QGIS 3.10 use QgsClassificationEqualInterval class instead
*/
static QList<double> calcEqualIntervalBreaks( double minimum, double maximum, int classes, bool useSymmetricMode, double symmetryPoint, bool astride );
Q_DECL_DEPRECATED static QList<double> calcEqualIntervalBreaks( double minimum, double maximum, int classes, bool useSymmetricMode, double symmetryPoint, bool astride );
/**
* Recalculate classes for a layer
@ -198,22 +216,32 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
* \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 2.6 (three first arguments) and 3.4 (three last arguments)
* \deprecated since QGIS 3.10
*/
void updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses, bool useSymmetricMode = false, double symmetryPoint = 0.0, bool astride = false );
Q_DECL_DEPRECATED void updateClasses( QgsVectorLayer *vlayer, Mode mode, int nclasses, bool useSymmetricMode = false, double symmetryPoint = 0.0, bool astride = false ) SIP_DEPRECATED;
/**
* Recalculate classes for a layer
* \param vl The layer being rendered (from which data values are calculated)
* \param nclasses the number of classes
*/
void updateClasses( const QgsVectorLayer *vl, int nclasses );
/**
* Returns the label format used to generate default classification labels
* \since QGIS 2.6
* \deprecated since QGIS 3.10 use classificationMethod() and QgsClassificationMethod::setLabelFormat instead
*/
const QgsRendererRangeLabelFormat &labelFormat() const { return mLabelFormat; }
Q_DECL_DEPRECATED const QgsRendererRangeLabelFormat &labelFormat() const;
/**
* 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.
* \since QGIS 2.6
* \deprecated since QGIS 3.10 use classificationMethod() and QgsClassificationMethod::setLabelFormat instead
*/
void setLabelFormat( const QgsRendererRangeLabelFormat &labelFormat, bool updateRanges = false );
Q_DECL_DEPRECATED void setLabelFormat( const QgsRendererRangeLabelFormat &labelFormat, bool updateRanges = false ) SIP_DEPRECATED;
/**
* Reset the label decimal places to a numberbased on the minimum class interval
@ -222,6 +250,8 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
*/
void calculateLabelPrecision( bool updateRanges = true );
Q_NOWARN_DEPRECATED_PUSH;
/**
* Creates a new graduated renderer.
* \param vlayer vector layer
@ -236,8 +266,9 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
* \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] )
* \returns new QgsGraduatedSymbolRenderer object
* \deprecated since QGIS 3.10
*/
static QgsGraduatedSymbolRenderer *createRenderer( QgsVectorLayer *vlayer,
static Q_DECL_DEPRECATED QgsGraduatedSymbolRenderer *createRenderer( QgsVectorLayer *vlayer,
const QString &attrName,
int classes,
Mode mode,
@ -247,7 +278,8 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
bool useSymmetricMode = false,
double symmetryPoint = 0.0,
QStringList listForCboPrettyBreaks = QStringList(),
bool astride = false );
bool astride = false ) SIP_DEPRECATED;
Q_NOWARN_DEPRECATED_POP;
//! create renderer from XML element
static QgsFeatureRenderer *create( QDomElement &element, const QgsReadWriteContext &context ) SIP_FACTORY;
@ -370,13 +402,17 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
*/
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
/**
* Updates the labels of the ranges
* \since QGIS 3.10
*/
void updateRangeLabels();
protected:
QString mAttrName;
QgsRangeList mRanges;
Mode mMode = Custom;
std::unique_ptr<QgsSymbol> mSourceSymbol;
std::unique_ptr<QgsColorRamp> mSourceColorRamp;
QgsRendererRangeLabelFormat mLabelFormat;
std::unique_ptr<QgsExpression> mExpression;
GraduatedMethod mGraduatedMethod = GraduatedColor;
@ -400,10 +436,7 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
//! \note not available in Python bindings
static const char *graduatedMethodStr( GraduatedMethod method ) SIP_SKIP;
bool mUseSymmetricMode = false;
double mSymmetryPoint = 0.0;
QStringList mListForCboPrettyBreaks = QStringList();
bool mAstride = false;
std::shared_ptr<QgsClassificationMethod> mClassificationMethod;
private:
@ -415,11 +448,16 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer
//! Returns list of legend symbol items from individual ranges
QgsLegendSymbolList baseLegendSymbolItems() const;
// TODO QGIS 4: remove
Q_NOWARN_DEPRECATED_PUSH
static QString methodIdFromMode( QgsGraduatedSymbolRenderer::Mode mode );
static QgsGraduatedSymbolRenderer::Mode modeFromMethodId( const QString &methodId );
Q_NOWARN_DEPRECATED_POP
#ifdef SIP_RUN
QgsGraduatedSymbolRenderer( const QgsGraduatedSymbolRenderer & );
QgsGraduatedSymbolRenderer &operator=( const QgsGraduatedSymbolRenderer & );
#endif
};
#endif // QGSGRADUATEDSYMBOLRENDERER_H

View File

@ -14,6 +14,17 @@
***************************************************************************/
#include "qgsrendererrange.h"
#include "qgsclassificationmethod.h"
QgsRendererRange::QgsRendererRange( const QgsClassificationRange &range, QgsSymbol *symbol, bool render )
: mLowerValue( range.lowerBound() )
, mUpperValue( range.upperBound() )
, mSymbol( symbol )
, mLabel( range.label() )
, mRender( render )
{
}
QgsRendererRange::QgsRendererRange( double lowerValue, double upperValue, QgsSymbol *symbol, const QString &label, bool render )
: mLowerValue( lowerValue )

View File

@ -26,6 +26,7 @@ class QDomDocument;
class QDomElement;
class QgsSymbol;
class QgsClassificationRange;
/**
@ -40,6 +41,14 @@ class CORE_EXPORT QgsRendererRange
* Constructor for QgsRendererRange.
*/
QgsRendererRange() = default;
/**
* Creates a renderer symbol range
* \param range The classification range
* \param symbol The symbol for this renderer range
* \param render If true, it will be renderered
*/
QgsRendererRange( const QgsClassificationRange &range, QgsSymbol *symbol SIP_TRANSFER, bool render = true );
QgsRendererRange( double lowerValue, double upperValue, QgsSymbol *symbol SIP_TRANSFER, const QString &label, bool render = true );
QgsRendererRange( const QgsRendererRange &range );
@ -88,12 +97,14 @@ class CORE_EXPORT QgsRendererRange
typedef QList<QgsRendererRange> QgsRangeList;
/**
* \ingroup core
* \class QgsRendererRangeLabelFormat
* \since QGIS 2.6
* \deprecated since QGIS 3.10, use QgsClassificationMethod instead
*/
class CORE_EXPORT QgsRendererRangeLabelFormat
class Q_DECL_DEPRECATED CORE_EXPORT QgsRendererRangeLabelFormat SIP_DEPRECATED
{
public:
QgsRendererRangeLabelFormat();

View File

@ -1019,6 +1019,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/core/scalebar
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/core/classification
${CMAKE_SOURCE_DIR}/src/core/effects
${CMAKE_SOURCE_DIR}/src/core/metadata
${CMAKE_SOURCE_DIR}/src/core/expression

View File

@ -12,6 +12,17 @@
* (at your option) any later version. *
* *
***************************************************************************/
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QPen>
#include <QPainter>
#include <QClipboard>
#include <QCompleter>
#include "qgsgraduatedsymbolrendererwidget.h"
#include "qgspanelwidget.h"
@ -23,26 +34,19 @@
#include "qgscolorrampbutton.h"
#include "qgsstyle.h"
#include "qgsexpressioncontextutils.h"
#include "qgsvectorlayer.h"
#include "qgssymbolselectordialog.h"
#include "qgsexpressionbuilderdialog.h"
#include "qgslogger.h"
#include "qgsludialog.h"
#include "qgsproject.h"
#include "qgsmapcanvas.h"
#include "qgsclassificationmethod.h"
#include "qgsapplication.h"
#include "qgsclassificationmethodregistry.h"
#include "qgsclassificationequalinterval.h"
#include "qgsclassificationstandarddeviation.h"
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QPen>
#include <QPainter>
#include <QClipboard>
// ------------------------------ Model ------------------------------------
///@cond PRIVATE
@ -151,7 +155,7 @@ QVariant QgsGraduatedSymbolRendererModel::data( const QModelIndex &index, int ro
{
case 1:
{
int decimalPlaces = mRenderer->labelFormat().precision() + 2;
int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
if ( decimalPlaces < 0 ) decimalPlaces = 0;
return QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) + " - " + QLocale().toString( range.upperValue(), 'f', decimalPlaces );
}
@ -454,11 +458,13 @@ QgsGraduatedSymbolRendererWidget::QgsGraduatedSymbolRendererWidget( QgsVectorLay
// setup user interface
setupUi( this );
cboGraduatedMode->addItem( tr( "Equal Interval" ), QgsGraduatedSymbolRenderer::EqualInterval );
cboGraduatedMode->addItem( tr( "Quantile (Equal Count)" ), QgsGraduatedSymbolRenderer::Quantile );
cboGraduatedMode->addItem( tr( "Natural Breaks (Jenks)" ), QgsGraduatedSymbolRenderer::Jenks );
cboGraduatedMode->addItem( tr( "Standard Deviation" ), QgsGraduatedSymbolRenderer::StdDev );
cboGraduatedMode->addItem( tr( "Pretty Breaks" ), QgsGraduatedSymbolRenderer::Pretty );
mSymmetryPointValidator = new QDoubleValidator();
cboSymmetryPoint->setEditable( true );
cboSymmetryPoint->setValidator( mSymmetryPointValidator );
const QMap<QString, QString> methods = QgsApplication::classificationMethodRegistry()->methodNames();
for ( QMap<QString, QString>::const_iterator it = methods.constBegin(); it != methods.constEnd(); ++it )
cboGraduatedMode->addItem( it.value(), it.key() );
connect( methodComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged );
this->layout()->setContentsMargins( 0, 0, 0, 0 );
@ -474,8 +480,8 @@ QgsGraduatedSymbolRendererWidget::QgsGraduatedSymbolRendererWidget( QgsVectorLay
mSizeUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
spinPrecision->setMinimum( QgsRendererRangeLabelFormat::MIN_PRECISION );
spinPrecision->setMaximum( QgsRendererRangeLabelFormat::MAX_PRECISION );
spinPrecision->setMinimum( QgsClassificationMethod::MIN_PRECISION );
spinPrecision->setMaximum( QgsClassificationMethod::MAX_PRECISION );
btnColorRamp->setShowRandomColorRamp( true );
@ -500,16 +506,16 @@ QgsGraduatedSymbolRendererWidget::QgsGraduatedSymbolRendererWidget( QgsVectorLay
btnChangeGraduatedSymbol->setSymbol( mGraduatedSymbol->clone() );
methodComboBox->blockSignals( true );
methodComboBox->addItem( QStringLiteral( "Color" ) );
methodComboBox->addItem( tr( "Color" ), ColorMode );
if ( mGraduatedSymbol->type() == QgsSymbol::Marker )
{
methodComboBox->addItem( QStringLiteral( "Size" ) );
methodComboBox->addItem( tr( "Size" ), SizeMode );
minSizeSpinBox->setValue( 1 );
maxSizeSpinBox->setValue( 8 );
}
else if ( mGraduatedSymbol->type() == QgsSymbol::Line )
{
methodComboBox->addItem( QStringLiteral( "Size" ) );
methodComboBox->addItem( tr( "Size" ), SizeMode );
minSizeSpinBox->setValue( .1 );
maxSizeSpinBox->setValue( 2 );
}
@ -600,8 +606,8 @@ void QgsGraduatedSymbolRendererWidget::connectUpdateHandlers()
connect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
connect( cboSymmetryPointForPretty, static_cast<void ( QComboBox::* )( int )>( &QComboBox::activated ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
connect( spinSymmetryPointForOtherMethods, static_cast<void( QgsDoubleSpinBox::* )()>( &QgsDoubleSpinBox::editingFinished ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
connect( cboSymmetryPoint, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
connect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
}
// Connect/disconnect event handlers which trigger updating renderer
@ -621,86 +627,46 @@ void QgsGraduatedSymbolRendererWidget::disconnectUpdateHandlers()
disconnect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
disconnect( cboSymmetryPointForPretty, static_cast<void ( QComboBox::* )( int )>( &QComboBox::activated ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
disconnect( spinSymmetryPointForOtherMethods, static_cast<void( QgsDoubleSpinBox::* )()>( &QgsDoubleSpinBox::editingFinished ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
disconnect( cboSymmetryPoint, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
disconnect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
}
void QgsGraduatedSymbolRendererWidget::updateUiFromRenderer( bool updateCount )
{
disconnectUpdateHandlers();
spinSymmetryPointForOtherMethods->setShowClearButton( false );
// update UI from the graduated renderer (update combo boxes, view)
if ( cboGraduatedMode->findData( mRenderer->mode() ) >= 0 )
const QgsClassificationMethod *method = mRenderer->classificationMethod();
const QgsRangeList ranges = mRenderer->ranges();
// use the breaks for symmetry point
int precision = spinPrecision->value() + 2;
while ( cboSymmetryPoint->count() )
cboSymmetryPoint->removeItem( 0 );
for ( int i = 0; i < ranges.count() - 1; i++ )
cboSymmetryPoint->addItem( QString::number( ranges.at( i ).upperValue(), 'f', precision ), ranges.at( i ).upperValue() );
if ( method )
{
cboGraduatedMode->setCurrentIndex( cboGraduatedMode->findData( mRenderer->mode() ) );
int idx = cboGraduatedMode->findData( method->id() );
if ( idx >= 0 )
cboGraduatedMode->setCurrentIndex( idx );
mGroupBoxSymmetric->setVisible( method->symmetricModeAvailable() );
mGroupBoxSymmetric->setChecked( method->symmetricModeEnabled() );
cbxAstride->setChecked( method->symmetryAstride() );
if ( method->symmetricModeEnabled() )
cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QString::number( method->symmetryPoint(), 'f', method->labelPrecision() + 2 ) );
txtLegendFormat->setText( method->labelFormat() );
spinPrecision->setValue( method->labelPrecision() );
cbxTrimTrailingZeroes->setChecked( method->labelTrimTrailingZeroes() );
}
// symmetric classification
const QgsGraduatedSymbolRenderer::Mode cboMode = static_cast< QgsGraduatedSymbolRenderer::Mode >( cboGraduatedMode->currentData().toInt() );
switch ( cboMode )
{
case QgsGraduatedSymbolRenderer::EqualInterval:
case QgsGraduatedSymbolRenderer::StdDev:
{
mGroupBoxSymmetric->setVisible( true );
cbxAstride->setVisible( true );
cboSymmetryPointForPretty->setVisible( false );
spinSymmetryPointForOtherMethods->setVisible( true );
spinSymmetryPointForOtherMethods->setValue( mRenderer->symmetryPoint() );
break;
}
case QgsGraduatedSymbolRenderer::Pretty:
{
mGroupBoxSymmetric->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 ) );
break;
}
case QgsGraduatedSymbolRenderer::Quantile:
case QgsGraduatedSymbolRenderer::Jenks:
case QgsGraduatedSymbolRenderer::Custom:
{
mGroupBoxSymmetric->setVisible( false );
cbxAstride->setVisible( false );
cboSymmetryPointForPretty->setVisible( false );
spinSymmetryPointForOtherMethods->setVisible( false );
spinSymmetryPointForOtherMethods->setValue( mRenderer->symmetryPoint() );
break;
}
}
if ( mRenderer->useSymmetricMode() )
{
mGroupBoxSymmetric->setChecked( true );
spinSymmetryPointForOtherMethods->setEnabled( true );
cbxAstride->setEnabled( true );
cboSymmetryPointForPretty->setEnabled( true );
}
else
{
mGroupBoxSymmetric->setChecked( false );
spinSymmetryPointForOtherMethods->setEnabled( false );
cbxAstride->setEnabled( false );
cboSymmetryPointForPretty->setEnabled( false );
}
if ( 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();
int nclasses = ranges.count();
if ( nclasses && updateCount )
spinGraduatedClasses->setValue( mRenderer->ranges().count() );
spinGraduatedClasses->setValue( ranges.count() );
// set column
QString attrName = mRenderer->classAttribute();
@ -731,7 +697,7 @@ void QgsGraduatedSymbolRendererWidget::updateUiFromRenderer( bool updateCount )
methodComboBox->blockSignals( true );
if ( mRenderer->graduatedMethod() == QgsGraduatedSymbolRenderer::GraduatedColor )
{
methodComboBox->setCurrentIndex( 0 );
methodComboBox->setCurrentIndex( methodComboBox->findData( ColorMode ) );
if ( mRenderer->sourceColorRamp() )
{
btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
@ -739,7 +705,7 @@ void QgsGraduatedSymbolRendererWidget::updateUiFromRenderer( bool updateCount )
}
else
{
methodComboBox->setCurrentIndex( 1 );
methodComboBox->setCurrentIndex( methodComboBox->findData( SizeMode ) );
if ( !mRenderer->ranges().isEmpty() ) // avoid overriding default size with zeros
{
minSizeSpinBox->setValue( mRenderer->minSymbolSize() );
@ -749,11 +715,6 @@ void QgsGraduatedSymbolRendererWidget::updateUiFromRenderer( bool updateCount )
toggleMethodWidgets( methodComboBox->currentIndex() );
methodComboBox->blockSignals( false );
QgsRendererRangeLabelFormat labelFormat = mRenderer->labelFormat();
txtLegendFormat->setText( labelFormat.format() );
spinPrecision->setValue( labelFormat.precision() );
cbxTrimTrailingZeroes->setChecked( labelFormat.trimTrailingZeroes() );
viewGraduated->resizeColumnToContents( 0 );
viewGraduated->resizeColumnToContents( 1 );
viewGraduated->resizeColumnToContents( 2 );
@ -891,6 +852,21 @@ void QgsGraduatedSymbolRendererWidget::applyChangeToSymbol()
emit widgetChanged();
}
void QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished( )
{
const QString text = cboSymmetryPoint->lineEdit()->text();
int index = cboSymmetryPoint->findText( text );
if ( index != -1 )
{
cboSymmetryPoint->setCurrentIndex( index );
}
else
{
cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), text );
classifyGraduated();
}
}
void QgsGraduatedSymbolRendererWidget::classifyGraduated()
{
@ -904,98 +880,49 @@ void QgsGraduatedSymbolRendererWidget::classifyGraduated()
return;
}
QgsGraduatedSymbolRenderer::Mode mode = QgsGraduatedSymbolRenderer::Quantile;
bool useSymmetricMode = false;
bool astride = false;
const QString methodId = cboGraduatedMode->currentData().toString();
QgsClassificationMethod *method = QgsApplication::classificationMethodRegistry()->method( methodId );
Q_ASSERT( method );
int attrNum = mLayer->fields().lookupField( attrName );
double minimum = mLayer->minimumValue( attrNum ).toDouble();
double maximum = mLayer->maximumValue( attrNum ).toDouble();
spinSymmetryPointForOtherMethods->setMinimum( minimum );
spinSymmetryPointForOtherMethods->setMaximum( maximum );
spinSymmetryPointForOtherMethods->setDecimals( spinPrecision->value() );
mSymmetryPointValidator->setBottom( minimum );
mSymmetryPointValidator->setTop( maximum );
mSymmetryPointValidator->setDecimals( spinPrecision->value() );
double symmetryPoint = spinSymmetryPointForOtherMethods->value();
const QgsGraduatedSymbolRenderer::Mode cboMode = static_cast< QgsGraduatedSymbolRenderer::Mode >( cboGraduatedMode->currentData().toInt() );
switch ( cboMode )
if ( method->id() == QgsClassificationEqualInterval::METHOD_ID ||
method->id() == QgsClassificationStandardDeviation::METHOD_ID )
{
case QgsGraduatedSymbolRenderer::EqualInterval:
{
mode = QgsGraduatedSymbolRenderer::EqualInterval;
// knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
// using "(maximum-minimum)/100)" to avoid direct comparison of doubles
if ( spinSymmetryPointForOtherMethods->value() < ( minimum + ( maximum - minimum ) / 100. ) || spinSymmetryPointForOtherMethods->value() > ( maximum - ( maximum - minimum ) / 100. ) )
spinSymmetryPointForOtherMethods->setValue( minimum + ( maximum - minimum ) / 2. );
if ( mGroupBoxSymmetric->isChecked() )
{
useSymmetricMode = true;
symmetryPoint = spinSymmetryPointForOtherMethods->value();
astride = cbxAstride->isChecked();
}
break;
}
case QgsGraduatedSymbolRenderer::Jenks:
{
mode = QgsGraduatedSymbolRenderer::Jenks;
break;
}
case QgsGraduatedSymbolRenderer::StdDev:
{
mode = QgsGraduatedSymbolRenderer::StdDev;
// knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
// using "(maximum-minimum)/100)" to avoid direct comparison of doubles
if ( spinSymmetryPointForOtherMethods->value() < ( minimum + ( maximum - minimum ) / 100. ) || spinSymmetryPointForOtherMethods->value() > ( maximum - ( maximum - minimum ) / 100. ) )
spinSymmetryPointForOtherMethods->setValue( minimum + ( maximum - minimum ) / 2. );
if ( mGroupBoxSymmetric->isChecked() )
{
useSymmetricMode = true;
symmetryPoint = spinSymmetryPointForOtherMethods->value();
astride = cbxAstride->isChecked();
}
break;
}
case QgsGraduatedSymbolRenderer::Pretty:
{
mode = QgsGraduatedSymbolRenderer::Pretty;
if ( mGroupBoxSymmetric->isChecked() )
{
useSymmetricMode = true;
astride = cbxAstride->isChecked();
symmetryPoint = cboSymmetryPointForPretty->currentText().toDouble(); //selected number
}
break;
}
case QgsGraduatedSymbolRenderer::Quantile:
case QgsGraduatedSymbolRenderer::Custom:
{
// default should be quantile for now
mode = QgsGraduatedSymbolRenderer::Quantile; // Quantile
break;
}
// knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
// using "(maximum-minimum)/100)" to avoid direct comparison of doubles
double currentValue = cboSymmetryPoint->currentText().toDouble();
if ( currentValue < ( minimum + ( maximum - minimum ) / 100. ) || currentValue > ( maximum - ( maximum - minimum ) / 100. ) )
cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QString::number( minimum + ( maximum - minimum ) / 2., 'f', method->labelPrecision() + 2 ) );
}
// Jenks is n^2 complexity, warn for big dataset (more than 50k records)
if ( mGroupBoxSymmetric->isChecked() )
{
double symmetryPoint = cboSymmetryPoint->currentText().toDouble();
bool astride = cbxAstride->isChecked();
method->setSymmetricMode( true, symmetryPoint, astride );
}
// set method to renderer
mRenderer->setClassificationMethod( method );
// create and set new renderer
mRenderer->setClassAttribute( attrName );
// If complexity >= oN^2, warn for big dataset (more than 50k records)
// and give the user the chance to cancel
if ( QgsGraduatedSymbolRenderer::Jenks == mode && mLayer->featureCount() > 50000 )
if ( method && method->codeComplexity() > 1 && mLayer->featureCount() > 50000 )
{
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 )
if ( methodComboBox->currentData() == ColorMode )
{
if ( !ramp )
{
@ -1011,9 +938,9 @@ void QgsGraduatedSymbolRendererWidget::classifyGraduated()
QApplication::setOverrideCursor( Qt::WaitCursor );
mRenderer->updateClasses( mLayer, mode, nclasses, useSymmetricMode, symmetryPoint, astride );
mRenderer->updateClasses( mLayer, nclasses );
if ( methodComboBox->currentIndex() == 1 )
if ( methodComboBox->currentData() == SizeMode )
mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
mRenderer->calculateLabelPrecision();
@ -1138,7 +1065,7 @@ void QgsGraduatedSymbolRendererWidget::changeRange( int rangeIdx )
const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
// Add arbitrary 2 to number of decimal places to retain a bit extra.
// Ensures users can see if legend is not completely honest!
int decimalPlaces = mRenderer->labelFormat().precision() + 2;
int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
if ( decimalPlaces < 0 ) decimalPlaces = 0;
dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
@ -1249,11 +1176,10 @@ void QgsGraduatedSymbolRendererWidget::changeCurrentValue( QStandardItem *item )
void QgsGraduatedSymbolRendererWidget::labelFormatChanged()
{
QgsRendererRangeLabelFormat labelFormat = QgsRendererRangeLabelFormat(
txtLegendFormat->text(),
spinPrecision->value(),
cbxTrimTrailingZeroes->isChecked() );
mRenderer->setLabelFormat( labelFormat, true );
mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
mRenderer->updateRangeLabels();
mModel->updateLabels();
}
@ -1296,7 +1222,7 @@ QList<QgsSymbol *> QgsGraduatedSymbolRendererWidget::selectedSymbols()
QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
{
int decimalPlaces = mRenderer->labelFormat().precision() + 2;
int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
if ( decimalPlaces < 0 )
decimalPlaces = 0;
double precision = 1.0 / std::pow( 10, decimalPlaces );

View File

@ -131,6 +131,7 @@ class GUI_EXPORT QgsGraduatedSymbolRendererWidget : public QgsRendererWidget, pr
void dataDefinedSizeLegend();
void changeGraduatedSymbol();
void selectionChanged( const QItemSelection &selected, const QItemSelection &deselected );
void symmetryPointEditingFinished();
protected slots:
@ -160,6 +161,14 @@ class GUI_EXPORT QgsGraduatedSymbolRendererWidget : public QgsRendererWidget, pr
void keyPressEvent( QKeyEvent *event ) override;
private:
enum MethodMode
{
ColorMode,
SizeMode
};
QgsExpressionContext createExpressionContext() const override;
std::unique_ptr< QgsGraduatedSymbolRenderer > mRenderer;
std::unique_ptr< QgsSymbol > mGraduatedSymbol;
@ -170,7 +179,7 @@ class GUI_EXPORT QgsGraduatedSymbolRendererWidget : public QgsRendererWidget, pr
QgsRangeList mCopyBuffer;
QgsExpressionContext createExpressionContext() const override;
QDoubleValidator *mSymmetryPointValidator;
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>482</width>
<height>618</height>
<width>498</width>
<height>641</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
@ -357,26 +357,7 @@ Negative rounds to powers of 10</string>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1">
<item row="0" column="1">
<widget class="QgsDoubleSpinBox" name="spinSymmetryPointForOtherMethods">
<property name="specialValueText">
<string/>
</property>
<property name="minimum">
<double>-99999999999999995164818811802792197885196090803013355167206819763650035712.000000000000000</double>
</property>
<property name="maximum">
<double>99999999999999995164818811802792197885196090803013355167206819763650035712.000000000000000</double>
</property>
<property name="value">
<double>100.000000000000000</double>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1,0">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
@ -384,8 +365,12 @@ Negative rounds to powers of 10</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QComboBox" name="cboSymmetryPointForPretty"/>
<item row="0" column="1">
<widget class="QComboBox" name="cboSymmetryPoint">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="cbxAstride">

View File

@ -27,6 +27,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/core/scalebar
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/core/classification
${CMAKE_SOURCE_DIR}/src/core/mesh
${CMAKE_SOURCE_DIR}/src/test
${CMAKE_BINARY_DIR}/src/core

View File

@ -19,6 +19,7 @@
#include <QSettings>
#include "qgsgraduatedsymbolrenderer.h"
#include "qgsclassificationequalinterval.h"
#include "qgssymbollayerutils.h"
/**
@ -176,7 +177,7 @@ void TestQgsGraduatedSymbolRenderer::classifySymmetric()
// with astride = false
astride = false;
breaks = unchanged_breaks;
QgsGraduatedSymbolRenderer::makeBreaksSymmetric( breaks, symmetryPoint, astride );
QgsClassificationMethod::makeBreaksSymmetric( breaks, symmetryPoint, astride );
QCOMPARE( breaks.count() % 2, 0 );
// because the minimum is not in the breaks
int newPosOfSymmetryPoint = breaks.count() / 2;
@ -185,7 +186,7 @@ void TestQgsGraduatedSymbolRenderer::classifySymmetric()
// with astride = true
astride = true;
breaks = unchanged_breaks;
QgsGraduatedSymbolRenderer::makeBreaksSymmetric( breaks, symmetryPoint, astride );
QgsClassificationMethod::makeBreaksSymmetric( breaks, symmetryPoint, astride );
QCOMPARE( breaks.count() % 2, 1 );
QVERIFY( !breaks.contains( symmetryPoint ) );
}
@ -195,7 +196,10 @@ void TestQgsGraduatedSymbolRenderer::classifySymmetric()
// with astride = false
astride = false;
breaks = QgsGraduatedSymbolRenderer::calcEqualIntervalBreaks( minimum[valTest], maximum[valTest], nclasses, useSymmetricMode, symmetryPointForEqualInterval[valTest], astride );
QgsClassificationEqualInterval method;
method.setSymmetricMode( useSymmetricMode, symmetryPointForEqualInterval[valTest], astride );
QList<QgsClassificationRange> ranges = method.classes( minimum[valTest], maximum[valTest], nclasses );
breaks = QgsClassificationMethod::listToValues( ranges );
QCOMPARE( breaks.count() % 2, 0 );
// because the minimum is not in the breaks
newPosOfSymmetryPoint = breaks.count() / 2 ;
@ -203,7 +207,9 @@ void TestQgsGraduatedSymbolRenderer::classifySymmetric()
// with astride = true
astride = true;
breaks = QgsGraduatedSymbolRenderer::calcEqualIntervalBreaks( minimum[valTest], maximum[valTest], nclasses, useSymmetricMode, symmetryPointForEqualInterval[valTest], astride );
method.setSymmetricMode( useSymmetricMode, symmetryPointForEqualInterval[valTest], astride );
ranges = method.classes( minimum[valTest], maximum[valTest], nclasses );
breaks = QgsClassificationMethod::listToValues( ranges );
QCOMPARE( breaks.count() % 2, 1 );
QVERIFY( !breaks.contains( symmetryPointForEqualInterval[valTest] ) );
}

View File

@ -25,6 +25,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src/core/processing
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/core/classification
${CMAKE_SOURCE_DIR}/src/core/fieldformatter
${CMAKE_SOURCE_DIR}/src/test
${CMAKE_SOURCE_DIR}/src/native