Generate colors between red and green for an input range [duplicate]

Possible Duplicate:
Color coding based on number

I want for a user to be able to select from a range from 1-100, where as the numbers become less than 50, the color of the area becomes darker green, and as the color becomes closer to 100, the color becomes more red.

I am trying to make it so that as the range in more towards the center, the color should be close to white (where 50 = full white).

I tried the answer from here: Generate colors between red and green for a power meter? to no avail.... 50 ends up being a muddled green...

I have the following html:

<span><span class="value">50</span><input type="range" /></span>​

And the following javascript:

$(document).on({
    change: function(e) {

        var self = this,
            span = $(self).parent("span"),
            val = parseInt(self.value);
        if (val > 100) {
            val = 100;
        }
        else if (val < 0) {
            val = 0;
        }
        $(".value", span).text(val);
        var r = Math.floor((255 * val) / 100),
            g = Math.floor((255 * (100 - val)) / 100),
            b = 0;
        span.css({
            backgroundColor: "rgb(" + r + "," + g + "," + b + ")"
        });
    }
}, "input[type='range']");​

Fiddle: http://jsfiddle.net/maniator/tKrM9/1/

I have tried many different combinations of r,g,b but I really cannot seem to get it right.


You're getting the muddled green because of the way you're creating your gradient in RBG space. To get a "cleaner" gradient, you can use the HSV model as mentioned in the answer of the question you linked to.

RGB gradient (top) vs HSV (bottom)top gradient uses RGB, bottom uses HSV

By scaling the H (hue) value between 0 (red) and 120 (green) you'll get a nice clean transition. However, at the mid point (60) you'll end up with bright yellow instead of your intended white. You can address this by modifying the S (saturation) value -- at 0 saturation, you'll end up with white (1 gives you full colour saturation.

Here's a crude example which scales the saturation from 1 to 0 and back to 1 as the input value goes from 0 to 50 to 100 - http://jsfiddle.net/xgJ2e/2/

var hue = Math.floor((100 - val) * 120 / 100);  // go from green to red
var saturation = Math.abs(val - 50)/50;   // fade to white as it approaches 50

p.s. Converting between colour models is easy using jquery-colors, although it's not too hard to roll your own.


I came up with this answer with some help from here, which uses an Interpolate function in which I can set the start and end colors easily.

function Interpolate(start, end, steps, count) {
    var s = start,
        e = end,
        final = s + (((e - s) / steps) * count);
    return Math.floor(final);
}

function Color(_r, _g, _b) {
    var r, g, b;
    var setColors = function(_r, _g, _b) {
        r = _r;
        g = _g;
        b = _b;
    };

    setColors(_r, _g, _b);
    this.getColors = function() {
        var colors = {
            r: r,
            g: g,
            b: b
        };
        return colors;
    };
}

$(document).on({
    change: function(e) {

        var self = this,
            span = $(self).parent("span"),
            val = parseInt(self.value),
            red = new Color(232, 9, 26),
            white = new Color(255, 255, 255),
            green = new Color(6, 170, 60),
            start = green,
            end = white;

        $(".value", span).text(val);

        if (val > 50) {
            start = white,
                end = red;
            val = val % 51;
        }
        var startColors = start.getColors(),
            endColors = end.getColors();
        var r = Interpolate(startColors.r, endColors.r, 50, val);
        var g = Interpolate(startColors.g, endColors.g, 50, val);
        var b = Interpolate(startColors.b, endColors.b, 50, val);

        span.css({
            backgroundColor: "rgb(" + r + "," + g + "," + b + ")"
        });
    }
}, "input[type='range']");​

Fiddle: http://jsfiddle.net/maniator/tKrM9/53/


In a very hand wavy way, I think I would do it this way:

Make the range from 1-50 have maximum green (FF), and a scaled value for both red and blue ranging from 00 for red and blue at 1, all the way up to FF for red and blue at 50.

(Sample values: 1 -> 00FF00, 25 -> 7FFF7F, 50 -> FFFFFF)

Then from 51-100, keep red at FF, while scaling back blue and green so that blue and green approach 0 as you reach 100.

(Sample values: 51 -> FFFFFF, 75 -> FF7F7F, 100 -> FF0000)

This is guaranteed to give you brilliant green at 0, white at 50, and red at 100.

The areas in between may not be exactly perfect, simply because the eye interprets different color intensities differently (we're better at some colors than others), but it should get you pretty close.

I modified the code in your fiddle to the following, and it does what I describe:

$(document).on({
    change: function(e) {

        var self = this,
            span = $(self).parent("span"),
            val = parseInt(self.value);
        if (val > 100) {
            val = 100;
        }
        else if (val < 0) {
            val = 0;
        }
        $(".value", span).text(val);
        if (val <= 50)
        {
            r = Math.floor((255 * (val / 50))),
            g = 255,
            b = Math.floor((255 * (val / 50)));
        }
        else
        {
            r = 255,
            g = Math.floor((100 - val) / 50 * 255),
            b = Math.floor((100 - val) / 50 * 255);
        }
        span.css({
            backgroundColor: "rgb(" + r + "," + g + "," + b + ")"
        });
    }
}, "input[type='range']");