Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to calculate the proportional color between the three given with a percentage?

I have three hexadecimal colors introduced by the user, for example:

#39bf26
#c7c228
#C7282E

Then they have to choose an percentaje between 1 and 100.

  • If the percentaje is 100, the color returned would be the first color inserted: #39bf26.
  • If the percentaje is 50, the color returned would be the second color inserted: #c7c228.
  • If the percentaje is 1, the color returned would be the third color inserted: #C7282E.
  • And finally, if the percentage is between of any of the previous values, then the color returned would be a proportional mix of the two first colors when percentaje < 50 and a mix of the two last colors if percentaje > 50.

I found an similar algorithm here: http://www.awcore.com/js/snippets/161/from-red-to-green-color-map-depend-on-percentage_en

But I'm afraid I haven't experience in color algorithms, so I expected you could advise or guide me a little.

If you need more info let me know and i'll edit the post.

like image 871
mllamazares Avatar asked Dec 02 '25 10:12

mllamazares


2 Answers

Unnecessary overkill - introduce a Colour object to parse, .scale and .add hex colours

var Colour = (function () {
    function limit(x) {
        if (x > 255) return 255;
        if (x < 0) return 0;
        return Math.floor(x);
    }
    function toHex(r, g, b) {
        if (r > 15) r = r.toString(16);
        else r = '0' + r.toString(16);
        if (g > 15) g = g.toString(16);
        else g = '0' + g.toString(16);
        if (b > 15) b = b.toString(16);
        else b = '0' + b.toString(16);
        return '#' + (r + g + b).toUpperCase();
    }
    function Colour(hex) {
        if (hex.length === 7 || hex.length === 4) hex = hex.slice(1);
        if (hex.length === 3)
            hex = hex.charAt(0) + hex.charAt(0)
                + hex.charAt(1) + hex.charAt(1)
                + hex.charAt(2) + hex.charAt(2);
        this.hex = '#' + hex.toUpperCase();
        this.r = parseInt(hex.slice(0, 2), 16);
        this.g = parseInt(hex.slice(2, 4), 16);
        this.b = parseInt(hex.slice(4, 6), 16);
    }
    Colour.prototype.scale = function (x) {
        this.r = limit(this.r * x);
        this.g = limit(this.g * x);
        this.b = limit(this.b * x);
        this.hex = toHex(this.r, this.g, this.b);
        return this;
    };
    Colour.prototype.add = function (c) {
        return new Colour(
            toHex(
                limit(this.r + c.r),
                limit(this.g + c.g),
                limit(this.b + c.b)
            )
        );
    };
    Colour.prototype.toString = function () {
        return this.hex;
    };
    Colour.prototype.valueOf = Colour.prototype.toString;
    return Colour;
}());

Then introduce your colours;

var myColours = [
    new Colour('#39bf26'), // Colour {hex: "#39BF26", r: 57, g: 191, b: 38, …}
    new Colour('#c7c228'), // Colour {hex: "#C7C228", r: 199, g: 194, b: 40, …}
    new Colour('#C7282E')  // Colour {hex: "#C7282E", r: 199, g: 40, b: 46, …}
];

Now write a function for percent logic

function percent(x, col) {
    var factor;
    if (x < 50) {
        factor = (50 - x) / 50;
        return col[0].scale(factor).add(col[1].scale(1-factor));
    } else {
        factor = (100 - x) / 50;
        return col[2].scale(factor).add(col[1].scale(1-factor));
    }
}

And use it

percent(25, myColours); // Colour {hex: "#7FC027", r: 127, g: 192, b: 39, …}
percent(75, myColours); // Colour {hex: "#C6752B", r: 198, g: 117, b: 43, …}

Unfortunately (unless you want to introduce new properties) this will suffer from small rounding errors, as seen in the 75% result (C6 not C7 for red, because C7 = 199, 199 / 2 = 99.5 => 99, then 99 + 99 = 198 => C6).

like image 61
Paul S. Avatar answered Dec 04 '25 22:12

Paul S.


Ok, here is the solution:

function getColorForPercentage(pct) {
    pct /= 100;

    var percentColors = [
            { pct: 0.01, color: { r: 0xdd, g: 0x51, b: 0x4c } },
            { pct: 0.5, color: { r: 0xfa, g: 0xa7, b: 0x32 } },
            { pct: 1.0, color: { r: 0x5e, g: 0xb9, b: 0x5e }} ];

    for (var i = 0; i < percentColors.length; i++) {
        if (pct <= percentColors[i].pct) {
            var lower = percentColors[i - 1] || { pct: 0.1, color: { r: 0x0, g: 0x00, b: 0 } };
            var upper = percentColors[i];
            var range = upper.pct - lower.pct;
            var rangePct = (pct - lower.pct) / range;
            var pctLower = 1 - rangePct;
            var pctUpper = rangePct;
            var color = {
                r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
                g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
                b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
            };
            return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';
        }
    }
}
like image 26
mllamazares Avatar answered Dec 05 '25 00:12

mllamazares