find the value within a range using percent - javascript

I have a range of number
40 to 255 where 40 is 0% and 255 is 100%
how can I get the number which is 50% ?
I tried searching for a way to do this but can't find any.

You have (255-40) = 100%
1% = (255-40)/100
50% = ((255-40)/100) * 50 = 107.5
Since you have offset of 40 at the beginning, 50% from offset = 107.5 + 40 = 147.5

Extending #Kiran s answer with some generic Javascript
const rangeMin = 40;
const rangeMax = 255;
const percentage = 50;
const valueFromPercentage = (((rangeMax - rangeMin) / 100) * percentage ) + rangeMin;
const rangeMin = 40;
const rangeMax = 255;
const value = 147;
const percentageFromValue = ((value - rangeMin) / (rangeMax - rangeMin)) * 100;

Any number c in an interval [a, b] can be written as a linear combination of the two endpoints c = a*x + b*(1-x) for some x ∈ [0, 1].
For [a, b] = [0, 100], the number c = 50 can be written as 0*0.5 + 100*0.5 so x = 0.5.
If the interval [0, 100] is linearly mapped to [40, 255], the ratio x = 0.5 will stay the same, and so c gets mapped to c' = 40*0.5 + 255*0.5 = (40 + 255)/2 = 147.5.

Use the following
(255 - 40)/2 + 40

Related

Get exact color from two colors with percentage in javascript

I have a percentage variable and two colors in javascript.
Ex. One color is #D7F0FE & another color is #3DB6FC
Percentage variable is 30 (%)
I need to get exact color from these three values.
If percentage is zero then it should be #D7F0FE and if it's 100 then it should be #3DB6FC.
Need to find the best way to do that
I recommend to use RGB value as this value define with numbers.
Such as
#D7F0FE -> rgb(215, 240, 254)
#3DB6FC -> rgb(61, 182, 252)
Now do some math to generate rgb value dynamically based on percentage.
Please check the following code, it generate rgb value depending on Input Percentage.
<html>
<head>
</head>
<body id="body">
<input type="number" id="input-p">
<button onclick="changePercentage()">Percentage: </button>
<script>
var body = document.getElementById('body');
var rgb1 = [215, 240, 254];
var rgb2 = [61, 182, 252];
var rgb = [];
var p = 0;
var r = (rgb2[0] - rgb1[0])/100;
var g = (rgb2[1] - rgb1[1])/100;
var b = (rgb2[2] - rgb1[2])/100;
var color = 'rgb(215, 240, 254)';
body.style.background = color;
function newColor(){
console.log(p * r);
rgb = [
Math.ceil(rgb1[0] + (p * r)),
Math.ceil(rgb1[1] + (p * g)),
Math.ceil(rgb1[2] + (p * b))
];
color = 'rgb('+rgb[0]+','+rgb[1]+','+rgb[2]+')';
body.style.background = color;
}
function changePercentage(){
p = document.getElementById('input-p').value;
newColor();
}
</script>
</body>
</html>
not optimized
Here is a clean solution based on this comment here:
/** eg. gradient("#FF0000", "#0000FF", 0.5) => "#800080" */
function gradient(color1, color2, ratio) {
const from = rgb(color1)
const to = rgb(color2)
const r = Math.ceil(from.r * ratio + to.r * (1 - ratio));
const g = Math.ceil(from.g * ratio + to.g * (1 - ratio));
const b = Math.ceil(from.b * ratio + to.b * (1 - ratio));
return "#" + hex(r) + hex(g) + hex(b);
}
/** eg. rgb("#FF0080") => { r: 256, g: 0, b: 128 } */
function rgb(color) {
const hex = color.replace("#", "")
return {
r: parseInt(hex.substring(0, 2), 16),
g: parseInt(hex.substring(2, 4), 16),
b: parseInt(hex.substring(4, 6), 16),
}
}
/** eg. hex(123) => "7b" */
function hex(num) {
num = num.toString(16);
return (num.length == 1) ? '0' + num : num;
}

Calculating the length of a segment in a logarithmic scale

I want to calculate the length of a line for a series of events.
I'm doing this with the following code.
var maxLineLength = 20;
var lineLen = function(x, max) {
return maxLineLength * (x / max);
}
var events = [0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
console.log(lineLen(x, max));
});
This works, but I'm using linear scaling, while I'd like to use logarithms, because I don't want small events to become too small numbers when big ones are present.
I modified the lineLen function as you can see below, but - obviously - it doesn't work for events equals to one, because the log of one is zero. I want to show events equals to one (opportunely scaled) and not make them become zero. I also need positive numbers to remain positive (0.1 becomes a negative number)
How should I modify lineLen to use a logarithmic scale?
var maxLineLength = 20;
var lineLen = function(x, max) {
return maxLineLength * (Math.log(x) / Math.log(max));
}
var events = [0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
console.log(lineLen(x, max));
});
You can take log(x+1) instead of log(x), that doesn't change the value too much and the ratios are maintained for smaller numbers.
var maxLineLength = 20;
var lineLen = (x, max) => maxLineLength * Math.log(x+1)/Math.log(max+1);
var events = [ 0.1, 1, 5, 20, 50];
var visualizer = function(events){
var max = Math.max.apply(null, events);
return events.reduce((y, x) => {
y.push(lineLen(x, max));
return y;
}, []);
};
console.log(visualizer(events));
You can use an expression like Math.pow(x, 0.35) instead of Math.log(x). It keeps all values positive, and gives the behavior that you want for small ratios. You can experiment with different exponent values in the range (0,1) to find the one that fits your needs.
var maxLineLength = 20;
var exponent = 0.35;
var lineLen = function(x, max) {
return maxLineLength * Math.pow(x/max, exponent);
}
var events = [0, 0.01, 0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
console.log(lineLen(x, max));
});
You could decrement maxLineLength and add one at the end of the calculation.
For values smaller than one, you could use a factor which normalizes all values relative to the first value. The start value is always one, or in terms of logaritmic view, zero.
var maxLineLength = 20,
lineLen = function(max) {
return function (x) {
return (maxLineLength - 1) * Math.log(x) / Math.log(max) + 1;
};
},
events = [0.1, 1, 5, 20, 50],
normalized = events.map((v, _, a) => v / a[0]),
max = Math.max.apply(null, normalized),
result = normalized.map(lineLen(max));
console.log(result);
console.log(normalized);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You are scaling these numbers. Your starting set is the domain, what you end up with is the range. The shape of that transformation, it sounds like, will be either log or follow a power.
It turns out this is a very common problem in many fields, especially data visualization. That is why D3.js - THE data visualization tool - has everything you need to do this easily.
const x = d3.scale.log().range([0, events]);
It's the right to for the job. And if you need to do some graphs, you're all set!
Shorter but not too short; longer but not too long.
Actually I met the same problem years ago, and gave up for this (maybe?).
I've just read your question here and now, and I think I've just found the solution: shifting.
const log = (base, value) => (Math.log(value) / Math.log(base));
const weights = [0, 0.1, 1, 5, 20, 50, 100];
const base = Math.E; // Setting
const shifted = weights.map(x => x + base);
const logged = shifted.map(x => log(base, x));
const unshifted = logged.map(x => x - 1);
const total = unshifted.reduce((a, b) => a + b, 0);
const ratio = unshifted.map(x => x / total);
const percents = ratio.map(x => x * 100);
console.log(percents);
// [
// 0,
// 0.35723375538333857,
// 3.097582209424984,
// 10.3192042142806,
// 20.994247877004888,
// 29.318026542735115,
// 35.91370540117108
// ]
Visualization
The smaller the logarithmic base is, the more they are adjusted;
and vice versa.
Actually I don't know the reason. XD
<!DOCTYPE html>
<html>
<head>
<meta name="author" content="K.">
<title>Shorter but not too short; longer but not too long.</title>
<style>
canvas
{
background-color: whitesmoke;
}
</style>
</head>
<body>
<canvas id="canvas" height="5"></canvas>
<label>Weights: <input id="weights" type="text" value="[0, 0.1, 100, 1, 5, 20, 2.718, 50]">.</label>
<label>Base: <input id="base" type="number" value="2.718281828459045">.</label>
<button id="draw" type="button">Draw</button>
<script>
const input = new Proxy({}, {
get(_, thing)
{
return eval(document.getElementById(thing).value);
}
});
const color = ["tomato", "black"];
const canvas_element = document.getElementById("canvas");
const canvas_context = canvas_element.getContext("2d");
canvas_element.width = document.body.clientWidth;
document.getElementById("draw").addEventListener("click", _ => {
const log = (base, value) => (Math.log(value) / Math.log(base));
const weights = input.weights;
const base = input.base;
const orig_total = weights.reduce((a, b) => a + b, 0);
const orig_percents = weights.map(x => x / orig_total * 100);
const adjusted = weights.map(x => x + base);
const logged = adjusted.map(x => log(base, x));
const rebased = logged.map(x => x - 1);
const total = rebased.reduce((a, b) => a + b, 0);
const ratio = rebased.map(x => x / total);
const percents = ratio.map(x => x * 100);
const result = percents.map((percent, index) => `${weights[index]} | ${orig_percents[index]}% --> ${percent}% (${color[index & 1]})`);
console.info(result);
let position = 0;
ratio.forEach((rate, index) => {
canvas_context.beginPath();
canvas_context.moveTo(position, 0);
position += (canvas_element.width * rate);
canvas_context.lineTo(position, 0);
canvas_context.lineWidth = 10;
canvas_context.strokeStyle = color[index & 1];
canvas_context.stroke();
});
});
</script>
</body>
</html>
As long as your events are > 0 you can avoid shifting by scaling the events so the minimum value is above 1. The scalar can be calculated based on a minimum line length in addition to the maximum line length you already have.
// generates an array of line lengths between minLineLength and maxLineLength
// assumes events contains only values > 0 and 0 < minLineLength < maxLineLength
function generateLineLengths(events, minLineLength, maxLineLength) {
var min = Math.min.apply(null, events);
var max = Math.max.apply(null, events);
//calculate scalar that sets line length for the minimum value in events to minLineLength
var mmr = minLineLength / maxLineLength;
var scalar = Math.pow(Math.pow(max, mmr) / min, 1 / (1 - mmr));
function lineLength(x) {
return maxLineLength * (Math.log(x * scalar) / Math.log(max * scalar));
}
return events.map(lineLength)
}
var events = [0.1, 1, 5, 20, 50];
console.log('Between 1 and 20')
generateLineLengths(events, 1, 20).forEach(function(x) {
console.log(x)
})
// 1
// 8.039722549519123
// 12.960277450480875
// 17.19861274759562
// 20
console.log('Between 5 and 25')
generateLineLengths(events, 5, 25).forEach(function(x) {
console.log(x)
})
// 5
// 12.410234262651711
// 17.58976573734829
// 22.05117131325855
// 25

Calculating color value (r,g,b) using javascript

I have x amounts of arrays containing speed values.
The values are in m/s and I don't know how many there are and how large they are. I need to create a function to decide on what color to use.
My idea was to find the max and min value of the array (the speeds), and since I know the values are all dividable with 0.25 I wanted to count the possible amount of "steps" by doing var steps = (max - min) / 0.25
Since I have an RGB spectrum I thought I could somehow calculate what value to use, but I simply can't wrap my head around what to do.
What I want to be able to do was to have slower speeds be red'ish, medium speeds to be green'ish and fast speeds to be blue'ish.
An example could be that I have an array:
speeds = [0.5, 0.5, 0.75, 1.25, 0.50, 0.75, 1.00, 4.00, 4.50, 8.00, 7.50, 8.50, 6.50, 6.00, 5.00, 5.25, 4.75, 4.00, 3.25, 2.50, 1.25, 0.00]
Now, for each value I have I want to calculate a color where the largest values will be more intense the larger they are (in the blue spectrum - something like (0, 0, 255)) while the smaller values will be more intense (in the red spectrum - (255, 0, 0)) the lower they are. And for the middle values I thought they could more intense in the green color (0, 255, 0) if it is absolutely in the middle, and then adding either a little bit of red or blue based on which side they are leaning towards.
I have tried to look for a plugin that could do this for me but I am unable to find such and I have also tried googling for a way to do this, but without any luck.
You could calculate the color of the two areas and use the three colors for generating the gradient.
function getColor(v, min, max) {
function getC(f, l, r) {
return {
r: Math.floor((1 - f) * l.r + f * r.r),
g: Math.floor((1 - f) * l.g + f * r.g),
b: Math.floor((1 - f) * l.b + f * r.b),
};
}
var left = { r: 255, g: 0, b: 0 },
middle = { r: 0, g: 255, b: 0 },
right = { r: 0, g: 0, b: 255 },
mid = (max - min) / 2;
return v < min + mid ?
getC((v - min) / mid, left, middle) :
getC((v - min - mid) / mid, middle, right);
}
var speeds = [0.5, 0.5, 0.75, 1.25, 0.50, 0.75, 1.00, 4.00, 4.50, 8.00, 7.50, 8.50, 6.50, 6.00, 5.00, 5.25, 4.75, 4.00, 3.25, 2.50, 1.25, 0.00],
min = Math.min(...speeds),
max = Math.max(...speeds);
speeds.forEach(function (a) {
var color = getColor(a, min, max);
document.body.innerHTML += '<span style="color: #fff; background-color: rgb(' + color.r + ',' + color.g + ',' + color.b + ');">' + a + '</span> ';
});
Suppose min is the minimum speed and max is the maximum speed, then all the speeds are between min and max:
min |---------------------------| max
speeds
You have to partition this interval into two smaller intervals, like this:
|-------------|--------------|
min mid max
You can assign to min full Red, to mid full Green, and to max full Blue:
R G B
|-------------|--------------|
min mid max
Now you have to compute for each speed value its color. Suppose that s is the value of one of your speeds:
r = g = b = 0;
if (s <= mid) {
r = 255 - (s - min) / (mid - min) * 255; // r is 255 when s = min and 0 when s = mid
g = 255 - (mid - s) / (mid - min) * 255; // g is 255 when s = mid and 0 when s = min
} else {
b = 255 - (s - mid) / (max - mid) * 255;
g = 255 - (max - s) / (max - mid) * 255;
}
Given your array of speeds, you can do the following:
var speeds = [0.5, 0.5, 0.75, 1.25, 0.50, 0.75, 1.00, 4.00, 4.50, 8.00, 7.50, 8.50, 6.50, 6.00, 5.00, 5.25, 4.75, 4.00, 3.25, 2.50, 1.25, 0.00]
var max = Math.max(...speeds);
var min = Math.min(...speeds);
var mid = (max - min) / 2;
var colors = speeds.map((s) => {
var r, g, b;
r = g = b = 0;
if (s <= mid) {
r = 255 - (s - min) / (mid - min) * 255;
g = 255 - (mid - s) / (mid - min) * 255;
} else {
b = 255 - (s - mid) / (max - mid) * 255;
g = 255 - (max - s) / (max - mid) * 255;
}
return [r, g, b];
});
console.log(colors);
The array colors will contain an [r, g, b] list for each speed in speeds.
Another option would be to simple calculate the hsl value, since you already know the exact colors you are dealing with. Converting from hsl to rgb should not be to hard, there are plenty of libraries out there that do that very well.
Here is an example.
var speeds = [0.5, 0.5, 0.75, 1.25, 0.50, 0.75, 1.00, 4.00, 4.50, 8.00, 7.50, 8.50, 6.50, 6.00, 5.00, 5.25, 4.75, 4.00, 3.25, 2.50, 1.25, 0.00];
var fragment = document.createDocumentFragment();
var list = document.querySelector('ul');
var speedsMin = Math.min(...speeds);
var speedsMax = Math.max(...speeds);
var hslMin = 0;
var hslMax = 240;
var hslValues = speeds.map(function(value) {
return {
h: Math.ceil( ( (value - speedsMin) / (speedsMax - speedsMin) ) * (hslMax - hslMin) + hslMin ),
s: 100,
l: 50
}
})
hslValues.forEach(function(value) {
var item = document.createElement('li');
var color = 'hsl(' + value.h + ',' + value.s + '%,' + value.l + '%)';
item.style.backgroundColor = color;
fragment.appendChild(item)
})
list.appendChild(fragment)
ul {
list-style-type: none
margin: 0;
padding: 0;
}
ul li {
width: 20px;
height: 20px;
border-radius: 10px;
display: inline-block;
margin: 0 4px
}
<ul></ul>
var min...,max...;
var mid=(min+max)/2;
speeds.foreach(function(x,idx){
var r,g,b;
if(x<mid){
b=0;
g=255*(x-min)/(mid-min);
r=255-g;
}else{
r=0;
g=255*(max-x)/(max-mid);
b=255-g;
}
// do something with r-g-b here.
});
The idea is something like this, but after writing I have a hard time bending my brain to verify it. I think it is a correct red->green->blue 2-segment gradient now.
Above 2-3 gradient-segments, I would just really create a palette.
var r=[];g=[];b=[];
// black-red
for(var i=0;i<256;i++){
r.push(i);
g.push(0);
b.push(0);
}
// red-green
for(var i=1;i<256;i++){
r.push(255-i);
g.push(i);
b.push(0);
}
// green-blue
for(var i=1;i<256;i++){
r.push(0);
g.push(255-i);
b.push(i);
}
// blue-white
for(var i=1;i<256;i++){
r.push(i);
g.push(i);
b.push(255);
}
Then you have a palette of 1021 elements, index is x*r.length/(max-min)|0.
A more JavaScript-ish iteration of the latter:
var colors=[];
// black-red
for(var i=0;i<256;i++)colors.push({r:i,g:0,b:0}); // black-red
for(var i=1;i<256;i++)colors.push({r:255-i,g:i,b:0}); // red-green
for(var i=1;i<256;i++)colors.push({r:0,g:255-i,b:i}); // green-blue
for(var i=1;i<256;i++)colors.push({r:i,g:i,b:255}); // blue-white
speeds.foreacy(function(x,idx){
var color=colors[x*colors.length/(max-min)|0]; // r-g-b fields with values 0-255
...
}
If you aren't opposed to using a library you could check out D3.js, specifically the utility to create custom scales. An example of this can be found here
Example
You would need to set up your color scale using your speeds array as a domain and the colors as the output range:
let colors = d3.scale.linear().domain([0, Math.max(...speeds)])
.interpolate(d3.interpolateHcl)
.range([d3.rgb('#FF0000'), d3.rgb('#0000FF')]);
colors is now a function that, given an index as input, will output a color. After that set up, loop through the speeds array to get the corresponding color:
for (let i = 0; i < speeds.length; i++) {
// colors(i)
}
I would sort this array and after this divide your array into 3 smaller arrays. After this operation you should normalize your arrays. You can do it by multiply each element in the list using formula:
(xi - min)/(max - min).
You have to normalize your new arrays not the old one. For first array (the smallest value) you can count red intensive by k=(max-xi)*255.0 . This colors will be (k, 0, 0) . The array with the biggest speeds you colorize by formula: k=xi*255.0 [colors will be (0,0,k)]. The formula for mid values depends on your choice. (Higher speed = more green or Higher speed = less speed).
I'm sorry for complicated description. Good luck.

How to visualise a range of values logarithmically?

I am visualising bubbles for cities, bigger if the city has a bigger value. Eg:
London: 14500
New York: 100
Tokyo: 1100
The values range from ~100-15000
I am having trouble creating a function that will return reasonable values, so that the 100 value bubbles aren't too small. The only way I could think to do this is to set a minimum size, eg:
if (size < 5) { size = 5 }
However, this causes the cities with values of ~100 to look very similar to cities with values of ~1000. I'd like the values of approx 0-15000 to return as values between 0.5 and 1 (or something similar). How would this be done?
Here's what I have so far, but like I said it's causing values of 100 and values of 1000 to both be under the min value:
var minBulletSize = 7.5;
var maxBulletSize = 20;
var maxSquare = maxBulletSize * maxBulletSize * 2 * Math.PI;
var minSquare = minBulletSize * minBulletSize * 2 * Math.PI;
// create circle for each location
for (var i = 0; i < mapData.length; i++) {
var dataItem = mapData[i];
var value = dataItem.value;
// calculate size of a bubble
var square = (value/1000 - minBulletSize) / (maxBulletSize - minBulletSize) * (maxSquare - minSquare) + minSquare;
if (square < minSquare) {
square = minSquare;
}
if (square > maxSquare) {
square = maxSquare;
}
var size = Math.sqrt(square / (Math.PI * 2));
var id = dataItem.code;
}
I have taken a look at how to make a logarithmic function to look "logarithmic" within the constraints of 0.5 and 1 :
Math.log10(x / 0.8 + 1) / 3 + 0.5 where x is in between 0 to 24.5.
This is purely a function that seems to look good for me where you can get very dynamic numbers early although a clear growth can be seen in larger numbers.
Feel free to mess around with the numbers, this is VERY subjective.
Next you will need to fit in your 100~15000 range within 0 to 24.5.
I would simply do a x = (x - 100) / 608.16 to get your range to be within 0 to 24.5.
var minBulletSize = 7.5;
var maxBulletSize = 20;
var maxSquare = maxBulletSize * maxBulletSize * 2 * Math.PI;
var minSquare = minBulletSize * minBulletSize * 2 * Math.PI;
// create circle for each location
for (var i = 0; i < mapData.length; i++) {
var dataItem = mapData[i];
var value = dataItem.value;
// assuming value is in between 100 and 15000
value = (value - 100) / 608.16;
value = Math.log10(value / 0.8 + 1) / 3.0 + 0.5;
// value will be in between 0.5 and 1 on a logarithmic scale.
// Do what you want with it :)
}
Tinker the values within the functions until you find a perfect curve for you.

Color difference/similarity% between two values with JS

I need to compute the difference between two hex color values so the output is a percentage value. The first thing I discarted was converting the hex value into decimal, as the first one will have much higher weight than the last.
The second option is to compute the difference between each of the RGB values and then add them all. However, the difference between 0, 0, 0 and 30, 30, 30 is much lower than the one between 0, 0, 0 and 90, 0, 0.
This question recommends using YUV, but I can't figure out how to use it to establish the difference.
Also, this other question has a nice formula to compute the difference and output a RGB value, but it's not quite there.
For those just looking for a quick copy/paste, here's the code from this repo by antimatter15 (with some tweaks for ease of use):
function deltaE(rgbA, rgbB) {
let labA = rgb2lab(rgbA);
let labB = rgb2lab(rgbB);
let deltaL = labA[0] - labB[0];
let deltaA = labA[1] - labB[1];
let deltaB = labA[2] - labB[2];
let c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
let c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
let deltaC = c1 - c2;
let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
let sc = 1.0 + 0.045 * c1;
let sh = 1.0 + 0.015 * c1;
let deltaLKlsl = deltaL / (1.0);
let deltaCkcsc = deltaC / (sc);
let deltaHkhsh = deltaH / (sh);
let i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh;
return i < 0 ? 0 : Math.sqrt(i);
}
function rgb2lab(rgb){
let r = rgb[0] / 255, g = rgb[1] / 255, b = rgb[2] / 255, x, y, z;
r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
x = (x > 0.008856) ? Math.pow(x, 1/3) : (7.787 * x) + 16/116;
y = (y > 0.008856) ? Math.pow(y, 1/3) : (7.787 * y) + 16/116;
z = (z > 0.008856) ? Math.pow(z, 1/3) : (7.787 * z) + 16/116;
return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)]
}
To use it, just pass in two rgb arrays:
deltaE([128, 0, 255], [128, 0, 255]); // 0
deltaE([128, 0, 255], [128, 0, 230]); // 3.175
deltaE([128, 0, 255], [128, 0, 230]); // 21.434
deltaE([0, 0, 255], [255, 0, 0]); // 61.24
The above table is from here. The above code is based on the 1994 version of DeltaE.
the issue is that you want something like a distance on a 3 dimensionnal world,
but that rgb representation is not intuitive at all : 'near' colors can be
much different that 'far' color.
Take for instance two shades of grey c1 : (120,120,120) and c2 : (150,150,150) and a now take c3 : (160,140,140) it is closer to c2 than c1, yet it is purple, and for the eye the darker grey is much closer to grey than a purple.
I would suggest you to use hsv : a color is defined by a 'base' color (hue), the saturation, and the intensity. colors having close hue are indeed very close. colors having very different hue do not relate one to another (expl : yellow and green ) but might seem closer with a (very) low saturation and (very) low intensity.
( At night all colors are alike. )
Since the hue is divided into 6 blocks, cyl = Math.floor( hue / 6 ) gives you the first step of your similarity evalution : if same part of the cylinder -> quite close.
If they don't belong to same cylinder, they might still be (quite) close if (h2-h1) is small, compare it to (1/6). If (h2-h1) > 1/6 this might just be too different colors.
Then you can be more precise with the (s,v). Colors they are nearer if both low/very low saturation and/or low intensity.
Play around with a color picker supporting both rgb and hsv until you know what you would like to have as a difference value. But be aware that you cannot have a 'true' similarity measure.
you have a rgb --> hsv javascript convertor here : http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
Just compute an Euclidean distance:
var c1 = [0, 0, 0],
c2 = [30, 30, 30],
c3 = [90, 0, 0],
distance = function(v1, v2){
var i,
d = 0;
for (i = 0; i < v1.length; i++) {
d += (v1[i] - v2[i])*(v1[i] - v2[i]);
}
return Math.sqrt(d);
};
console.log( distance(c1, c2), distance(c1, c3), distance(c2, c3) );
//will give you 51.96152422706632 90 73.48469228349535
I released an npm/Bower package for calculating the three CIE algorithms: de76, de94, and de00.
It's public domain and on Github:
http://zschuessler.github.io/DeltaE/
Here's a quickstart guide:
Install via npm
npm install delta-e
Usage
// Include library
var DeltaE = require('delta-e');
// Create two test LAB color objects to compare!
var color1 = {L: 36, A: 60, B: 41};
var color2 = {L: 100, A: 40, B: 90};
// 1976 formula
console.log(DeltaE.getDeltaE76(color1, color2));
// 1994 formula
console.log(DeltaE.getDeltaE94(color1, color2));
// 2000 formula
console.log(DeltaE.getDeltaE00(color1, color2));
You will need to convert to LAB color to use this library. d3.js has an excellent API for doing that - and I'm sure you can find something adhoc as well.
The 3rd rule for color comparisons on ColorWiki is "Never attempt to convert between color differences calculated by different equations through the use of averaging factors". This is because colors that are mathematically close to each other aren't always visually similar to us humans.
What you're looking for is probably delta-e, which is a single number that represents the 'distance' between two colors.
The most popular algorithms are listed below, with CIE76 (aka CIE 1976 or dE76) being the most popular.
CIE76
CMC l:c
dE94
dE2000
Each one goes about things in a different way, but for the most part they all require you to convert to a better (for comparison) color model than RGB.
Wikipedia has all the formulae: http://en.wikipedia.org/wiki/Color_difference
You can check your work with online color calculators:
CIE76
CMC l:c
Finally, it's not javascript but there's an open-source c# library I started will do some of these conversions and calculations: https://github.com/THEjoezack/ColorMine
Using Color.js:
let Color = await import("https://cdn.jsdelivr.net/npm/colorjs.io#0.0.5/dist/color.esm.js").then(m => m.default);
let color1 = new Color(`rgb(10,230,95)`);
let color2 = new Color(`rgb(100,20,130)`);
let colorDistance = color1.deltaE2000(color2);
A distance of 0 means the colors are identical, and a value of 100 means they're opposite.
deltaE2000 is the current industry standard (it's better than the 1994 version mentioned in top answer), but Color.js also has other algorithms like deltaE76, deltaECMC and deltaEITP.

Categories