12

Using javascript/jquery, I want to sort an array of rgba values to the colours of the visible spectrum. By doing this, like shades should be bunched together. Is there a plugin to do this or how would I go about doing it?

Spectrum image: http://www.gamonline.com/catalog/colortheory/images/spectrum.gif

Brigand
  • 84,529
  • 20
  • 165
  • 173
Andrew Deniszczyc
  • 3,298
  • 3
  • 19
  • 16
  • 2
    I think you would convert the RGB colors to HSV, and then sort by hue and the other two values as secondary keys (according to your preference). – Pointy Aug 12 '12 at 16:11

2 Answers2

16

Disclosure: I'm the author of the library recommended below.

If you don't mind using a library, here's a much more concise version of Oriol's detailed response. It uses the sc-color library:

var sorted = colorArray.sort(function(colorA, colorB) {
    return sc_color(colorA).hue() - sc_color(colorB).hue();
});
Ben
  • 494
  • 3
  • 9
  • For what I need, Oriol's method seems good to me, as this library is a bit large for what I require. In the future I may use this though as it looks very interesting, thanks! – Andrew Deniszczyc Aug 13 '12 at 19:36
  • 1
    ben, you just saved me SO MUCH TIME. Thank you thank you thank you. I've been playing around with color sorting and found that sorting by the hue, saturation and lightness produced a more visually consistent sort than just sorting by hue. Of course, I'm working with hundreds of colors, many with the same hues and saturations. – Chris Esplin Mar 06 '13 at 15:39
  • 1
    Updated links to point to the Github repo (and use the new library name) – Ben Jul 12 '15 at 20:07
14

If your array of colors is like this:

var rgbArr = [c1, c2, c3, ...]

where each color ci is an array of three numbers between 0 and 255

ci = [red, green, blue]

then, you can use this function to convert the colors to HSL

function rgbToHsl(c) {
  var r = c[0]/255, g = c[1]/255, b = c[2]/255;
  var max = Math.max(r, g, b), min = Math.min(r, g, b);
  var h, s, l = (max + min) / 2;

  if(max == min) {
    h = s = 0; // achromatic
  } else {
    var d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    switch(max){
      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
      case g: h = (b - r) / d + 2; break;
      case b: h = (r - g) / d + 4; break;
    }
    h /= 6;
  }
  return new Array(h * 360, s * 100, l * 100);
}

and sort them by hue

var sortedRgbArr = rgbArr.map(function(c, i) {
  // Convert to HSL and keep track of original indices
  return {color: rgbToHsl(c), index: i};
}).sort(function(c1, c2) {
  // Sort by hue
  return c1.color[0] - c2.color[0];
}).map(function(data) {
  // Retrieve original RGB color
  return rgbArr[data.index];
});

Here is a runnable example (thanks Ionică Bizău):

function display(container, arr) {
  container = document.querySelector(container);
  arr.forEach(function(c) {
    var el = document.createElement("div");
    el.style.backgroundColor = "rgb(" + c.join(", ") + ")";
    container.appendChild(el);
  })
}
function rgbToHsl(c) {
  var r = c[0] / 255,
      g = c[1] / 255,
      b = c[2] / 255;
  var max = Math.max(r, g, b),
      min = Math.min(r, g, b);
  var h, s, l = (max + min) / 2;

  if (max == min) {
    h = s = 0; // achromatic
  } else {
    var d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    switch (max) {
      case r:
        h = (g - b) / d + (g < b ? 6 : 0);
        break;
      case g:
        h = (b - r) / d + 2;
        break;
      case b:
        h = (r - g) / d + 4;
        break;
    }
    h /= 6;
  }
  return new Array(h * 360, s * 100, l * 100);
}

var rgbArr = [];
for (var i = 0; i < 100; ++i) {
  rgbArr.push([
    Math.floor(Math.random() * 256),
    Math.floor(Math.random() * 256),
    Math.floor(Math.random() * 256)
  ]);
}
display("#before", rgbArr);

var sortedRgbArr = rgbArr.map(function(c, i) {
  // Convert to HSL and keep track of original indices
  return {color: rgbToHsl(c), index: i};
}).sort(function(c1, c2) {
  // Sort by hue
  return c1.color[0] - c2.color[0];
}).map(function(data) {
  // Retrieve original RGB color
  return rgbArr[data.index];
});
display("#after", sortedRgbArr);
#before > div,
#after > div {
  width: 1%;
  height: 20px;
  display: inline-block;
}
Random colors: <div id="before"></div>
Same colors, sorted by hue: <div id="after"></div>

sortedRgbArr will contain the rgb colors of rgbArr sorted more or less like the colors of the visible spectrum.

The problem is that the HSL spectrum looks like this:

HSL spectrum

Your spectrum is weird because it doesn't have all colors, such as pink.

I guess that's because pink doesn't exist in the nature, it's a combination of the colors of the opposite extremes of light's spectrum. But we have it in rgb, so you have to decide where do you want it.

Moreover, it seems that your spectrum goes from lower to higher wavelength, not frequency. But then your spectrum is a reverse of HSL's spectrum.

Replace c1.color[0] - c2.color[0] with c2.color[0] - c1.color[0] if you want it like your spectrum.

Community
  • 1
  • 1
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • Thanks! The soectrum image was just as a guide to help explain what I was trying achieve. You have helped me lots, so thank you! – Andrew Deniszczyc Aug 13 '12 at 09:13
  • This is a great answer! Tho, I have a problem that the transition is not as smooth like in the spectrum: https://jsfiddle.net/3s5hv9un/3/ How to solve that? I think it's related on the luminosity. – Ionică Bizău Jul 19 '16 at 08:41
  • @IonicăBizău My answers sorts by hue, but HSL also has saturation and value. You could for example have an almost unsaturated red and an almost unsaturated green. They could look like almost the same gray, but be sorted in different places. – Oriol Jul 19 '16 at 15:39
  • I feel like sorting by hue is not enough. Is there another way to sort the colors in the rainbow color order? Thanks! – Ionică Bizău Jul 19 '16 at 19:24
  • @IonicăBizău Then you should define your desired color space, transform the RGB colors to that space, and sort there. There is no obvious answer. – Oriol Jul 19 '16 at 19:29