Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Improved method for selecting random colors for categorised renderer,
which should result in more visually distinct color choices
  • Loading branch information
nyalldawson committed Sep 25, 2014
1 parent 3f8a860 commit b837223
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 1 deletion.
9 changes: 9 additions & 0 deletions python/core/symbology-ng/qgsvectorcolorrampv2.sip
Expand Up @@ -14,6 +14,7 @@ class QgsVectorColorRampV2
%End

public:

virtual ~QgsVectorColorRampV2();

// Number of defined colors
Expand Down Expand Up @@ -139,6 +140,14 @@ class QgsRandomColorsV2 : QgsVectorColorRampV2
double value( int index ) const;

QColor color( double value ) const;

/*Sets the desired total number of unique colors for the resultant ramp. Calling
* this method pregenerates a set of visually distinct colors which are returned
* by subsequent calls to color().
* @param colorCount number of unique colors
* @note added in QGIS 2.5
*/
virtual void setTotalColorCount( const int colorCount );

QString type() const;

Expand Down
44 changes: 43 additions & 1 deletion src/core/symbology-ng/qgsvectorcolorrampv2.cpp
Expand Up @@ -22,6 +22,8 @@
#include "qgslogger.h"

#include <stdlib.h> // for random()
#include <algorithm>

#include <QTime>

//////////////
Expand Down Expand Up @@ -366,12 +368,53 @@ QColor QgsRandomColorsV2::color( double value ) const
Q_UNUSED( value );
int minVal = 130;
int maxVal = 255;

int colorIndex = value * ( mTotalColorCount - 1 );
if ( mTotalColorCount >= 1 && mPrecalculatedColors.length() > colorIndex )
{
//use precalculated hue
return mPrecalculatedColors.at( colorIndex );
}

//can't use precalculated hues, use a totally random hue
int h = 1 + ( int )( 360.0 * rand() / ( RAND_MAX + 1.0 ) );
int s = ( rand() % ( DEFAULT_RANDOM_SAT_MAX - DEFAULT_RANDOM_SAT_MIN + 1 ) ) + DEFAULT_RANDOM_SAT_MIN;
int v = ( rand() % ( maxVal - minVal + 1 ) ) + minVal;
return QColor::fromHsv( h, s, v );
}

void QgsRandomColorsV2::setTotalColorCount( const int colorCount )
{
//calculate colors in advance, so that we can ensure they are more visually distinct than pure random colors
mPrecalculatedColors.clear();
mTotalColorCount = colorCount;

//This works ok for low color counts, but for > 10 or so colors there's still a good chance of
//similar colors being picked. TODO - investigate alternative "n-visually distinct color" routines

//random offsets
double hueOffset = ( 360.0 * rand() / ( RAND_MAX + 1.0 ) );

//try to maximise difference between hues. this is not an ideal implementation, as constant steps
//through the hue wheel are not visually perceived as constant changes in hue
//(for instance, we are much more likely to get green hues than yellow hues)
double hueStep = 359.0 / colorCount;
double currentHue = hueOffset;

//build up a list of colors
for ( int idx = 0; idx < colorCount; ++ idx )
{
int h = qRound( currentHue ) % 360;
int s = ( rand() % ( DEFAULT_RANDOM_SAT_MAX - DEFAULT_RANDOM_SAT_MIN + 1 ) ) + DEFAULT_RANDOM_SAT_MIN;
int v = ( rand() % ( DEFAULT_RANDOM_VAL_MAX - DEFAULT_RANDOM_VAL_MIN + 1 ) ) + DEFAULT_RANDOM_VAL_MIN;
mPrecalculatedColors << QColor::fromHsv( h, s, v );
currentHue += hueStep;
}

//lastly, shuffle color list
std::random_shuffle( mPrecalculatedColors.begin(), mPrecalculatedColors.end() );
}

QString QgsRandomColorsV2::type() const
{
return "randomcolors";
Expand Down Expand Up @@ -653,4 +696,3 @@ bool QgsCptCityColorRampV2::loadFile()
mFileLoaded = true;
return true;
}

15 changes: 15 additions & 0 deletions src/core/symbology-ng/qgsvectorcolorrampv2.h
Expand Up @@ -25,6 +25,7 @@
class CORE_EXPORT QgsVectorColorRampV2
{
public:

virtual ~QgsVectorColorRampV2() {}

// Number of defined colors
Expand Down Expand Up @@ -173,11 +174,25 @@ class CORE_EXPORT QgsRandomColorsV2: public QgsVectorColorRampV2

QColor color( double value ) const;

/*Sets the desired total number of unique colors for the resultant ramp. Calling
* this method pregenerates a set of visually distinct colors which are returned
* by subsequent calls to color().
* @param colorCount number of unique colors
* @note added in QGIS 2.5
*/
virtual void setTotalColorCount( const int colorCount );

QString type() const;

QgsVectorColorRampV2* clone() const;

QgsStringMap properties() const;

protected:

int mTotalColorCount;
QList<QColor> mPrecalculatedColors;

};


Expand Down
11 changes: 11 additions & 0 deletions src/gui/symbology-ng/qgscategorizedsymbolrendererv2widget.cpp
Expand Up @@ -594,6 +594,17 @@ static void _createCategories( QgsCategoryList& cats, QList<QVariant>& values, Q

bool hasNull = false;

QgsRandomColorsV2* randomRamp = dynamic_cast<QgsRandomColorsV2*>( ramp );
if ( randomRamp )
{
//ramp is a random colors ramp, so inform it of the total number of required colors
//this allows the ramp to pregenerate a set of visually distinctive colors

//assume we need an extra color for nulls
int totalColors = num + 1;
randomRamp->setTotalColorCount( totalColors );
}

for ( int i = 0; i < num; i++ )
{
QVariant value = values[i];
Expand Down

0 comments on commit b837223

Please sign in to comment.