22

What is the best way to pick random colors for a bar chart / histogram such that each color is different from the other.. and possibly in contrast

The most talked about way is

'#'+(Math.random()*0xFFFFFF<<0).toString(16);

but this can generate similar colors.. and sometimes distinguishing them might be a problem.. Example enter image description here

Kartik Dinesh
  • 253
  • 1
  • 2
  • 6
  • 1
    Any method to pick random colours is sometimes going to generate similar colours. If you never want them to be similar just prepare a list of suitable colours and pick from it (at random if you like). – James Gaunt Jul 25 '11 at 22:51
  • 2
    The biggest reason why you don't want to use the method suggested is that what people visually perceive as the color spectrum is not linearly proportional to the hex color spectrum. That is why the ["web safe colors"](http://www.w3schools.com/html/html_colors.asp) don't look anything like a rainbow. – Nicole Jul 25 '11 at 22:52
  • 1
    http://phrogz.net/tmp/24colors.html – Phrogz Jul 25 '11 at 22:53

6 Answers6

25

I would generate colors using HSV (hue, saturation, value) instead of RGB. In HSV, the color is defined by the hue, ranging from 0-360. Thus, if you want e.g. 6 different colors, you can simply divide 360 by 5 (because we want to include 0) and get 72, so each color should increment with 72. Use a function like this one to convert the generated HSV color to RGB.

The following function returns an array of total different colors in RGB format. Note that the colors won't be "random" in this example, as they will always range from red to pink.

function randomColors(total)
{
    var i = 360 / (total - 1); // distribute the colors evenly on the hue range
    var r = []; // hold the generated colors
    for (var x=0; x<total; x++)
    {
        r.push(hsvToRgb(i * x, 100, 100)); // you can also alternate the saturation and value for even more contrast between the colors
    }
    return r;
}
Håvard
  • 9,900
  • 1
  • 41
  • 46
  • 5
    +1, But I'm not a fan of the function name - as you say, there is no randomisation going on. – UpTheCreek Mar 26 '13 at 15:55
  • 1
    Here's a [JSFiddle showing the implementation](https://jsfiddle.net/aldass/9f0yadfy/) above. I've made some small modifications. – Al Dass Jan 27 '16 at 05:54
8

The best way is to convert from HSV values. You can divide the maximum value of "Hue" by the amount of colors you need and then increment by this result.

For improved contrast, you can also alternate between high and low values of lightness.

slaphappy
  • 6,894
  • 3
  • 34
  • 59
  • 3
    However, as @Renesis says, evenly dividing the hue is not the same as picking visually-distinct colors--we are far less sensitive to variations of green compared to our sensitivity to red and (to a lesser extent) blue. Differentiating grasses and leaves is less important than seeing fire. – Phrogz Jul 25 '11 at 22:54
  • Good point, this method is only valid if you need a small amount of different colors. – slaphappy Jul 25 '11 at 22:55
5

The existing answers which mention the Hue, Saturation, Value representation of colors are very elegant, are closer to how humans perceive color, and it is probably best to follow their advice. Also creating a long precalculated list of colors and choosing subsets of them as needed is fast and reliable.

However, here is some code that answers your question directly: it will generate random colors in RGB that are sufficiently different. There are two drawbacks to this technique that I can see. First, these colors are really random and could look kind of gross together, and second it might take a while for the code to stumble on colors that work, depending on how "far apart" you require the colors to be.

function hex2rgb(h) {
    return [(h & (255 << 16)) >> 16, (h & (255 << 8)) >> 8, h & 255];
}
function distance(a, b) {
    var d = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
    return Math.sqrt((d[0]*d[0]) + (d[1]*d[1]) + (d[2]*d[2]));
}
function freshColor(sofar, d) {
    var n, ok;
    while(true) {
        ok = true;
        n = Math.random()*0xFFFFFF<<0;
        for(var c in sofar) {
            if(distance(hex2rgb(sofar[c]), hex2rgb(n)) < d) {
                ok = false;
                break;
            }
        }
        if(ok) { return n; }
    }
}
function getColors(n, d) {
    var a = [];
    for(; n > 0; n--) {
        a.push(freshColor(a, d));
    }
    return a;
}

The distance between colors is the Euclidean distance measured by the R, G, and B components. Thus the furthest that two colors (black and white) can be is about 441.67.

To use this code, call getColors where the first parameter is the number of colors, and the second is the minimum distance between any two of them. It will return an array of numerical RGB values.

Joe Nelson
  • 549
  • 5
  • 12
3

I like using hsl values for specifying colour this way.

So

"color: hsl(" + getRandomArbitary(0, 360) + ", 50%, 50%)";

would give you random results, but that won't give you your distinct separations. So I'd base it on the i value of a loop. Something like,

for (var i = 0; i < whateverYourValue; i += 1) {
    color = "color: hsl(" + i * 10 + ", 50%, 50%)";

    // set your colour on whatever
}

obviously the above is indicative, and not valid code on it's own.

Want to know more on hsl? Check http://mothereffinghsl.com/ 'cause, you know, it's fun.

Nicholas Evans
  • 2,194
  • 2
  • 16
  • 18
2
'#'+(Math.random()*0xFFFFFF<<0).toString(16);

Isn't the best method to use because it can generate values like #4567 which is missing two digits instead of generating #004567

It's better to pick each character individually like:

'#'+Math.floor(Math.random()*16).toString(16)+
    Math.floor(Math.random()*16).toString(16)+
    Math.floor(Math.random()*16).toString(16)+
    Math.floor(Math.random()*16).toString(16)+
    Math.floor(Math.random()*16).toString(16)+
    Math.floor(Math.random()*16).toString(16);

But that can easily be reduced to picking three numbers since hex colours can be shortened. IE. #457 == #445577

Then if you want to decrease the number of posibilities and widen the gap between them you can use:

'#'+(5*Math.floor(Math.random()*4)).toString(16)+
    (5*Math.floor(Math.random()*4)).toString(16)+
    (5*Math.floor(Math.random()*4)).toString(16);

Which divides the number of choices for each color by 5, and then evens out the distribution equally.

Paul
  • 139,544
  • 27
  • 275
  • 264
  • See my comment on the question for a longer explanation, but this is bad, because [web safe colors](http://www.w3schools.com/html/html_colors.asp) (colors divided evenly across RGB values) are ugly. – Nicole Jul 25 '11 at 23:04
1

I second what kbok and Harpyon say about working in HSV colorspace, and this little library makes it super easy to switch between RGB and HSV - and others.

fvu
  • 32,488
  • 6
  • 61
  • 79