10
votes

I'm trying to graphically display a graph of N lines and I'm trying to find a way to dynamically assign distinct colors based on how many lines I have. The values in RGB range from 0 to 1. I can't use white because the background is white. I found it easy for N < 7:

r=(h&0x4)/4;
g=(h&0x2)/2;
b=h&0x1;

This gives me black, blue, green, cyan, red, magenta, yellow. But after that it will use white and then loop. Does anybody know a good way to assign RGB values for an index? I also have an opacity value to play with.

3
Maybe a good requirement for an answer is that the colors are optimally as far apart from each other as possible. (You don't want to just select 0x001, 0x002, and 0x003 if you can do something like 0xF00, 0x0F0, and 0x00F)Jeremy Powell
You don't really have an opacity value to play with :) On a white background, 0,0,0 with 50% opacity is the same as 127,127,127 with 100% opacity. In other words opacity is not independent from the other parameters.Pascal Cuoq

3 Answers

5
votes

My preferred method for doing this is to find n evenly-spaced points along the colour wheel.

We represent the colour wheel as a range of values between 0 and 360. Thus, the values we will use are 360 / n * 0, 360 / n * 1, ..., 360 / n * (n - 1). In doing this, we've defined the hue of each of our colours. We can describe each of these colours as Hue-Saturation-Value (HSV) colours by setting saturation to 1 and lightness to 1.

(A higher saturation means the colour is more "rich"; a lower saturation means the colour is closer to gray. A higher lightness means the colour is "brighter"; a lower lightness means the colour is "darker".)

Now, a simple calculation gives us the RGB values of each of these colours.

http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_HSV_to_RGB

Note that the equations given can be simplified:

  • p = v * (1 - s) = 1 * (1 - 1) = 1 * 0 = 0
  • q = v * (1 - f * s) = 1 * (1 - f * 1) = 1 - f
  • t = v * (1 - (1 - f) * s) = 1 * (1 - (1 - f) * 1) = 1 - (1 - f) = 1 - 1 + f = f

Pseudo-code-ish Implementation in Python

Note: This is intentionally a horribly inefficient implementation. The point of giving this example in Python is essentially so I can give executable pseudocode.

import math

def uniquecolors(n):
    """Compute a list of distinct colors, each of which is represented as an RGB 3-tuple."""
    hues = []
    # i is in the range 0, 1, ..., n - 1
    for i in range(n):
        hues.append(360.0 / i)

    hs = []
    for hue in hues:
        h = math.floor(hue / 60) % 6
        hs.append(h)

    fs = []
    for hue in hues:
        f = hue / 60 - math.floor(hue / 60)
        fs.append(f)

    rgbcolors = []
    for h, f in zip(hs, fs):
        v = 1
        p = 0
        q = 1 - f
        t = f
        if h == 0:
            color = v, t, p
        elif h == 1:
            color = q, v, p
        elif h == 2:
            color = p, v, t
        elif h == 3:
            color = p, q, v
        elif h == 4:
            color = t, p, v
        elif h == 5:
            color = v, p, q
        rgbcolors.append(color)

    return rgbcolors

Concise Implementation in Python

import math

v = 1.0
s = 1.0
p = 0.0
def rgbcolor(h, f):
    """Convert a color specified by h-value and f-value to an RGB
    three-tuple."""
    # q = 1 - f
    # t = f
    if h == 0:
        return v, f, p
    elif h == 1:
        return 1 - f, v, p
    elif h == 2:
        return p, v, f
    elif h == 3:
        return p, 1 - f, v
    elif h == 4:
        return f, p, v
    elif h == 5:
        return v, p, 1 - f

def uniquecolors(n):
    """Compute a list of distinct colors, ecah of which is
    represented as an RGB three-tuple"""
    hues = (360.0 / n * i for i in range(n))
    hs = (math.floor(hue / 60) % 6 for hue in hues)
    fs = (hue / 60 - math.floor(hue / 60) for hue in hues)
    return [rgbcolor(h, f) for h, f in zip(hs, fs)]
0
votes
for r from 0 to 255 step (255*3/N):
  for g from 0 to 255 step (255*3/N):
    for b from 0 to 255 step (255*3/N):
      ...
0
votes

I wrote this code once to solve exactly the problem you describe (the background was also white). It's the same idea as yours, only generalized. It should be easy to adapt from OCaml to your language.

Usage

You don't need to tell the function how many colors you will need. Call the function with 1, 2, ... to get color number 1, number 2, ... The colors from small numbers are wide apart, the latter colors of course become closer and closer to each other.

Code

lsl is "logical shift left", lsr "logical shift right", and ! simply means "access a reference". In OCaml RGB colors are represented in a single integer, with one byte per color.

let number_to_color n =
  let color = ref 0 in
  let number = ref n in
  for i = 0 to 7 do
    color := (!color lsl 1) +
      (if !number land 1 <> 0 then 1 else 0) +
      (if !number land 2 <> 0 then 256 else 0) +
      (if !number land 4 <> 0 then 65536 else 0);
    number := !number lsr 3
  done;
  !color