When generating graphs and showing different sets of data it usually a good idea to difference the sets by color. So one line is red and the next is green and so on. The pro
I implemented this algorithm in a shorter way
void ColorValue::SetColorValue( double r, double g, double b, ColorType myType )
{
this->c[0] = r;
this->c[1] = g;
this->c[2] = b;
this->type = myType;
}
DistinctColorGenerator::DistinctColorGenerator()
{
mFactor = 255;
mColorsGenerated = 0;
mpColorCycle = new ColorValue[6];
mpColorCycle[0].SetColorValue( 1.0, 0.0, 0.0, TYPE_RGB);
mpColorCycle[1].SetColorValue( 0.0, 1.0, 0.0, TYPE_RGB);
mpColorCycle[2].SetColorValue( 0.0, 0.0, 1.0, TYPE_RGB);
mpColorCycle[3].SetColorValue( 1.0, 1.0, 0.0, TYPE_RGB);
mpColorCycle[4].SetColorValue( 1.0, 0.0, 1.0, TYPE_RGB);
mpColorCycle[5].SetColorValue( 0.0, 1.0, 1.0, TYPE_RGB);
}
//----------------------------------------------------------
ColorValue DistinctColorGenerator::GenerateNewColor()
{
int innerCycleNr = mColorsGenerated % 6;
int outerCycleNr = mColorsGenerated / 6;
int cycleSize = pow( 2, (int)(log((double)(outerCycleNr)) / log( 2.0 ) ) );
int insideCycleCounter = outerCycleNr % cyclesize;
if ( outerCycleNr == 0)
{
mFactor = 255;
}
else
{
mFactor = ( 256 / ( 2 * cycleSize ) ) + ( insideCycleCounter * ( 256 / cycleSize ) );
}
ColorValue newColor = mpColorCycle[innerCycleNr] * mFactor;
mColorsGenerated++;
return newColor;
}
I have put up a page online for procedurally generating visually distinct colors:
http://phrogz.net/css/distinct-colors.html
Unlike other answers here that evenly step across RGB or HSV space (where there is a nonlinear relationship between the axis values and the perceptual differences), my page uses the standard CMI(I:c) color distance algorithm to prevent two colors from being too visually close.
The final tab of the page allows you to sort the values in several ways, and then interleave them (ordered shuffle) so that you get very distinct colors placed next to one another.
As of this writing, it only works well in Chrome and Safari, with a shim for Firefox; it uses HTML5 range input sliders in the interface, which IE9 and Firefox do not yet support natively.
You could also think of the color space as all combinations of three numbers from 0 to 255, inclusive. That's the base-255 representation of a number between 0 and 255^3, forced to have three decimal places (add zeros on to the end if need be.)
So to generate x number of colors, you'd calculate x evenly spaced percentages, 0 to 100. Get numbers by multiplying those percentages by 255^3, convert those numbers to base 255, and add zeros as previously mentioned.
Base conversion algorithm, for reference (in pseudocode that's quite close to C#):
int num = (number to convert);
int baseConvert = (desired base, 255 in this case);
(array of ints) nums = new (array of ints);
int x = num;
double digits = Math.Log(num, baseConvert); //or ln(num) / ln(baseConvert)
int numDigits = (digits - Math.Ceiling(digits) == 0 ? (int)(digits + 1) : (int)Math.Ceiling(digits)); //go up one if it turns out even
for (int i = 0; i < numDigits; i++)
{
int toAdd = ((int)Math.Floor(x / Math.Pow((double)convertBase, (double)(numDigits - i - 1))));
//Formula for 0th digit: d = num / (convertBase^(numDigits - 1))
//Then subtract (d * convertBase^(numDigits - 1)) from the num and continue
nums.Add(toAdd);
x -= toAdd * (int)Math.Pow((double)convertBase, (double)(numDigits - i - 1));
}
return nums;
You might also have to do something to bring the range in a little bit, to avoid having white and black, if you want. Those numbers aren't actually a smooth color scale, but they'll generate separate colors if you don't have too many.
This question has more on base conversion in .NET.
You have three colour channels 0 to 255 R, G and B.
First go through
0, 0, 255
0, 255, 0
255, 0, 0
Then go through
0, 255, 255
255, 0, 255
255, 255, 0
Then divide by 2 => 128 and start again:
0, 0, 128
0, 128, 0
128, 0, 0
0, 128, 128
128, 0, 128
128, 128, 0
Divide by 2 => 64
Next time add 64 to 128 => 192
follow the pattern.
Straightforward to program and gives you fairly distinct colours.
EDIT: Request for code sample
Also - adding in the additional pattern as below if gray is an acceptable colour:
255, 255, 255
128, 128, 128
There are a number of ways you can handle generating these in code.
If you can guarantee that you will never need more than a fixed number of colours, just generate an array of colours following this pattern and use those:
static string[] ColourValues = new string[] {
"FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", "000000",
"800000", "008000", "000080", "808000", "800080", "008080", "808080",
"C00000", "00C000", "0000C0", "C0C000", "C000C0", "00C0C0", "C0C0C0",
"400000", "004000", "000040", "404000", "400040", "004040", "404040",
"200000", "002000", "000020", "202000", "200020", "002020", "202020",
"600000", "006000", "000060", "606000", "600060", "006060", "606060",
"A00000", "00A000", "0000A0", "A0A000", "A000A0", "00A0A0", "A0A0A0",
"E00000", "00E000", "0000E0", "E0E000", "E000E0", "00E0E0", "E0E0E0",
};
If you don't know how many colours you are going to need, the code below will generate up to 896 colours using this pattern. (896 = 256 * 7 / 2) 256 is the colour space per channel, we have 7 patterns and we stop before we get to colours separated by only 1 colour value.
I've probably made harder work of this code than I needed to. First, there is an intensity generator which starts at 255, then generates the values as per the pattern described above. The pattern generator just loops through the seven colour patterns.
using System;
class Program {
static void Main(string[] args) {
ColourGenerator generator = new ColourGenerator();
for (int i = 0; i < 896; i++) {
Console.WriteLine(string.Format("{0}: {1}", i, generator.NextColour()));
}
}
}
public class ColourGenerator {
private int index = 0;
private IntensityGenerator intensityGenerator = new IntensityGenerator();
public string NextColour() {
string colour = string.Format(PatternGenerator.NextPattern(index),
intensityGenerator.NextIntensity(index));
index++;
return colour;
}
}
public class PatternGenerator {
public static string NextPattern(int index) {
switch (index % 7) {
case 0: return "{0}0000";
case 1: return "00{0}00";
case 2: return "0000{0}";
case 3: return "{0}{0}00";
case 4: return "{0}00{0}";
case 5: return "00{0}{0}";
case 6: return "{0}{0}{0}";
default: throw new Exception("Math error");
}
}
}
public class IntensityGenerator {
private IntensityValueWalker walker;
private int current;
public string NextIntensity(int index) {
if (index == 0) {
current = 255;
}
else if (index % 7 == 0) {
if (walker == null) {
walker = new IntensityValueWalker();
}
else {
walker.MoveNext();
}
current = walker.Current.Value;
}
string currentText = current.ToString("X");
if (currentText.Length == 1) currentText = "0" + currentText;
return currentText;
}
}
public class IntensityValue {
private IntensityValue mChildA;
private IntensityValue mChildB;
public IntensityValue(IntensityValue parent, int value, int level) {
if (level > 7) throw new Exception("There are no more colours left");
Value = value;
Parent = parent;
Level = level;
}
public int Level { get; set; }
public int Value { get; set; }
public IntensityValue Parent { get; set; }
public IntensityValue ChildA {
get {
return mChildA ?? (mChildA = new IntensityValue(this, this.Value - (1<<(7-Level)), Level+1));
}
}
public IntensityValue ChildB {
get {
return mChildB ?? (mChildB = new IntensityValue(this, Value + (1<<(7-Level)), Level+1));
}
}
}
public class IntensityValueWalker {
public IntensityValueWalker() {
Current = new IntensityValue(null, 1<<7, 1);
}
public IntensityValue Current { get; set; }
public void MoveNext() {
if (Current.Parent == null) {
Current = Current.ChildA;
}
else if (Current.Parent.ChildA == Current) {
Current = Current.Parent.ChildB;
}
else {
int levelsUp = 1;
Current = Current.Parent;
while (Current.Parent != null && Current == Current.Parent.ChildB) {
Current = Current.Parent;
levelsUp++;
}
if (Current.Parent != null) {
Current = Current.Parent.ChildB;
}
else {
levelsUp++;
}
for (int i = 0; i < levelsUp; i++) {
Current = Current.ChildA;
}
}
}
}
I think the HSV (or HSL) space has more opportunities here. If you don't mind the extra conversion, it's pretty easy to go through all the colors by just rotating the Hue value. If that's not enough, you can change the Saturation/Value/Lightness values and go through the rotation again. Or, you can always shift the Hue values or change your "stepping" angle and rotate more times.
You could get a random set of your 3 255 values and check it against the last set of 3 values, making sure they are each at least X away from the old values before using them.
OLD: 190, 120, 100
NEW: 180, 200, 30
If X = 20, then the new set would be regenerated again.