How to Automatically Generate N "Distinct" Colors

How to automatically generate N distinct colors?

You can use the HSL color model to create your colors.

If all you want is differing hues (likely), and slight variations on lightness or saturation, you can distribute the hues like so:

// assumes hue [0, 360), saturation [0, 100), lightness [0, 100)

for(i = 0; i < 360; i += 360 / num_colors) {
HSLColor c;
c.hue = i;
c.saturation = 90 + randf() * 10;
c.lightness = 50 + randf() * 10;

addColor(c);
}

How to generate n different colors for any natural number n?

100 is a lot of colours, but you might be able to do it by distributing them as sparsely as possible in the HSB or HSL space; doing it in RGB is probably difficult.

For example, you might decide to use 10 different hues, 4 different saturation levels, and 3 different brightness settings, that would give you up to 120 colours. You'll need to pick the saturation and brightness values carefully; human eyes are complicated and confusing sensors. If you treat the colour space as a cone, you will probably want a different number of hues at each lightness/saturation level.

Here's a link to the wikipedia entry on HSB.

Generate unique colours

Using the RGB color model is not a good way to get a good color mix. It's better to use another color model to generate your color, and then convert from that color model to RGB.

I suggest you the HSV or HSL color model instead, in particular you want to vary the Hue.

If you want X different color values, vary them from 0 to 360 with a step size of 360 divided by X.

How to generate random colors in matplotlib?

I'm calling scatter inside a loop and want each plot in a different color.

Based on that, and on your answer: It seems to me that you actually want n distinct colors for your datasets; you want to map the integer indices 0, 1, ..., n-1 to distinct RGB colors. Something like:

mapping index to color

Here is the function to do it:

import matplotlib.pyplot as plt

def get_cmap(n, name='hsv'):
'''Returns a function that maps each index in 0, 1, ..., n-1 to a distinct
RGB color; the keyword argument name must be a standard mpl colormap name.'''
return plt.cm.get_cmap(name, n)

Usage in your pseudo-code snippet in the question:

cmap = get_cmap(len(data))
for i, (X, Y) in enumerate(data):
scatter(X, Y, c=cmap(i))

I generated the figure in my answer with the following code:

import matplotlib.pyplot as plt

def get_cmap(n, name='hsv'):
'''Returns a function that maps each index in 0, 1, ..., n-1 to a distinct
RGB color; the keyword argument name must be a standard mpl colormap name.'''
return plt.cm.get_cmap(name, n)

def main():
N = 30
fig=plt.figure()
ax=fig.add_subplot(111)
plt.axis('scaled')
ax.set_xlim([ 0, N])
ax.set_ylim([-0.5, 0.5])
cmap = get_cmap(N)
for i in range(N):
rect = plt.Rectangle((i, -0.5), 1, 1, facecolor=cmap(i))
ax.add_artist(rect)
ax.set_yticks([])
plt.show()

if __name__=='__main__':
main()

Tested with both Python 2.7 & matplotlib 1.5, and with Python 3.5 & matplotlib 2.0. It works as expected.

Algorithm For Generating Unique Colors

Does the number of possible elements have a reasonable low bound? One quick and easy solution is to just store an array of color values using the ID of the item. That assumes that you have a relatively low amount of colors, and you're certain that you won't go above a certain number of items, however.

If you want to generate colors rather than use a list, one trick to make them have a consistent and decent look is to generate them using HSB. Pre-define a brightness and saturation, then base the hue value off some function of the ID (this can be a variety of things depending on how many IDs you plan to have, but multiplying the ID by some amount (and modding when it exceeds 255!) is a good rough approach. With this approach the colors will all "align" in terms of saturation and brightness but they'll each have a distinct color.

I'm a bit bored at work, so I whipped together a fast solution:

class HsbColor
{
public int Hue { get; set; }
public int Saturation { get; set; }
public int Brightness { get; set; }

public Color ToRGB
{
// left as exercise to the reader...
}
}

public class Item
{

public int Id { get; set; }
private static const byte EXPECTED_MAX = 15;
private static int HUE_FACTOR = 255 / EXPECTED_MAX;

public HsbColor Color
{
get {

var color = new HsbColor() { Saturation = 175, Brightness = 175 };

color.Hue = (Id * HUE_FACTOR) % 255;

return color;
}
}
}

Generate distinctly different RGB colors in graphs

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.

The Easy Way

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",
};

The Hard Way

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;
}

}
}
}

Randomly generate 3 distinct colors

My original answer, which uses the hcl color space, often generated color combinations that were hard to distinguish. This updated answer uses the Lab color space, which is scaled based on the perceptual distance between colors, so similar distances in Lab space should correspond to similar perceptual color differences. In Lab, L is luminance or brightness on a scale of 0 to 100. a represents green to red, and b represents blue to yellow, with both on a scale of -100 to 100.

The code below generates two random values for a and b. If we think of these two values as representing a point in the ab plane, we generate two more colors with maximum perceptual distance from each other by rotating this point first by 120 degrees and then by 240 degrees. We then choose a single L value to give us three equally spaced colors.

Below I've packaged this in a function to make it easy to generate several plots with random colors. I've also set a minimum absolute value for a and b so that we don't get colors that are too similar, and included an Lval argument for choosing the L value of the Lab colors.

Based on a several runs, it looks like this approach performs much better than my original hcl version (although this may be due not only to the use of Lab space instead of hcl space, but also because I used only one dimension of hcl space but two dimensions of Lab space).

library(colorspace)

random.colors = function(Lval=80, ABmin=50) {

# 120 deg rotation matrix
aa = 2*pi/3
rot = matrix(c(cos(aa), -sin(aa), sin(aa), cos(aa)), nrow=2, byrow=TRUE)

# Generate random A and B points in LAB space
x = runif(2, ABmin, 100) * sample(c(-1,1), 2,replace=TRUE)

# Create three equally spaced colors in Lab space and convert to RGB
cols = LAB(cbind(rep(Lval,3), rbind(x, x %*% rot, x %*% rot %*% rot)))
cols = rgb(convertColor(cols@coords, from="Lab", to="sRGB"))

plot(0, type = "n", xlim = c(0,10), ylim = c(0,10),
ann = FALSE, axes = FALSE, asp = 1)

polygon(x = c(1,9,9,1), y = c(1,1,9,9), border = NA, col = cols[1])

symbols(x = 5, y = 5, circles = 4, inches = FALSE,
add = TRUE, bg = cols[2], fg = NA)

text(x = 5, y = 5, labels = "Hi", col = cols[3], font = 2, cex = 3)
}

par(mfrow=c(3,3), mar=rep(0,4))
replicate(9,random.colors())

Sample Image

For simplicity, the example above constrains the a and b values to be a constant distance from the origin (in ab-space) and uses the same L value for all three colors. You could instead extend this method to use all three dimensions of the Lab space. In addition, instead of requiring a constant distance from the origin, you could pick the first color at random and then require that the next two colors be picked such that all three colors are maximally separated from each other in Lab space.

Original Answer

You could generate colors that are equally spaced in the hue dimension (that is, have the maximum possible hue separation from each other). For example:

set.seed(60)
cols = hcl(runif(1,0,359.99) + c(0,120,240), 100, 65)

plot(0, type = "n", xlim = c(0,10), ylim = c(0,10),
ann = FALSE, axes = FALSE, asp = 1)

polygon(x = c(1,9,9,1), y = c(1,1,9,9), border = NA, col = cols[1])

symbols(x = 5, y = 5, circles = 4, inches = FALSE,
add = TRUE, bg = cols[2], fg = NA)

text(x = 5, y = 5, labels = "Hi", col = cols[3], font = 2, cex = 3)

Sample Image

Here are nine more random draws. As you can see, there are some combinations that don't work too well. But perhaps you can play around with different ways of slicing up the color space to see if you can get something better.

Sample Image



Related Topics



Leave a reply



Submit