5

I am building a star visualisation engine, and need to interpolate values received from the API. The HSL colour stops are:

-.63, hsl: 228° 100% 80%,
.165, hsl: 224° 100% 90%,
.33, hsl: 240° 100% 98%,
.495, hsl: 64° 100% 92%,
.66, hsl: 52° 100% 82%,
.825, hsl: 28° 100% 66%,
2.057, hsl: 6° 95% 65%,

Each star will return a colour value between -.63 and 2.057, I need a function that takes that value and gets a colour along the spectrum comprised of the above stops.

I've had some previous help but cannot for the life of me figure this out, and haven't been able to find a concise tutorial or walkthrough of interpolating HSL values. The only way I can figure this out is via an external lib, but that seems a ridiculous solution for something relatively simple. Any help would be appreciated!

j_d
  • 2,818
  • 9
  • 50
  • 91
  • I don't see a concrete question. What is the "this" that you need figuring out? Linear interpolation? – Jongware Jun 13 '15 at 11:12
  • @Jongware Yes, I've updated the question for clarity. – j_d Jun 13 '15 at 11:15
  • Okay, thanks - much better. Your HSL array looks strange, though. May we assume these values are fixed, so we can write them down in a more common Javascript notation? – Jongware Jun 13 '15 at 11:19
  • @Jongware Yes, they are fixed. Sorry, I am new to HSL colours, haven't used them before. As I understand it, they interpolate with less distortion than hex or rgb(?). – j_d Jun 13 '15 at 11:31
  • It depends on what you call *distortion*. Interpolation works along the axes you hand it over, and in the case of HSL one of these is *hue*, which looks more natural to humans than the same with RGB. – Jongware Jun 13 '15 at 11:36
  • @Jongware I see. So given the three values of HSL, I take it to interpolate will require receiving a value (eg 0.5) and generating three values (H, S, L) for it. – j_d Jun 13 '15 at 11:43

2 Answers2

4

A straightforward linear interpolation function in HSB space:

function get_color_for(value) {
    var i, h,s,b,stops = [
        [-0.63, [228, 100, 80]],
        [0.165, [224, 100, 90]],
        [0.33, [240, 100, 98]],
        [0.495, [64, 100, 92]],
        [0.66, [52, 100, 82]],
        [0.825, [28, 100, 66]],
        [2.057, [6, 95, 65]]
    ];
    if (value <= stops[0][0]) return stops[0][1];
    if (value >= stops[stops.length - 1][0]) return stops[stops.length - 1][1];
    i = 0;
    while (value > stops[i][0])
       i++;
    value -= stops[i-1][0];
    value /= (stops[i][0]-stops[i-1][0]);
    h = stops[i-1][1][0]+(stops[i][1][0]-stops[i-1][1][0])*value;
    s = stops[i-1][1][1]+(stops[i][1][1]-stops[i-1][1][1])*value;
    b = stops[i-1][1][2]+(stops[i][1][2]-stops[i-1][1][2])*value;
    return [h,s,b];
}

For values lower than the minimum input it always will return the first, and for higher than the maximum, the last color set.

The returned value is an HSL array, which can be used immediately in CSS. If your environment needs RGB colors you can use an hsl-to-rgb conversion function, such as in this earlier SO question. Check what your output device expects for the RGB range: 0.0 to 1.0, 0 to 100, or 0 to 255.

Note that this function cannot be used for hsb in general. The problem is the hue part: this wraps around at 360 (degrees), and so attempting to interpolate between 350 and 10 will make it return cyan (the value around 170) instead of red (the value at 0).

Here is a jsfiddle, displaying the output for a range of numbers.

Community
  • 1
  • 1
Jongware
  • 22,200
  • 8
  • 54
  • 100
1

RGB interpolation would work better for your color scale, HSL adds a greenish color between blue and yellow:

enter image description here

RGB interpolation:

function lerp(start, end, t) {
    return (1 - t) * start + t * end;
}

function getColor(t) {
    var colors = [
        [153, 173, 255], [204, 218, 255], [245, 245, 255],
        [252, 255, 214], [255, 243, 163], [255, 163, 82],
        [251, 98,  81]
    ];
    var domain = [-0.63, 0.165, 0.33, 0.495, 0.66, 0.825, 2.057];

    if (t <= domain[0]) {
        return colors[0];
    }

    if (t >= domain[domain.length - 1]) {
        return colors[colors.length - 1];
    }

    for (var i = 0; t > domain[i]; i++);

    var from = colors[i - 1];
    var to = colors[i];
    t = (t - domain[i - 1]) / (domain[i] - domain[i - 1]);
    return [
        lerp(from[0], to[0], t),
        lerp(from[1], to[1], t),
        lerp(from[2], to[2], t)
    ];
}

Drawing it:

function rgbToCSS(rgb) {
    return 'rgb(' + Math.round(rgb[0]) + ',' +
                    Math.round(rgb[1]) + ',' +
                    Math.round(rgb[2]) + ')';
}

var canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 100;
var context = canvas.getContext('2d');
for (var i = 0; i < canvas.width; i++) {
    context.fillStyle = rgbToCSS(getColor(lerp(-0.63, 2.057, i / canvas.width)));
    context.fillRect(i, 0, 1, canvas.height);
}
document.body.appendChild(canvas);

JSFiddle

Alexey Lebedev
  • 11,988
  • 4
  • 39
  • 47
  • I spotted that greenish tint as well, but decided my astrophysics is not strong enough to decide whether stars can actually have that color (Jack Vance mentions at least one in one of his novels; I don't know if that counts). As I am fairly sure they don't shine in RGB space, I left it uncommented. Still: +1 for an also good answer! – Jongware Jun 13 '15 at 15:06
  • @Jongware astronomers didn't find green stars so far, the color goes from blue to white, and then to yellow https://en.wikipedia.org/wiki/Stellar_classification#Modern_classification – Alexey Lebedev Jun 13 '15 at 20:00