mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
Merge pull request #31500 from 3nids/refactor_classification
Refactor graduated symbol renderer
This commit is contained in:
commit
b3d52df67b
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 *
|
||||
************************************************************************/
|
@ -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 *
|
||||
************************************************************************/
|
@ -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 *
|
||||
************************************************************************/
|
@ -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 *
|
||||
************************************************************************/
|
@ -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 *
|
||||
************************************************************************/
|
@ -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 *
|
||||
************************************************************************/
|
@ -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 *
|
||||
************************************************************************/
|
@ -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 *
|
||||
************************************************************************/
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
53
src/core/classification/qgsclassificationcustom.cpp
Normal file
53
src/core/classification/qgsclassificationcustom.cpp
Normal 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>();
|
||||
}
|
44
src/core/classification/qgsclassificationcustom.h
Normal file
44
src/core/classification/qgsclassificationcustom.h
Normal 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
|
97
src/core/classification/qgsclassificationequalinterval.cpp
Normal file
97
src/core/classification/qgsclassificationequalinterval.cpp
Normal 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;
|
||||
}
|
46
src/core/classification/qgsclassificationequalinterval.h
Normal file
46
src/core/classification/qgsclassificationequalinterval.h
Normal 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
|
170
src/core/classification/qgsclassificationjenks.cpp
Normal file
170
src/core/classification/qgsclassificationjenks.cpp
Normal 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();
|
||||
}
|
44
src/core/classification/qgsclassificationjenks.h
Normal file
44
src/core/classification/qgsclassificationjenks.h
Normal 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
|
301
src/core/classification/qgsclassificationmethod.cpp
Normal file
301
src/core/classification/qgsclassificationmethod.cpp
Normal 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;
|
||||
}
|
311
src/core/classification/qgsclassificationmethod.h
Normal file
311
src/core/classification/qgsclassificationmethod.h
Normal 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
|
58
src/core/classification/qgsclassificationmethodregistry.cpp
Normal file
58
src/core/classification/qgsclassificationmethodregistry.cpp
Normal 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;
|
||||
}
|
||||
|
58
src/core/classification/qgsclassificationmethodregistry.h
Normal file
58
src/core/classification/qgsclassificationmethodregistry.h
Normal 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
|
54
src/core/classification/qgsclassificationprettybreaks.cpp
Normal file
54
src/core/classification/qgsclassificationprettybreaks.cpp
Normal 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;
|
||||
}
|
42
src/core/classification/qgsclassificationprettybreaks.h
Normal file
42
src/core/classification/qgsclassificationprettybreaks.h
Normal 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
|
88
src/core/classification/qgsclassificationquantile.cpp
Normal file
88
src/core/classification/qgsclassificationquantile.cpp
Normal 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;
|
||||
}
|
||||
|
43
src/core/classification/qgsclassificationquantile.h
Normal file
43
src/core/classification/qgsclassificationquantile.h
Normal 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
|
137
src/core/classification/qgsclassificationstandarddeviation.cpp
Normal file
137
src/core/classification/qgsclassificationstandarddeviation.cpp
Normal 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();
|
||||
}
|
52
src/core/classification/qgsclassificationstandarddeviation.h
Normal file
52
src/core/classification/qgsclassificationstandarddeviation.h
Normal 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
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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 "";
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 )
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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] ) );
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user