Monday, December 30, 2013

Automatic colour generation in JavaScript (with a trick!)

The task — generate a number of sufficiently different colours for display on a page.


For example, this is needed to display a lot of elements, which belong to one of several categories (say, 10­-20 categories), and in colouring them you will help the viewer to easier identify elements of the same group.


Colour Theory

The pep-talk version of it, anyway :)

In web-world we are all used to the stock RGB (Red/Green/Blue) palette, yet it is in fact not the best choice. It works well for defining brightness of each of the three-coloured pixels on our screens, but it does not actually reflect nature of colour much. Try to manually create yellow out of that, for instance. Good luck.

See, it is much easier to find differing colours on what is called a “Colour Wheel” (invented by some really smart person, I am sure), where different colour hues (tints) are signified by an angle out of 360 degrees (a circle indeed, hence a “wheel”):

Da Wheel.

If you pick a single hue value, then you can control how much of that colour is being displayed (Saturation), and how light/dark it is (Brightness). Very easy to find opposing / nearby / lighter / darker / whatever shades this way. This is why those artsy designer-types quite often (if not always) prefer an HSB format (stands for Hue / Saturation / Brightness) over RGB. This is like choosing English (a formal and precise subset of it) over Assembler programming language.

Equidistant Colours

How does it help us? It helps a lot. In order to generate several colours that are as far from each other as possible, yet still possess similar lightness — i.e. to solve our basic problem of autogenerated set of differing colours, — we, in the very simple method, need to only pick different hue “angles” with the same saturation and brightness values.

This is not precisely true, I'll explain below why, but good enough for now.

And we have a huge blessing disguised in support of HSL format (Hue / Saturation / Lightness — lightness being synonymous to “brightness”, fine by me) by all modern browsers. Yay for technological progress!

So, if we pick saturation and lightness at 80% and 70% respectively, if we need, say, 12 colours, we can just divide the 360° by 12 (yields step of 30°), and then plot them starting from zero. These values can be used as a background colour, in the following style declaration: “background-color: hsl(0, 80%, 70%)”.

Easy.

You'll see my code below, and this is the result I get from running it for 16 colours:


Not bad, huh?

The Trick

It is okay, but being the grumpy type I have to say it could be better (and it'll never be ideal, oh well). Nearby colours, especially in the green range, are too close together visually. This is important if you have a poorly tuned monitor, or view it slightly off-normal angles (on cheaper screen types).

Let's improve it. The hues are already equidistant, so you can't really gain much more contrast in picking the tint. Yet the immense variety of colours we see in real life is not achieved by hue alone!

So the small trick I propose is to alternate between slightly different saturation/brightness values for adjacent colours — say, for even colours use SL of (80%, 75%), and for odd ones use (70%, 80%).

This results in the following sequence (old one shown to the left, for comparison):

Before — After*.
*Improvement guaranteed or your money back after 15 years of following this Simple Exercise Routine™!
Better, I say. This can be tweaked further, depending on how many colours you need (perhaps you need to alternate every three colours, for example), and the actual saturation/brightness values will vary depending on the specific needs of your project — whether you need lighter or darker colours etc. Even hue range could be limited to a portion of the spectrum.

I hope this gives you a good idea and a starting point for your own experiments.

The Code

You can check the working demo in the corresponding JSFiddle (I hope it lives long and prospers!).

Here's the JS code, to give you an idea of what is happening:
$('#gen-btn').click(generate);

function generate() {
 var genQty = $('#gen-qty').val();
 
 populateList('#colour-show-1', genQty, 0);
 populateList('#colour-show-2', genQty, 1);
}

// Creates an HTML demo list with generated colours
// variant is the choice of which method to apply
//   0 - basic; 1 - with the odd/even saturation/lightness trick
function populateList(selector, qty, variant) {
 var html = "", colour;
 var step = Math.floor(360 / qty);

 for(var i = 0; i < qty; i++) {
  colour = getColour(i, step, variant);
  html += '<li style="background-color: ' + colour + ';">&nbsp;</li>';
 }

 $(selector).html(html);

 return;
}

// Returns individual colour, based on its 
//   number in the sequence, 
//   the angular step, and 
//   the chosen method 
//   (0 - without or 1 - with the saturation/lightness trick)
function getColour(num, step, variant) {
 var col;

 switch (variant) {
  case 1:
   col = 'hsl(' + (num * step).toString() + ', ' // Hue
    + ((num % 2 == 0) ? 80 : 75) + '%, ' // Saturation, even/ odd
    + ((num % 2 == 0) ? 70 : 80) + '%)'; // Lightness, even/odd
   break;
  default: col = 'hsl(' + (num * step).toString() + ', 80%, 70%)';
 }

 return col;
}

Possible Improvements

I would actually like to hear if you have any ideas on how this can be improved further. One observation I made earlier is that the colours in the green section are visually too close together — perhaps some nuance of how our (mine?) eye sees it. So we could gain better result by spreading that particular range further apart, while there is room to make red-orange, and blue-purple hues closer and still have them distinguishable.

That's just an idea, though, and may be you'll have more. For now the simple version works for me, and it may work for you. Take care!

No comments:

Post a Comment