Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Move prettyBreaks calculation to QgsSymbolLayerV2Utils
  • Loading branch information
vmora authored and nyalldawson committed Apr 27, 2015
1 parent 0c5063f commit cf3a712
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 150 deletions.
7 changes: 7 additions & 0 deletions python/core/symbology-ng/qgssymbollayerv2utils.sip
Expand Up @@ -374,4 +374,11 @@ class QgsSymbolLayerV2Utils
*/
static QString fieldOrExpressionFromExpression( QgsExpression* expression );

/** Computes a sequence of about 'classes' equally spaced round values
* which cover the range of values from 'minimum' to 'maximum'.
* The values are chosen so that they are 1, 2 or 5 times a power of 10.
* @note added in 2.10
*/
static QList<double> prettyBreaks( double minimum, double maximum, int classes );

};
153 changes: 3 additions & 150 deletions src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp
Expand Up @@ -31,7 +31,6 @@
#include <QDomElement>
#include <QSettings> // for legend
#include <limits> // for jenks classification
#include <cmath> // for pretty classification
#include <ctime>

QgsRendererRangeV2::QgsRendererRangeV2()
Expand Down Expand Up @@ -617,160 +616,14 @@ static QList<double> _calcQuantileBreaks( QList<double> values, int classes )
return breaks;
}

static QList<double> _calcPrettyBreaks( double minimum, double maximum, int classes )
{

// C++ implementation of R's pretty algorithm
// Based on code for determining optimal tick placement for statistical graphics
// from the R statistical programming language.
// Code ported from R implementation from 'labeling' R package
//
// Computes a sequence of about 'classes' equally spaced round values
// which cover the range of values from 'minimum' to 'maximum'.
// The values are chosen so that they are 1, 2 or 5 times a power of 10.

QList<double> breaks;
if ( classes < 1 )
{
breaks.append( maximum );
return breaks;
}

int minimumCount = ( int ) classes / 3;
double shrink = 0.75;
double highBias = 1.5;
double adjustBias = 0.5 + 1.5 * highBias;
int divisions = classes;
double h = highBias;
double cell;
int U;
bool small = false;
double dx = maximum - minimum;

if ( dx == 0 && maximum == 0 )
{
cell = 1.0;
small = true;
U = 1;
}
else
{
cell = qMax( qAbs( minimum ), qAbs( maximum ) );
if ( adjustBias >= 1.5 * h + 0.5 )
{
U = 1 + ( 1.0 / ( 1 + h ) );
}
else
{
U = 1 + ( 1.5 / ( 1 + adjustBias ) );
}
small = dx < ( cell * U * qMax( 1, divisions ) * 1e-07 * 3.0 );
}

if ( small )
{
if ( cell > 10 )
{
cell = 9 + cell / 10;
cell = cell * shrink;
}
if ( minimumCount > 1 )
{
cell = cell / minimumCount;
}
}
else
{
cell = dx;
if ( divisions > 1 )
{
cell = cell / divisions;
}
}
if ( cell < 20 * 1e-07 )
{
cell = 20 * 1e-07;
}

double base = pow( 10.0, floor( log10( cell ) ) );
double unit = base;
if (( 2 * base ) - cell < h *( cell - unit ) )
{
unit = 2.0 * base;
if (( 5 * base ) - cell < adjustBias *( cell - unit ) )
{
unit = 5.0 * base;
if (( 10.0 * base ) - cell < h *( cell - unit ) )
{
unit = 10.0 * base;
}
}
}
// Maybe used to correct for the epsilon here??
int start = floor( minimum / unit + 1e-07 );
int end = ceil( maximum / unit - 1e-07 );

// Extend the range out beyond the data. Does this ever happen??
while ( start * unit > minimum + ( 1e-07 * unit ) )
{
start = start - 1;
}
while ( end * unit < maximum - ( 1e-07 * unit ) )
{
end = end + 1;
}
QgsDebugMsg( QString( "pretty classes: %1" ).arg( end ) );

// If we don't have quite enough labels, extend the range out
// to make more (these labels are beyond the data :( )
int k = floor( 0.5 + end - start );
if ( k < minimumCount )
{
k = minimumCount - k;
if ( start >= 0 )
{
end = end + k / 2;
start = start - k / 2 + k % 2;
}
else
{
start = start - k / 2;
end = end + k / 2 + k % 2;
}
}
double minimumBreak = start * unit;
//double maximumBreak = end * unit;
int count = end - start;

for ( int i = 1; i < count + 1; i++ )
{
breaks.append( minimumBreak + i * unit );
}

if ( breaks.isEmpty() )
return breaks;

if ( breaks.first() < minimum )
{
breaks[0] = minimum;
}
if ( breaks.last() > maximum )
{
breaks[breaks.count()-1] = maximum;
}

return breaks;
} // _calcPrettyBreaks


static QList<double> _calcStdDevBreaks( QList<double> values, int classes, QList<double> &labels )
{

// 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 '_calcPrettyBreaks' of the centred and scaled
// 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
Expand Down Expand Up @@ -799,7 +652,7 @@ static QList<double> _calcStdDevBreaks( QList<double> values, int classes, QList
}
stdDev = sqrt( stdDev / n );

QList<double> breaks = _calcPrettyBreaks(( minimum - mean ) / stdDev, ( maximum - mean ) / stdDev, classes );
QList<double> breaks = QgsSymbolLayerV2Utils::prettyBreaks(( minimum - mean ) / stdDev, ( maximum - mean ) / stdDev, classes );
for ( int i = 0; i < breaks.count(); i++ )
{
labels.append( breaks[i] );
Expand Down Expand Up @@ -1046,7 +899,7 @@ void QgsGraduatedSymbolRendererV2::updateClasses( QgsVectorLayer *vlayer, Mode m
}
else if ( mode == Pretty )
{
breaks = _calcPrettyBreaks( minimum, maximum, nclasses );
breaks = QgsSymbolLayerV2Utils::prettyBreaks( minimum, maximum, nclasses );
}
else if ( mode == Quantile || mode == Jenks || mode == StdDev )
{
Expand Down
143 changes: 143 additions & 0 deletions src/core/symbology-ng/qgssymbollayerv2utils.cpp
Expand Up @@ -3781,3 +3781,146 @@ QString QgsSymbolLayerV2Utils::fieldOrExpressionFromExpression( QgsExpression* e
return expression->expression();
}

QList<double> QgsSymbolLayerV2Utils::prettyBreaks( double minimum, double maximum, int classes )
{
// C++ implementation of R's pretty algorithm
// Based on code for determining optimal tick placement for statistical graphics
// from the R statistical programming language.
// Code ported from R implementation from 'labeling' R package
//
// Computes a sequence of about 'classes' equally spaced round values
// which cover the range of values from 'minimum' to 'maximum'.
// The values are chosen so that they are 1, 2 or 5 times a power of 10.

QList<double> breaks;
if ( classes < 1 )
{
breaks.append( maximum );
return breaks;
}

int minimumCount = ( int ) classes / 3;
double shrink = 0.75;
double highBias = 1.5;
double adjustBias = 0.5 + 1.5 * highBias;
int divisions = classes;
double h = highBias;
double cell;
int U;
bool small = false;
double dx = maximum - minimum;

if ( dx == 0 && maximum == 0 )
{
cell = 1.0;
small = true;
U = 1;
}
else
{
cell = qMax( qAbs( minimum ), qAbs( maximum ) );
if ( adjustBias >= 1.5 * h + 0.5 )
{
U = 1 + ( 1.0 / ( 1 + h ) );
}
else
{
U = 1 + ( 1.5 / ( 1 + adjustBias ) );
}
small = dx < ( cell * U * qMax( 1, divisions ) * 1e-07 * 3.0 );
}

if ( small )
{
if ( cell > 10 )
{
cell = 9 + cell / 10;
cell = cell * shrink;
}
if ( minimumCount > 1 )
{
cell = cell / minimumCount;
}
}
else
{
cell = dx;
if ( divisions > 1 )
{
cell = cell / divisions;
}
}
if ( cell < 20 * 1e-07 )
{
cell = 20 * 1e-07;
}

double base = pow( 10.0, floor( log10( cell ) ) );
double unit = base;
if (( 2 * base ) - cell < h *( cell - unit ) )
{
unit = 2.0 * base;
if (( 5 * base ) - cell < adjustBias *( cell - unit ) )
{
unit = 5.0 * base;
if (( 10.0 * base ) - cell < h *( cell - unit ) )
{
unit = 10.0 * base;
}
}
}
// Maybe used to correct for the epsilon here??
int start = floor( minimum / unit + 1e-07 );
int end = ceil( maximum / unit - 1e-07 );

// Extend the range out beyond the data. Does this ever happen??
while ( start * unit > minimum + ( 1e-07 * unit ) )
{
start = start - 1;
}
while ( end * unit < maximum - ( 1e-07 * unit ) )
{
end = end + 1;
}
QgsDebugMsg( QString( "pretty classes: %1" ).arg( end ) );

// If we don't have quite enough labels, extend the range out
// to make more (these labels are beyond the data :( )
int k = floor( 0.5 + end - start );
if ( k < minimumCount )
{
k = minimumCount - k;
if ( start >= 0 )
{
end = end + k / 2;
start = start - k / 2 + k % 2;
}
else
{
start = start - k / 2;
end = end + k / 2 + k % 2;
}
}
double minimumBreak = start * unit;
//double maximumBreak = end * unit;
int count = end - start;

for ( int i = 1; i < count + 1; i++ )
{
breaks.append( minimumBreak + i * unit );
}

if ( breaks.isEmpty() )
return breaks;

if ( breaks.first() < minimum )
{
breaks[0] = minimum;
}
if ( breaks.last() > maximum )
{
breaks[breaks.count()-1] = maximum;
}

return breaks;
}
7 changes: 7 additions & 0 deletions src/core/symbology-ng/qgssymbollayerv2utils.h
Expand Up @@ -426,6 +426,13 @@ class CORE_EXPORT QgsSymbolLayerV2Utils
*/
static QString fieldOrExpressionFromExpression( QgsExpression* expression );

/** Computes a sequence of about 'classes' equally spaced round values
* which cover the range of values from 'minimum' to 'maximum'.
* The values are chosen so that they are 1, 2 or 5 times a power of 10.
* @note added in 2.10
*/
static QList<double> prettyBreaks( double minimum, double maximum, int classes );

};

class QPolygonF;
Expand Down

0 comments on commit cf3a712

Please sign in to comment.