tags:

views:

334

answers:

3

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 ie 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.

A: 

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
Pascal Cuoq
A: 
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):
      ...
BlueRaja - Danny Pflughoeft
+1  A: 

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)]
Wesley
After the first 20 colors, it becomes a little wasteful not to take advantage of the colors with saturation and/or lightness different from 1.
Pascal Cuoq
That's absolutely true. It would be better to generate a set of colours for s = 1, v = 1, then perhaps s = 0.5, v = 1, and so on. Presumably, there could be an algorithmic way to do this, but there would have to be checks in place to avoid generating a lot of near-gray or near-black colors that are hard to tell apart.
Wesley
if f = (h / 60 - math.floor(h / 60)) won't floor(h / 60) always be 0 since h is between 0 and 5? If that is true f is between 0 and 5/60. Is that correct?
Stuart
Great catch, Suart - I meant to write "hue" there. Thanks!
Wesley
In the concise implementation of uniquecolors the hues object should not be a generator since the other generators will consume the same iterator resulting in only half of the expected colors. Changing the line to hues = [...] fixes this.
pkit